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