// Copyright 2011 The Go Authors. All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. package fcgi // This file implements FastCGI from the perspective of a child process. import ( "context" "errors" "fmt" "io" "net" "net/http" "net/http/cgi" "os" "strings" "time" ) // request holds the state for an in-progress request. As soon as it's complete, // it's converted to an http.Request. type request struct { pw *io.PipeWriter reqId uint16 params map[string]string buf [1024]byte rawParams []byte keepConn bool } // envVarsContextKey uniquely identifies a mapping of CGI // environment variables to their values in a request context type envVarsContextKey struct{} func newRequest(reqId uint16, flags uint8) *request { r := &request{ reqId: reqId, params: map[string]string{}, keepConn: flags&flagKeepConn != 0, } r.rawParams = r.buf[:0] return r } // parseParams reads an encoded []byte into Params. func (r *request) parseParams() { text := r.rawParams r.rawParams = nil for len(text) > 0 { keyLen, n := readSize(text) if n == 0 { return } text = text[n:] valLen, n := readSize(text) if n == 0 { return } text = text[n:] if int(keyLen)+int(valLen) > len(text) { return } key := readString(text, keyLen) text = text[keyLen:] val := readString(text, valLen) text = text[valLen:] r.params[key] = val } } // response implements http.ResponseWriter. type response struct { req *request header http.Header code int wroteHeader bool wroteCGIHeader bool w *bufWriter } func newResponse(c *child, req *request) *response { return &response{ req: req, header: http.Header{}, w: newWriter(c.conn, typeStdout, req.reqId), } } func (r *response) Header() http.Header { return r.header } func (r *response) Write(p []byte) (n int, err error) { if !r.wroteHeader { r.WriteHeader(http.StatusOK) } if !r.wroteCGIHeader { r.writeCGIHeader(p) } return r.w.Write(p) } func (r *response) WriteHeader(code int) { if r.wroteHeader { return } r.wroteHeader = true r.code = code if code == http.StatusNotModified { // Must not have body. r.header.Del("Content-Type") r.header.Del("Content-Length") r.header.Del("Transfer-Encoding") } if r.header.Get("Date") == "" { r.header.Set("Date", time.Now().UTC().Format(http.TimeFormat)) } } // writeCGIHeader finalizes the header sent to the client and writes it to the output. // p is not written by writeHeader, but is the first chunk of the body // that will be written. It is sniffed for a Content-Type if none is // set explicitly. func (r *response) writeCGIHeader(p []byte) { if r.wroteCGIHeader { return } r.wroteCGIHeader = true fmt.Fprintf(r.w, "Status: %d %s\r\n", r.code, http.StatusText(r.code)) if _, hasType := r.header["Content-Type"]; r.code != http.StatusNotModified && !hasType { r.header.Set("Content-Type", http.DetectContentType(p)) } r.header.Write(r.w) r.w.WriteString("\r\n") r.w.Flush() } func (r *response) Flush() { if !r.wroteHeader { r.WriteHeader(http.StatusOK) } r.w.Flush() } func (r *response) Close() error { r.Flush() return r.w.Close() } type child struct { conn *conn handler http.Handler requests map[uint16]*request // keyed by request ID } func newChild(rwc io.ReadWriteCloser, handler http.Handler) *child { return &child{ conn: newConn(rwc), handler: handler, requests: make(map[uint16]*request), } } func (c *child) serve() { defer c.conn.Close() defer c.cleanUp() var rec record for { if err := rec.read(c.conn.rwc); err != nil { return } if err := c.handleRecord(&rec); err != nil { return } } } var errCloseConn = errors.New("fcgi: connection should be closed") var emptyBody = io.NopCloser(strings.NewReader("")) // ErrRequestAborted is returned by Read when a handler attempts to read the // body of a request that has been aborted by the web server. var ErrRequestAborted = errors.New("fcgi: request aborted by web server") // ErrConnClosed is returned by Read when a handler attempts to read the body of // a request after the connection to the web server has been closed. var ErrConnClosed = errors.New("fcgi: connection to web server closed") func (c *child) handleRecord(rec *record) error { req, ok := c.requests[rec.h.Id] if !ok && rec.h.Type != typeBeginRequest && rec.h.Type != typeGetValues { // The spec says to ignore unknown request IDs. return nil } switch rec.h.Type { case typeBeginRequest: if req != nil { // The server is trying to begin a request with the same ID // as an in-progress request. This is an error. return errors.New("fcgi: received ID that is already in-flight") } var br beginRequest if err := br.read(rec.content()); err != nil { return err } if br.role != roleResponder { c.conn.writeEndRequest(rec.h.Id, 0, statusUnknownRole) return nil } req = newRequest(rec.h.Id, br.flags) c.requests[rec.h.Id] = req return nil case typeParams: // NOTE(eds): Technically a key-value pair can straddle the boundary // between two packets. We buffer until we've received all parameters. if len(rec.content()) > 0 { req.rawParams = append(req.rawParams, rec.content()...) return nil } req.parseParams() return nil case typeStdin: content := rec.content() if req.pw == nil { var body io.ReadCloser if len(content) > 0 { // body could be an io.LimitReader, but it shouldn't matter // as long as both sides are behaving. body, req.pw = io.Pipe() } else { body = emptyBody } go c.serveRequest(req, body) } if len(content) > 0 { // TODO(eds): This blocks until the handler reads from the pipe. // If the handler takes a long time, it might be a problem. req.pw.Write(content) } else { delete(c.requests, req.reqId) if req.pw != nil { req.pw.Close() } } return nil case typeGetValues: values := map[string]string{"FCGI_MPXS_CONNS": "1"} c.conn.writePairs(typeGetValuesResult, 0, values) return nil case typeData: // If the filter role is implemented, read the data stream here. return nil case typeAbortRequest: delete(c.requests, rec.h.Id) c.conn.writeEndRequest(rec.h.Id, 0, statusRequestComplete) if req.pw != nil { req.pw.CloseWithError(ErrRequestAborted) } if !req.keepConn { // connection will close upon return return errCloseConn } return nil default: b := make([]byte, 8) b[0] = byte(rec.h.Type) c.conn.writeRecord(typeUnknownType, 0, b) return nil } } // filterOutUsedEnvVars returns a new map of env vars without the // variables in the given envVars map that are read for creating each http.Request func filterOutUsedEnvVars(envVars map[string]string) map[string]string { withoutUsedEnvVars := make(map[string]string) for k, v := range envVars { if addFastCGIEnvToContext(k) { withoutUsedEnvVars[k] = v } } return withoutUsedEnvVars } func (c *child) serveRequest(req *request, body io.ReadCloser) { r := newResponse(c, req) httpReq, err := cgi.RequestFromMap(req.params) if err != nil { // there was an error reading the request r.WriteHeader(http.StatusInternalServerError) c.conn.writeRecord(typeStderr, req.reqId, []byte(err.Error())) } else { httpReq.Body = body withoutUsedEnvVars := filterOutUsedEnvVars(req.params) envVarCtx := context.WithValue(httpReq.Context(), envVarsContextKey{}, withoutUsedEnvVars) httpReq = httpReq.WithContext(envVarCtx) c.handler.ServeHTTP(r, httpReq) } // Make sure we serve something even if nothing was written to r r.Write(nil) r.Close() c.conn.writeEndRequest(req.reqId, 0, statusRequestComplete) // Consume the entire body, so the host isn't still writing to // us when we close the socket below in the !keepConn case, // otherwise we'd send a RST. (golang.org/issue/4183) // TODO(bradfitz): also bound this copy in time. Or send // some sort of abort request to the host, so the host // can properly cut off the client sending all the data. // For now just bound it a little and io.CopyN(io.Discard, body, 100<<20) body.Close() if !req.keepConn { c.conn.Close() } } func (c *child) cleanUp() { for _, req := range c.requests { if req.pw != nil { // race with call to Close in c.serveRequest doesn't matter because // Pipe(Reader|Writer).Close are idempotent req.pw.CloseWithError(ErrConnClosed) } } } // Serve accepts incoming FastCGI connections on the listener l, creating a new // goroutine for each. The goroutine reads requests and then calls handler // to reply to them. // If l is nil, Serve accepts connections from os.Stdin. // If handler is nil, [http.DefaultServeMux] is used. func Serve(l net.Listener, handler http.Handler) error { if l == nil { var err error l, err = net.FileListener(os.Stdin) if err != nil { return err } defer l.Close() } if handler == nil { handler = http.DefaultServeMux } for { rw, err := l.Accept() if err != nil { return err } c := newChild(rw, handler) go c.serve() } } // ProcessEnv returns FastCGI environment variables associated with the request r // for which no effort was made to be included in the request itself - the data // is hidden in the request's context. As an example, if REMOTE_USER is set for a // request, it will not be found anywhere in r, but it will be included in // ProcessEnv's response (via r's context). func ProcessEnv(r *http.Request) map[string]string { env, _ := r.Context().Value(envVarsContextKey{}).(map[string]string) return env } // addFastCGIEnvToContext reports whether to include the FastCGI environment variable s // in the http.Request.Context, accessible via ProcessEnv. func addFastCGIEnvToContext(s string) bool { // Exclude things supported by net/http natively: switch s { case "CONTENT_LENGTH", "CONTENT_TYPE", "HTTPS", "PATH_INFO", "QUERY_STRING", "REMOTE_ADDR", "REMOTE_HOST", "REMOTE_PORT", "REQUEST_METHOD", "REQUEST_URI", "SCRIPT_NAME", "SERVER_PROTOCOL": return false } if strings.HasPrefix(s, "HTTP_") { return false } // Explicitly include FastCGI-specific things. // This list is redundant with the default "return true" below. // Consider this documentation of the sorts of things we expect // to maybe see. switch s { case "REMOTE_USER": return true } // Unknown, so include it to be safe. return true }