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