| // 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" |
| "sync" |
| "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 |
| |
| mu sync.Mutex // protects requests: |
| 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 { |
| c.mu.Lock() |
| req, ok := c.requests[rec.h.Id] |
| c.mu.Unlock() |
| 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.mu.Lock() |
| c.requests[rec.h.Id] = req |
| c.mu.Unlock() |
| 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 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: |
| c.mu.Lock() |
| delete(c.requests, rec.h.Id) |
| c.mu.Unlock() |
| 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.mu.Lock() |
| delete(c.requests, req.reqId) |
| c.mu.Unlock() |
| 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() { |
| c.mu.Lock() |
| defer c.mu.Unlock() |
| 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 |
| } |