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