...
Run Format

Source file src/net/http/fcgi/fcgi.go

Documentation: net/http/fcgi

  // 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 implements the FastCGI protocol.
  //
  // The protocol is not an official standard and the original
  // documentation is no longer online. See the Internet Archive's
  // mirror at: https://web.archive.org/web/20150420080736/http://www.fastcgi.com/drupal/node/6?q=node/22
  //
  // Currently only the responder role is supported.
  package fcgi
  
  // This file defines the raw protocol and some utilities used by the child and
  // the host.
  
  import (
  	"bufio"
  	"bytes"
  	"encoding/binary"
  	"errors"
  	"io"
  	"sync"
  )
  
  // recType is a record type, as defined by
  // https://web.archive.org/web/20150420080736/http://www.fastcgi.com/drupal/node/6?q=node/22#S8
  type recType uint8
  
  const (
  	typeBeginRequest    recType = 1
  	typeAbortRequest    recType = 2
  	typeEndRequest      recType = 3
  	typeParams          recType = 4
  	typeStdin           recType = 5
  	typeStdout          recType = 6
  	typeStderr          recType = 7
  	typeData            recType = 8
  	typeGetValues       recType = 9
  	typeGetValuesResult recType = 10
  	typeUnknownType     recType = 11
  )
  
  // keep the connection between web-server and responder open after request
  const flagKeepConn = 1
  
  const (
  	maxWrite = 65535 // maximum record body
  	maxPad   = 255
  )
  
  const (
  	roleResponder = iota + 1 // only Responders are implemented.
  	roleAuthorizer
  	roleFilter
  )
  
  const (
  	statusRequestComplete = iota
  	statusCantMultiplex
  	statusOverloaded
  	statusUnknownRole
  )
  
  type header struct {
  	Version       uint8
  	Type          recType
  	Id            uint16
  	ContentLength uint16
  	PaddingLength uint8
  	Reserved      uint8
  }
  
  type beginRequest struct {
  	role     uint16
  	flags    uint8
  	reserved [5]uint8
  }
  
  func (br *beginRequest) read(content []byte) error {
  	if len(content) != 8 {
  		return errors.New("fcgi: invalid begin request record")
  	}
  	br.role = binary.BigEndian.Uint16(content)
  	br.flags = content[2]
  	return nil
  }
  
  // for padding so we don't have to allocate all the time
  // not synchronized because we don't care what the contents are
  var pad [maxPad]byte
  
  func (h *header) init(recType recType, reqId uint16, contentLength int) {
  	h.Version = 1
  	h.Type = recType
  	h.Id = reqId
  	h.ContentLength = uint16(contentLength)
  	h.PaddingLength = uint8(-contentLength & 7)
  }
  
  // conn sends records over rwc
  type conn struct {
  	mutex sync.Mutex
  	rwc   io.ReadWriteCloser
  
  	// to avoid allocations
  	buf bytes.Buffer
  	h   header
  }
  
  func newConn(rwc io.ReadWriteCloser) *conn {
  	return &conn{rwc: rwc}
  }
  
  func (c *conn) Close() error {
  	c.mutex.Lock()
  	defer c.mutex.Unlock()
  	return c.rwc.Close()
  }
  
  type record struct {
  	h   header
  	buf [maxWrite + maxPad]byte
  }
  
  func (rec *record) read(r io.Reader) (err error) {
  	if err = binary.Read(r, binary.BigEndian, &rec.h); err != nil {
  		return err
  	}
  	if rec.h.Version != 1 {
  		return errors.New("fcgi: invalid header version")
  	}
  	n := int(rec.h.ContentLength) + int(rec.h.PaddingLength)
  	if _, err = io.ReadFull(r, rec.buf[:n]); err != nil {
  		return err
  	}
  	return nil
  }
  
  func (r *record) content() []byte {
  	return r.buf[:r.h.ContentLength]
  }
  
  // writeRecord writes and sends a single record.
  func (c *conn) writeRecord(recType recType, reqId uint16, b []byte) error {
  	c.mutex.Lock()
  	defer c.mutex.Unlock()
  	c.buf.Reset()
  	c.h.init(recType, reqId, len(b))
  	if err := binary.Write(&c.buf, binary.BigEndian, c.h); err != nil {
  		return err
  	}
  	if _, err := c.buf.Write(b); err != nil {
  		return err
  	}
  	if _, err := c.buf.Write(pad[:c.h.PaddingLength]); err != nil {
  		return err
  	}
  	_, err := c.rwc.Write(c.buf.Bytes())
  	return err
  }
  
  func (c *conn) writeEndRequest(reqId uint16, appStatus int, protocolStatus uint8) error {
  	b := make([]byte, 8)
  	binary.BigEndian.PutUint32(b, uint32(appStatus))
  	b[4] = protocolStatus
  	return c.writeRecord(typeEndRequest, reqId, b)
  }
  
  func (c *conn) writePairs(recType recType, reqId uint16, pairs map[string]string) error {
  	w := newWriter(c, recType, reqId)
  	b := make([]byte, 8)
  	for k, v := range pairs {
  		n := encodeSize(b, uint32(len(k)))
  		n += encodeSize(b[n:], uint32(len(v)))
  		if _, err := w.Write(b[:n]); err != nil {
  			return err
  		}
  		if _, err := w.WriteString(k); err != nil {
  			return err
  		}
  		if _, err := w.WriteString(v); err != nil {
  			return err
  		}
  	}
  	w.Close()
  	return nil
  }
  
  func readSize(s []byte) (uint32, int) {
  	if len(s) == 0 {
  		return 0, 0
  	}
  	size, n := uint32(s[0]), 1
  	if size&(1<<7) != 0 {
  		if len(s) < 4 {
  			return 0, 0
  		}
  		n = 4
  		size = binary.BigEndian.Uint32(s)
  		size &^= 1 << 31
  	}
  	return size, n
  }
  
  func readString(s []byte, size uint32) string {
  	if size > uint32(len(s)) {
  		return ""
  	}
  	return string(s[:size])
  }
  
  func encodeSize(b []byte, size uint32) int {
  	if size > 127 {
  		size |= 1 << 31
  		binary.BigEndian.PutUint32(b, size)
  		return 4
  	}
  	b[0] = byte(size)
  	return 1
  }
  
  // bufWriter encapsulates bufio.Writer but also closes the underlying stream when
  // Closed.
  type bufWriter struct {
  	closer io.Closer
  	*bufio.Writer
  }
  
  func (w *bufWriter) Close() error {
  	if err := w.Writer.Flush(); err != nil {
  		w.closer.Close()
  		return err
  	}
  	return w.closer.Close()
  }
  
  func newWriter(c *conn, recType recType, reqId uint16) *bufWriter {
  	s := &streamWriter{c: c, recType: recType, reqId: reqId}
  	w := bufio.NewWriterSize(s, maxWrite)
  	return &bufWriter{s, w}
  }
  
  // streamWriter abstracts out the separation of a stream into discrete records.
  // It only writes maxWrite bytes at a time.
  type streamWriter struct {
  	c       *conn
  	recType recType
  	reqId   uint16
  }
  
  func (w *streamWriter) Write(p []byte) (int, error) {
  	nn := 0
  	for len(p) > 0 {
  		n := len(p)
  		if n > maxWrite {
  			n = maxWrite
  		}
  		if err := w.c.writeRecord(w.recType, w.reqId, p[:n]); err != nil {
  			return nn, err
  		}
  		nn += n
  		p = p[n:]
  	}
  	return nn, nil
  }
  
  func (w *streamWriter) Close() error {
  	// send empty record to close the stream
  	return w.c.writeRecord(w.recType, w.reqId, nil)
  }
  

View as plain text