Home | History | Annotate | Download | only in fcgi
      1 // Copyright 2011 The Go Authors. All rights reserved.
      2 // Use of this source code is governed by a BSD-style
      3 // license that can be found in the LICENSE file.
      4 
      5 package fcgi
      6 
      7 // This file implements FastCGI from the perspective of a child process.
      8 
      9 import (
     10 	"context"
     11 	"errors"
     12 	"fmt"
     13 	"io"
     14 	"io/ioutil"
     15 	"net"
     16 	"net/http"
     17 	"net/http/cgi"
     18 	"os"
     19 	"strings"
     20 	"sync"
     21 	"time"
     22 )
     23 
     24 // request holds the state for an in-progress request. As soon as it's complete,
     25 // it's converted to an http.Request.
     26 type request struct {
     27 	pw        *io.PipeWriter
     28 	reqId     uint16
     29 	params    map[string]string
     30 	buf       [1024]byte
     31 	rawParams []byte
     32 	keepConn  bool
     33 }
     34 
     35 // envVarsContextKey uniquely identifies a mapping of CGI
     36 // environment variables to their values in a request context
     37 type envVarsContextKey struct{}
     38 
     39 func newRequest(reqId uint16, flags uint8) *request {
     40 	r := &request{
     41 		reqId:    reqId,
     42 		params:   map[string]string{},
     43 		keepConn: flags&flagKeepConn != 0,
     44 	}
     45 	r.rawParams = r.buf[:0]
     46 	return r
     47 }
     48 
     49 // parseParams reads an encoded []byte into Params.
     50 func (r *request) parseParams() {
     51 	text := r.rawParams
     52 	r.rawParams = nil
     53 	for len(text) > 0 {
     54 		keyLen, n := readSize(text)
     55 		if n == 0 {
     56 			return
     57 		}
     58 		text = text[n:]
     59 		valLen, n := readSize(text)
     60 		if n == 0 {
     61 			return
     62 		}
     63 		text = text[n:]
     64 		if int(keyLen)+int(valLen) > len(text) {
     65 			return
     66 		}
     67 		key := readString(text, keyLen)
     68 		text = text[keyLen:]
     69 		val := readString(text, valLen)
     70 		text = text[valLen:]
     71 		r.params[key] = val
     72 	}
     73 }
     74 
     75 // response implements http.ResponseWriter.
     76 type response struct {
     77 	req         *request
     78 	header      http.Header
     79 	w           *bufWriter
     80 	wroteHeader bool
     81 }
     82 
     83 func newResponse(c *child, req *request) *response {
     84 	return &response{
     85 		req:    req,
     86 		header: http.Header{},
     87 		w:      newWriter(c.conn, typeStdout, req.reqId),
     88 	}
     89 }
     90 
     91 func (r *response) Header() http.Header {
     92 	return r.header
     93 }
     94 
     95 func (r *response) Write(data []byte) (int, error) {
     96 	if !r.wroteHeader {
     97 		r.WriteHeader(http.StatusOK)
     98 	}
     99 	return r.w.Write(data)
    100 }
    101 
    102 func (r *response) WriteHeader(code int) {
    103 	if r.wroteHeader {
    104 		return
    105 	}
    106 	r.wroteHeader = true
    107 	if code == http.StatusNotModified {
    108 		// Must not have body.
    109 		r.header.Del("Content-Type")
    110 		r.header.Del("Content-Length")
    111 		r.header.Del("Transfer-Encoding")
    112 	} else if r.header.Get("Content-Type") == "" {
    113 		r.header.Set("Content-Type", "text/html; charset=utf-8")
    114 	}
    115 
    116 	if r.header.Get("Date") == "" {
    117 		r.header.Set("Date", time.Now().UTC().Format(http.TimeFormat))
    118 	}
    119 
    120 	fmt.Fprintf(r.w, "Status: %d %s\r\n", code, http.StatusText(code))
    121 	r.header.Write(r.w)
    122 	r.w.WriteString("\r\n")
    123 }
    124 
    125 func (r *response) Flush() {
    126 	if !r.wroteHeader {
    127 		r.WriteHeader(http.StatusOK)
    128 	}
    129 	r.w.Flush()
    130 }
    131 
    132 func (r *response) Close() error {
    133 	r.Flush()
    134 	return r.w.Close()
    135 }
    136 
    137 type child struct {
    138 	conn    *conn
    139 	handler http.Handler
    140 
    141 	mu       sync.Mutex          // protects requests:
    142 	requests map[uint16]*request // keyed by request ID
    143 }
    144 
    145 func newChild(rwc io.ReadWriteCloser, handler http.Handler) *child {
    146 	return &child{
    147 		conn:     newConn(rwc),
    148 		handler:  handler,
    149 		requests: make(map[uint16]*request),
    150 	}
    151 }
    152 
    153 func (c *child) serve() {
    154 	defer c.conn.Close()
    155 	defer c.cleanUp()
    156 	var rec record
    157 	for {
    158 		if err := rec.read(c.conn.rwc); err != nil {
    159 			return
    160 		}
    161 		if err := c.handleRecord(&rec); err != nil {
    162 			return
    163 		}
    164 	}
    165 }
    166 
    167 var errCloseConn = errors.New("fcgi: connection should be closed")
    168 
    169 var emptyBody = ioutil.NopCloser(strings.NewReader(""))
    170 
    171 // ErrRequestAborted is returned by Read when a handler attempts to read the
    172 // body of a request that has been aborted by the web server.
    173 var ErrRequestAborted = errors.New("fcgi: request aborted by web server")
    174 
    175 // ErrConnClosed is returned by Read when a handler attempts to read the body of
    176 // a request after the connection to the web server has been closed.
    177 var ErrConnClosed = errors.New("fcgi: connection to web server closed")
    178 
    179 func (c *child) handleRecord(rec *record) error {
    180 	c.mu.Lock()
    181 	req, ok := c.requests[rec.h.Id]
    182 	c.mu.Unlock()
    183 	if !ok && rec.h.Type != typeBeginRequest && rec.h.Type != typeGetValues {
    184 		// The spec says to ignore unknown request IDs.
    185 		return nil
    186 	}
    187 
    188 	switch rec.h.Type {
    189 	case typeBeginRequest:
    190 		if req != nil {
    191 			// The server is trying to begin a request with the same ID
    192 			// as an in-progress request. This is an error.
    193 			return errors.New("fcgi: received ID that is already in-flight")
    194 		}
    195 
    196 		var br beginRequest
    197 		if err := br.read(rec.content()); err != nil {
    198 			return err
    199 		}
    200 		if br.role != roleResponder {
    201 			c.conn.writeEndRequest(rec.h.Id, 0, statusUnknownRole)
    202 			return nil
    203 		}
    204 		req = newRequest(rec.h.Id, br.flags)
    205 		c.mu.Lock()
    206 		c.requests[rec.h.Id] = req
    207 		c.mu.Unlock()
    208 		return nil
    209 	case typeParams:
    210 		// NOTE(eds): Technically a key-value pair can straddle the boundary
    211 		// between two packets. We buffer until we've received all parameters.
    212 		if len(rec.content()) > 0 {
    213 			req.rawParams = append(req.rawParams, rec.content()...)
    214 			return nil
    215 		}
    216 		req.parseParams()
    217 		return nil
    218 	case typeStdin:
    219 		content := rec.content()
    220 		if req.pw == nil {
    221 			var body io.ReadCloser
    222 			if len(content) > 0 {
    223 				// body could be an io.LimitReader, but it shouldn't matter
    224 				// as long as both sides are behaving.
    225 				body, req.pw = io.Pipe()
    226 			} else {
    227 				body = emptyBody
    228 			}
    229 			go c.serveRequest(req, body)
    230 		}
    231 		if len(content) > 0 {
    232 			// TODO(eds): This blocks until the handler reads from the pipe.
    233 			// If the handler takes a long time, it might be a problem.
    234 			req.pw.Write(content)
    235 		} else if req.pw != nil {
    236 			req.pw.Close()
    237 		}
    238 		return nil
    239 	case typeGetValues:
    240 		values := map[string]string{"FCGI_MPXS_CONNS": "1"}
    241 		c.conn.writePairs(typeGetValuesResult, 0, values)
    242 		return nil
    243 	case typeData:
    244 		// If the filter role is implemented, read the data stream here.
    245 		return nil
    246 	case typeAbortRequest:
    247 		c.mu.Lock()
    248 		delete(c.requests, rec.h.Id)
    249 		c.mu.Unlock()
    250 		c.conn.writeEndRequest(rec.h.Id, 0, statusRequestComplete)
    251 		if req.pw != nil {
    252 			req.pw.CloseWithError(ErrRequestAborted)
    253 		}
    254 		if !req.keepConn {
    255 			// connection will close upon return
    256 			return errCloseConn
    257 		}
    258 		return nil
    259 	default:
    260 		b := make([]byte, 8)
    261 		b[0] = byte(rec.h.Type)
    262 		c.conn.writeRecord(typeUnknownType, 0, b)
    263 		return nil
    264 	}
    265 }
    266 
    267 // filterOutUsedEnvVars returns a new map of env vars without the
    268 // variables in the given envVars map that are read for creating each http.Request
    269 func filterOutUsedEnvVars(envVars map[string]string) map[string]string {
    270 	withoutUsedEnvVars := make(map[string]string)
    271 	for k, v := range envVars {
    272 		if addFastCGIEnvToContext(k) {
    273 			withoutUsedEnvVars[k] = v
    274 		}
    275 	}
    276 	return withoutUsedEnvVars
    277 }
    278 
    279 func (c *child) serveRequest(req *request, body io.ReadCloser) {
    280 	r := newResponse(c, req)
    281 	httpReq, err := cgi.RequestFromMap(req.params)
    282 	if err != nil {
    283 		// there was an error reading the request
    284 		r.WriteHeader(http.StatusInternalServerError)
    285 		c.conn.writeRecord(typeStderr, req.reqId, []byte(err.Error()))
    286 	} else {
    287 		httpReq.Body = body
    288 		withoutUsedEnvVars := filterOutUsedEnvVars(req.params)
    289 		envVarCtx := context.WithValue(httpReq.Context(), envVarsContextKey{}, withoutUsedEnvVars)
    290 		httpReq = httpReq.WithContext(envVarCtx)
    291 		c.handler.ServeHTTP(r, httpReq)
    292 	}
    293 	r.Close()
    294 	c.mu.Lock()
    295 	delete(c.requests, req.reqId)
    296 	c.mu.Unlock()
    297 	c.conn.writeEndRequest(req.reqId, 0, statusRequestComplete)
    298 
    299 	// Consume the entire body, so the host isn't still writing to
    300 	// us when we close the socket below in the !keepConn case,
    301 	// otherwise we'd send a RST. (golang.org/issue/4183)
    302 	// TODO(bradfitz): also bound this copy in time. Or send
    303 	// some sort of abort request to the host, so the host
    304 	// can properly cut off the client sending all the data.
    305 	// For now just bound it a little and
    306 	io.CopyN(ioutil.Discard, body, 100<<20)
    307 	body.Close()
    308 
    309 	if !req.keepConn {
    310 		c.conn.Close()
    311 	}
    312 }
    313 
    314 func (c *child) cleanUp() {
    315 	c.mu.Lock()
    316 	defer c.mu.Unlock()
    317 	for _, req := range c.requests {
    318 		if req.pw != nil {
    319 			// race with call to Close in c.serveRequest doesn't matter because
    320 			// Pipe(Reader|Writer).Close are idempotent
    321 			req.pw.CloseWithError(ErrConnClosed)
    322 		}
    323 	}
    324 }
    325 
    326 // Serve accepts incoming FastCGI connections on the listener l, creating a new
    327 // goroutine for each. The goroutine reads requests and then calls handler
    328 // to reply to them.
    329 // If l is nil, Serve accepts connections from os.Stdin.
    330 // If handler is nil, http.DefaultServeMux is used.
    331 func Serve(l net.Listener, handler http.Handler) error {
    332 	if l == nil {
    333 		var err error
    334 		l, err = net.FileListener(os.Stdin)
    335 		if err != nil {
    336 			return err
    337 		}
    338 		defer l.Close()
    339 	}
    340 	if handler == nil {
    341 		handler = http.DefaultServeMux
    342 	}
    343 	for {
    344 		rw, err := l.Accept()
    345 		if err != nil {
    346 			return err
    347 		}
    348 		c := newChild(rw, handler)
    349 		go c.serve()
    350 	}
    351 }
    352 
    353 // ProcessEnv returns FastCGI environment variables associated with the request r
    354 // for which no effort was made to be included in the request itself - the data
    355 // is hidden in the request's context. As an example, if REMOTE_USER is set for a
    356 // request, it will not be found anywhere in r, but it will be included in
    357 // ProcessEnv's response (via r's context).
    358 func ProcessEnv(r *http.Request) map[string]string {
    359 	env, _ := r.Context().Value(envVarsContextKey{}).(map[string]string)
    360 	return env
    361 }
    362 
    363 // addFastCGIEnvToContext reports whether to include the FastCGI environment variable s
    364 // in the http.Request.Context, accessible via ProcessEnv.
    365 func addFastCGIEnvToContext(s string) bool {
    366 	// Exclude things supported by net/http natively:
    367 	switch s {
    368 	case "CONTENT_LENGTH", "CONTENT_TYPE", "HTTPS",
    369 		"PATH_INFO", "QUERY_STRING", "REMOTE_ADDR",
    370 		"REMOTE_HOST", "REMOTE_PORT", "REQUEST_METHOD",
    371 		"REQUEST_URI", "SCRIPT_NAME", "SERVER_PROTOCOL":
    372 		return false
    373 	}
    374 	if strings.HasPrefix(s, "HTTP_") {
    375 		return false
    376 	}
    377 	// Explicitly include FastCGI-specific things.
    378 	// This list is redundant with the default "return true" below.
    379 	// Consider this documentation of the sorts of things we expect
    380 	// to maybe see.
    381 	switch s {
    382 	case "REMOTE_USER":
    383 		return true
    384 	}
    385 	// Unknown, so include it to be safe.
    386 	return true
    387 }
    388