2012-03-27 23:13:14 +00:00
|
|
|
// 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 (
|
2018-12-28 15:30:48 +00:00
|
|
|
"context"
|
2012-03-27 23:13:14 +00:00
|
|
|
"errors"
|
|
|
|
"fmt"
|
|
|
|
"io"
|
|
|
|
"net"
|
|
|
|
"net/http"
|
|
|
|
"net/http/cgi"
|
|
|
|
"os"
|
2014-09-21 17:33:12 +00:00
|
|
|
"strings"
|
2012-03-27 23:13:14 +00:00
|
|
|
"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
|
|
|
|
}
|
|
|
|
|
2018-12-28 15:30:48 +00:00
|
|
|
// envVarsContextKey uniquely identifies a mapping of CGI
|
|
|
|
// environment variables to their values in a request context
|
|
|
|
type envVarsContextKey struct{}
|
|
|
|
|
2012-03-27 23:13:14 +00:00
|
|
|
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:]
|
2017-04-10 11:32:00 +00:00
|
|
|
if int(keyLen)+int(valLen) > len(text) {
|
|
|
|
return
|
|
|
|
}
|
2012-03-27 23:13:14 +00:00
|
|
|
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 {
|
2022-10-27 18:55:19 +00:00
|
|
|
req *request
|
|
|
|
header http.Header
|
|
|
|
code int
|
|
|
|
wroteHeader bool
|
|
|
|
wroteCGIHeader bool
|
|
|
|
w *bufWriter
|
2012-03-27 23:13:14 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
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
|
|
|
|
}
|
|
|
|
|
2022-10-27 18:55:19 +00:00
|
|
|
func (r *response) Write(p []byte) (n int, err error) {
|
2012-03-27 23:13:14 +00:00
|
|
|
if !r.wroteHeader {
|
|
|
|
r.WriteHeader(http.StatusOK)
|
|
|
|
}
|
2022-10-27 18:55:19 +00:00
|
|
|
if !r.wroteCGIHeader {
|
|
|
|
r.writeCGIHeader(p)
|
|
|
|
}
|
|
|
|
return r.w.Write(p)
|
2012-03-27 23:13:14 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
func (r *response) WriteHeader(code int) {
|
|
|
|
if r.wroteHeader {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
r.wroteHeader = true
|
2022-10-27 18:55:19 +00:00
|
|
|
r.code = code
|
2012-03-27 23:13:14 +00:00
|
|
|
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))
|
|
|
|
}
|
2022-10-27 18:55:19 +00:00
|
|
|
}
|
2012-03-27 23:13:14 +00:00
|
|
|
|
2022-10-27 18:55:19 +00:00
|
|
|
// 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))
|
|
|
|
}
|
2012-03-27 23:13:14 +00:00
|
|
|
r.header.Write(r.w)
|
|
|
|
r.w.WriteString("\r\n")
|
2022-10-27 18:55:19 +00:00
|
|
|
r.w.Flush()
|
2012-03-27 23:13:14 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
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 {
|
2015-08-28 15:33:40 +00:00
|
|
|
conn *conn
|
|
|
|
handler http.Handler
|
|
|
|
|
2012-03-27 23:13:14 +00:00
|
|
|
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()
|
2017-04-10 11:32:00 +00:00
|
|
|
defer c.cleanUp()
|
2012-03-27 23:13:14 +00:00
|
|
|
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")
|
|
|
|
|
2022-10-27 18:55:19 +00:00
|
|
|
var emptyBody = io.NopCloser(strings.NewReader(""))
|
2014-09-21 17:33:12 +00:00
|
|
|
|
2017-04-10 11:32:00 +00:00
|
|
|
// 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")
|
|
|
|
|
2012-03-27 23:13:14 +00:00
|
|
|
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:
|
2014-09-21 17:33:12 +00:00
|
|
|
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")
|
|
|
|
}
|
|
|
|
|
2012-03-27 23:13:14 +00:00
|
|
|
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
|
|
|
|
}
|
2015-08-28 15:33:40 +00:00
|
|
|
req = newRequest(rec.h.Id, br.flags)
|
|
|
|
c.requests[rec.h.Id] = req
|
2014-09-21 17:33:12 +00:00
|
|
|
return nil
|
2012-03-27 23:13:14 +00:00
|
|
|
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()
|
2014-09-21 17:33:12 +00:00
|
|
|
return nil
|
2012-03-27 23:13:14 +00:00
|
|
|
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()
|
2014-09-21 17:33:12 +00:00
|
|
|
} else {
|
|
|
|
body = emptyBody
|
2012-03-27 23:13:14 +00:00
|
|
|
}
|
|
|
|
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)
|
2022-10-27 18:55:19 +00:00
|
|
|
} else {
|
|
|
|
delete(c.requests, req.reqId)
|
|
|
|
if req.pw != nil {
|
|
|
|
req.pw.Close()
|
|
|
|
}
|
2012-03-27 23:13:14 +00:00
|
|
|
}
|
2014-09-21 17:33:12 +00:00
|
|
|
return nil
|
2012-03-27 23:13:14 +00:00
|
|
|
case typeGetValues:
|
|
|
|
values := map[string]string{"FCGI_MPXS_CONNS": "1"}
|
|
|
|
c.conn.writePairs(typeGetValuesResult, 0, values)
|
2014-09-21 17:33:12 +00:00
|
|
|
return nil
|
2012-03-27 23:13:14 +00:00
|
|
|
case typeData:
|
|
|
|
// If the filter role is implemented, read the data stream here.
|
2014-09-21 17:33:12 +00:00
|
|
|
return nil
|
2012-03-27 23:13:14 +00:00
|
|
|
case typeAbortRequest:
|
|
|
|
delete(c.requests, rec.h.Id)
|
|
|
|
c.conn.writeEndRequest(rec.h.Id, 0, statusRequestComplete)
|
2017-04-10 11:32:00 +00:00
|
|
|
if req.pw != nil {
|
|
|
|
req.pw.CloseWithError(ErrRequestAborted)
|
|
|
|
}
|
2012-03-27 23:13:14 +00:00
|
|
|
if !req.keepConn {
|
|
|
|
// connection will close upon return
|
|
|
|
return errCloseConn
|
|
|
|
}
|
2014-09-21 17:33:12 +00:00
|
|
|
return nil
|
2012-03-27 23:13:14 +00:00
|
|
|
default:
|
|
|
|
b := make([]byte, 8)
|
|
|
|
b[0] = byte(rec.h.Type)
|
|
|
|
c.conn.writeRecord(typeUnknownType, 0, b)
|
2014-09-21 17:33:12 +00:00
|
|
|
return nil
|
2012-03-27 23:13:14 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-12-28 15:30:48 +00:00
|
|
|
// 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
|
|
|
|
}
|
|
|
|
|
2012-03-27 23:13:14 +00:00
|
|
|
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
|
2018-12-28 15:30:48 +00:00
|
|
|
withoutUsedEnvVars := filterOutUsedEnvVars(req.params)
|
|
|
|
envVarCtx := context.WithValue(httpReq.Context(), envVarsContextKey{}, withoutUsedEnvVars)
|
|
|
|
httpReq = httpReq.WithContext(envVarCtx)
|
2012-03-27 23:13:14 +00:00
|
|
|
c.handler.ServeHTTP(r, httpReq)
|
|
|
|
}
|
2022-10-27 18:55:19 +00:00
|
|
|
// Make sure we serve something even if nothing was written to r
|
|
|
|
r.Write(nil)
|
2012-03-27 23:13:14 +00:00
|
|
|
r.Close()
|
|
|
|
c.conn.writeEndRequest(req.reqId, 0, statusRequestComplete)
|
2014-09-21 17:33:12 +00:00
|
|
|
|
|
|
|
// 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
|
2022-10-27 18:55:19 +00:00
|
|
|
io.CopyN(io.Discard, body, 100<<20)
|
2014-09-21 17:33:12 +00:00
|
|
|
body.Close()
|
|
|
|
|
2012-03-27 23:13:14 +00:00
|
|
|
if !req.keepConn {
|
|
|
|
c.conn.Close()
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-04-10 11:32:00 +00:00
|
|
|
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)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2012-03-27 23:13:14 +00:00
|
|
|
// 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()
|
|
|
|
}
|
|
|
|
}
|
2018-12-28 15:30:48 +00:00
|
|
|
|
|
|
|
// 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
|
|
|
|
}
|