|  | // 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 | 
|  | } |