github.com/useflyent/fhttp@v0.0.0-20211004035111-333f430cfbbf/fcgi/child.go (about) 1 // Copyright 2011 The Go Authors. All rights reserved. 2 // Use of this source code is governed by a BSD-style 3 // license that can be found in the LICENSE file. 4 5 package fcgi 6 7 // This file implements FastCGI from the perspective of a child process. 8 9 import ( 10 "context" 11 "errors" 12 "fmt" 13 "io" 14 "net" 15 "os" 16 "strings" 17 "sync" 18 "time" 19 20 http "github.com/useflyent/fhttp" 21 "github.com/useflyent/fhttp/cgi" 22 ) 23 24 // request holds the state for an in-progress request. As soon as it's complete, 25 // it's converted to an http.Request. 26 type request struct { 27 pw *io.PipeWriter 28 reqId uint16 29 params map[string]string 30 buf [1024]byte 31 rawParams []byte 32 keepConn bool 33 } 34 35 // envVarsContextKey uniquely identifies a mapping of CGI 36 // environment variables to their values in a request context 37 type envVarsContextKey struct{} 38 39 func newRequest(reqId uint16, flags uint8) *request { 40 r := &request{ 41 reqId: reqId, 42 params: map[string]string{}, 43 keepConn: flags&flagKeepConn != 0, 44 } 45 r.rawParams = r.buf[:0] 46 return r 47 } 48 49 // parseParams reads an encoded []byte into Params. 50 func (r *request) parseParams() { 51 text := r.rawParams 52 r.rawParams = nil 53 for len(text) > 0 { 54 keyLen, n := readSize(text) 55 if n == 0 { 56 return 57 } 58 text = text[n:] 59 valLen, n := readSize(text) 60 if n == 0 { 61 return 62 } 63 text = text[n:] 64 if int(keyLen)+int(valLen) > len(text) { 65 return 66 } 67 key := readString(text, keyLen) 68 text = text[keyLen:] 69 val := readString(text, valLen) 70 text = text[valLen:] 71 r.params[key] = val 72 } 73 } 74 75 // response implements http.ResponseWriter. 76 type response struct { 77 req *request 78 header http.Header 79 code int 80 wroteHeader bool 81 wroteCGIHeader bool 82 w *bufWriter 83 } 84 85 func newResponse(c *child, req *request) *response { 86 return &response{ 87 req: req, 88 header: http.Header{}, 89 w: newWriter(c.conn, typeStdout, req.reqId), 90 } 91 } 92 93 func (r *response) Header() http.Header { 94 return r.header 95 } 96 97 func (r *response) Write(p []byte) (n int, err error) { 98 if !r.wroteHeader { 99 r.WriteHeader(http.StatusOK) 100 } 101 if !r.wroteCGIHeader { 102 r.writeCGIHeader(p) 103 } 104 return r.w.Write(p) 105 } 106 107 func (r *response) WriteHeader(code int) { 108 if r.wroteHeader { 109 return 110 } 111 r.wroteHeader = true 112 r.code = code 113 if code == http.StatusNotModified { 114 // Must not have body. 115 r.header.Del("Content-Type") 116 r.header.Del("Content-Length") 117 r.header.Del("Transfer-Encoding") 118 } 119 if r.header.Get("Date") == "" { 120 r.header.Set("Date", time.Now().UTC().Format(http.TimeFormat)) 121 } 122 } 123 124 // writeCGIHeader finalizes the header sent to the client and writes it to the output. 125 // p is not written by writeHeader, but is the first chunk of the body 126 // that will be written. It is sniffed for a Content-Type if none is 127 // set explicitly. 128 func (r *response) writeCGIHeader(p []byte) { 129 if r.wroteCGIHeader { 130 return 131 } 132 r.wroteCGIHeader = true 133 fmt.Fprintf(r.w, "Status: %d %s\r\n", r.code, http.StatusText(r.code)) 134 if _, hasType := r.header["Content-Type"]; r.code != http.StatusNotModified && !hasType { 135 r.header.Set("Content-Type", http.DetectContentType(p)) 136 } 137 r.header.Write(r.w) 138 r.w.WriteString("\r\n") 139 r.w.Flush() 140 } 141 142 func (r *response) Flush() { 143 if !r.wroteHeader { 144 r.WriteHeader(http.StatusOK) 145 } 146 r.w.Flush() 147 } 148 149 func (r *response) Close() error { 150 r.Flush() 151 return r.w.Close() 152 } 153 154 type child struct { 155 conn *conn 156 handler http.Handler 157 158 mu sync.Mutex // protects requests: 159 requests map[uint16]*request // keyed by request ID 160 } 161 162 func newChild(rwc io.ReadWriteCloser, handler http.Handler) *child { 163 return &child{ 164 conn: newConn(rwc), 165 handler: handler, 166 requests: make(map[uint16]*request), 167 } 168 } 169 170 func (c *child) serve() { 171 defer c.conn.Close() 172 defer c.cleanUp() 173 var rec record 174 for { 175 if err := rec.read(c.conn.rwc); err != nil { 176 return 177 } 178 if err := c.handleRecord(&rec); err != nil { 179 return 180 } 181 } 182 } 183 184 var errCloseConn = errors.New("fcgi: connection should be closed") 185 186 var emptyBody = io.NopCloser(strings.NewReader("")) 187 188 // ErrRequestAborted is returned by Read when a handler attempts to read the 189 // body of a request that has been aborted by the web server. 190 var ErrRequestAborted = errors.New("fcgi: request aborted by web server") 191 192 // ErrConnClosed is returned by Read when a handler attempts to read the body of 193 // a request after the connection to the web server has been closed. 194 var ErrConnClosed = errors.New("fcgi: connection to web server closed") 195 196 func (c *child) handleRecord(rec *record) error { 197 c.mu.Lock() 198 req, ok := c.requests[rec.h.Id] 199 c.mu.Unlock() 200 if !ok && rec.h.Type != typeBeginRequest && rec.h.Type != typeGetValues { 201 // The spec says to ignore unknown request IDs. 202 return nil 203 } 204 205 switch rec.h.Type { 206 case typeBeginRequest: 207 if req != nil { 208 // The server is trying to begin a request with the same ID 209 // as an in-progress request. This is an error. 210 return errors.New("fcgi: received ID that is already in-flight") 211 } 212 213 var br beginRequest 214 if err := br.read(rec.content()); err != nil { 215 return err 216 } 217 if br.role != roleResponder { 218 c.conn.writeEndRequest(rec.h.Id, 0, statusUnknownRole) 219 return nil 220 } 221 req = newRequest(rec.h.Id, br.flags) 222 c.mu.Lock() 223 c.requests[rec.h.Id] = req 224 c.mu.Unlock() 225 return nil 226 case typeParams: 227 // NOTE(eds): Technically a key-value pair can straddle the boundary 228 // between two packets. We buffer until we've received all parameters. 229 if len(rec.content()) > 0 { 230 req.rawParams = append(req.rawParams, rec.content()...) 231 return nil 232 } 233 req.parseParams() 234 return nil 235 case typeStdin: 236 content := rec.content() 237 if req.pw == nil { 238 var body io.ReadCloser 239 if len(content) > 0 { 240 // body could be an io.LimitReader, but it shouldn't matter 241 // as long as both sides are behaving. 242 body, req.pw = io.Pipe() 243 } else { 244 body = emptyBody 245 } 246 go c.serveRequest(req, body) 247 } 248 if len(content) > 0 { 249 // TODO(eds): This blocks until the handler reads from the pipe. 250 // If the handler takes a long time, it might be a problem. 251 req.pw.Write(content) 252 } else if req.pw != nil { 253 req.pw.Close() 254 } 255 return nil 256 case typeGetValues: 257 values := map[string]string{"FCGI_MPXS_CONNS": "1"} 258 c.conn.writePairs(typeGetValuesResult, 0, values) 259 return nil 260 case typeData: 261 // If the filter role is implemented, read the data stream here. 262 return nil 263 case typeAbortRequest: 264 c.mu.Lock() 265 delete(c.requests, rec.h.Id) 266 c.mu.Unlock() 267 c.conn.writeEndRequest(rec.h.Id, 0, statusRequestComplete) 268 if req.pw != nil { 269 req.pw.CloseWithError(ErrRequestAborted) 270 } 271 if !req.keepConn { 272 // connection will close upon return 273 return errCloseConn 274 } 275 return nil 276 default: 277 b := make([]byte, 8) 278 b[0] = byte(rec.h.Type) 279 c.conn.writeRecord(typeUnknownType, 0, b) 280 return nil 281 } 282 } 283 284 // filterOutUsedEnvVars returns a new map of env vars without the 285 // variables in the given envVars map that are read for creating each http.Request 286 func filterOutUsedEnvVars(envVars map[string]string) map[string]string { 287 withoutUsedEnvVars := make(map[string]string) 288 for k, v := range envVars { 289 if addFastCGIEnvToContext(k) { 290 withoutUsedEnvVars[k] = v 291 } 292 } 293 return withoutUsedEnvVars 294 } 295 296 func (c *child) serveRequest(req *request, body io.ReadCloser) { 297 r := newResponse(c, req) 298 httpReq, err := cgi.RequestFromMap(req.params) 299 if err != nil { 300 // there was an error reading the request 301 r.WriteHeader(http.StatusInternalServerError) 302 c.conn.writeRecord(typeStderr, req.reqId, []byte(err.Error())) 303 } else { 304 httpReq.Body = body 305 withoutUsedEnvVars := filterOutUsedEnvVars(req.params) 306 envVarCtx := context.WithValue(httpReq.Context(), envVarsContextKey{}, withoutUsedEnvVars) 307 httpReq = httpReq.WithContext(envVarCtx) 308 c.handler.ServeHTTP(r, httpReq) 309 } 310 // Make sure we serve something even if nothing was written to r 311 r.Write(nil) 312 r.Close() 313 c.mu.Lock() 314 delete(c.requests, req.reqId) 315 c.mu.Unlock() 316 c.conn.writeEndRequest(req.reqId, 0, statusRequestComplete) 317 318 // Consume the entire body, so the host isn't still writing to 319 // us when we close the socket below in the !keepConn case, 320 // otherwise we'd send a RST. (golang.org/issue/4183) 321 // TODO(bradfitz): also bound this copy in time. Or send 322 // some sort of abort request to the host, so the host 323 // can properly cut off the client sending all the data. 324 // For now just bound it a little and 325 io.CopyN(io.Discard, body, 100<<20) 326 body.Close() 327 328 if !req.keepConn { 329 c.conn.Close() 330 } 331 } 332 333 func (c *child) cleanUp() { 334 c.mu.Lock() 335 defer c.mu.Unlock() 336 for _, req := range c.requests { 337 if req.pw != nil { 338 // race with call to Close in c.serveRequest doesn't matter because 339 // Pipe(Reader|Writer).Close are idempotent 340 req.pw.CloseWithError(ErrConnClosed) 341 } 342 } 343 } 344 345 // Serve accepts incoming FastCGI connections on the listener l, creating a new 346 // goroutine for each. The goroutine reads requests and then calls handler 347 // to reply to them. 348 // If l is nil, Serve accepts connections from os.Stdin. 349 // If handler is nil, http.DefaultServeMux is used. 350 func Serve(l net.Listener, handler http.Handler) error { 351 if l == nil { 352 var err error 353 l, err = net.FileListener(os.Stdin) 354 if err != nil { 355 return err 356 } 357 defer l.Close() 358 } 359 if handler == nil { 360 handler = http.DefaultServeMux 361 } 362 for { 363 rw, err := l.Accept() 364 if err != nil { 365 return err 366 } 367 c := newChild(rw, handler) 368 go c.serve() 369 } 370 } 371 372 // ProcessEnv returns FastCGI environment variables associated with the request r 373 // for which no effort was made to be included in the request itself - the data 374 // is hidden in the request's context. As an example, if REMOTE_USER is set for a 375 // request, it will not be found anywhere in r, but it will be included in 376 // ProcessEnv's response (via r's context). 377 func ProcessEnv(r *http.Request) map[string]string { 378 env, _ := r.Context().Value(envVarsContextKey{}).(map[string]string) 379 return env 380 } 381 382 // addFastCGIEnvToContext reports whether to include the FastCGI environment variable s 383 // in the http.Request.Context, accessible via ProcessEnv. 384 func addFastCGIEnvToContext(s string) bool { 385 // Exclude things supported by net/http natively: 386 switch s { 387 case "CONTENT_LENGTH", "CONTENT_TYPE", "HTTPS", 388 "PATH_INFO", "QUERY_STRING", "REMOTE_ADDR", 389 "REMOTE_HOST", "REMOTE_PORT", "REQUEST_METHOD", 390 "REQUEST_URI", "SCRIPT_NAME", "SERVER_PROTOCOL": 391 return false 392 } 393 if strings.HasPrefix(s, "HTTP_") { 394 return false 395 } 396 // Explicitly include FastCGI-specific things. 397 // This list is redundant with the default "return true" below. 398 // Consider this documentation of the sorts of things we expect 399 // to maybe see. 400 switch s { 401 case "REMOTE_USER": 402 return true 403 } 404 // Unknown, so include it to be safe. 405 return true 406 }