332 lines
		
	
	
		
			7.8 KiB
		
	
	
	
		
			Go
		
	
	
	
			
		
		
	
	
			332 lines
		
	
	
		
			7.8 KiB
		
	
	
	
		
			Go
		
	
	
	
| // 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 phpfpm
 | |
| 
 | |
| // This file implements FastCGI from the perspective of a child process.
 | |
| 
 | |
| import (
 | |
| 	"errors"
 | |
| 	"fmt"
 | |
| 	"io"
 | |
| 	"io/ioutil"
 | |
| 	"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
 | |
| }
 | |
| 
 | |
| 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
 | |
| 	w           *bufWriter
 | |
| 	wroteHeader bool
 | |
| }
 | |
| 
 | |
| 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(data []byte) (int, error) {
 | |
| 	if !r.wroteHeader {
 | |
| 		r.WriteHeader(http.StatusOK)
 | |
| 	}
 | |
| 	return r.w.Write(data)
 | |
| }
 | |
| 
 | |
| func (r *response) WriteHeader(code int) {
 | |
| 	if r.wroteHeader {
 | |
| 		return
 | |
| 	}
 | |
| 	r.wroteHeader = true
 | |
| 	if code == http.StatusNotModified {
 | |
| 		// Must not have body.
 | |
| 		r.header.Del("Content-Type")
 | |
| 		r.header.Del("Content-Length")
 | |
| 		r.header.Del("Transfer-Encoding")
 | |
| 	} else if r.header.Get("Content-Type") == "" {
 | |
| 		r.header.Set("Content-Type", "text/html; charset=utf-8")
 | |
| 	}
 | |
| 
 | |
| 	if r.header.Get("Date") == "" {
 | |
| 		r.header.Set("Date", time.Now().UTC().Format(http.TimeFormat))
 | |
| 	}
 | |
| 
 | |
| 	fmt.Fprintf(r.w, "Status: %d %s\r\n", code, http.StatusText(code))
 | |
| 	r.header.Write(r.w)
 | |
| 	r.w.WriteString("\r\n")
 | |
| }
 | |
| 
 | |
| 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 = ioutil.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
 | |
| 	}
 | |
| }
 | |
| 
 | |
| 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
 | |
| 		c.handler.ServeHTTP(r, httpReq)
 | |
| 	}
 | |
| 	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(ioutil.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()
 | |
| 	}
 | |
| }
 |