245 lines
6.0 KiB
Go
245 lines
6.0 KiB
Go
package fastcgi
|
|
|
|
import (
|
|
"bytes"
|
|
"encoding/binary"
|
|
"errors"
|
|
"io"
|
|
"net/http"
|
|
"net/url"
|
|
"strconv"
|
|
)
|
|
|
|
// -- TYPES --
|
|
// These are the types needed to house the data for an FCGI request
|
|
type Header struct {
|
|
Version uint8
|
|
Type FCGIRequestType
|
|
Id uint16
|
|
ContentLength uint16
|
|
PaddingLength uint8
|
|
Reserved uint8
|
|
}
|
|
|
|
func (h *Header) init(reqType FCGIRequestType, reqId uint16, l int) {
|
|
h.Version = 1
|
|
h.Type = reqType
|
|
h.Id = reqId
|
|
h.ContentLength = uint16(l)
|
|
h.PaddingLength = uint8(-l & 7)
|
|
}
|
|
|
|
// A record is essentially a "packet" in FastCGI.
|
|
// The header lets the server know what type
|
|
// of data is being sent, and it expects
|
|
// a certain structure depending on
|
|
// the type. For example, the
|
|
// FCGI_BEGIN_REQUEST record should
|
|
// have a body of 8 bytes with:
|
|
// - The first byte being the role
|
|
// - The second byte being also the role
|
|
// - The third byte being the flags
|
|
// - The last five bytes are reserved for future use
|
|
type Record struct {
|
|
Header Header
|
|
Content []byte
|
|
Buffer bytes.Buffer // Buffer to use when writing to the network
|
|
ReadBuffer []byte // Buffer to use when reading a response
|
|
}
|
|
|
|
// Turn a record into a byte array so it can be
|
|
// sent over the network socket
|
|
func (r *Record) toBytes() ([]byte, error) {
|
|
// client.h.init(recType, client.reqId, len(content))
|
|
if err := binary.Write(&r.Buffer, binary.BigEndian, r.Header); err != nil {
|
|
return nil, err
|
|
}
|
|
if _, err := r.Buffer.Write(r.Content); err != nil {
|
|
return nil, err
|
|
}
|
|
if _, err := r.Buffer.Write(pad[:r.Header.PaddingLength]); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return r.Buffer.Bytes(), nil
|
|
}
|
|
|
|
func (rec *Record) read(r io.Reader) (buf []byte, err error) {
|
|
if err = binary.Read(r, binary.BigEndian, &rec.Header); err != nil {
|
|
return
|
|
}
|
|
if rec.Header.Version != 1 {
|
|
err = errors.New("fcgi: invalid header version")
|
|
return
|
|
}
|
|
if rec.Header.Type == FCGI_END_REQUEST {
|
|
err = io.EOF
|
|
return
|
|
}
|
|
n := int(rec.Header.ContentLength) + int(rec.Header.PaddingLength)
|
|
if len(rec.ReadBuffer) < n {
|
|
rec.ReadBuffer = make([]byte, n)
|
|
}
|
|
if n, err = io.ReadFull(r, rec.ReadBuffer[:n]); err != nil {
|
|
return
|
|
}
|
|
buf = rec.ReadBuffer[:int(rec.Header.ContentLength)]
|
|
|
|
return
|
|
}
|
|
|
|
// 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
|
|
|
|
type FCGIRequest struct {
|
|
Id uint16
|
|
Type FCGIRequestType
|
|
Context map[string]string
|
|
Records []Record
|
|
}
|
|
|
|
func RequestFromHttp(r *http.Request) *FCGIRequest {
|
|
c := FCGIRequest{}
|
|
c.Context = make(map[string]string)
|
|
c.Context["SERVER_SOFTWARE"] = "oasis / fastcgi"
|
|
c.Context["QUERY_STRING"] = r.URL.RawQuery
|
|
c.Context["REMOTE_ADDR"] = "127.0.0.1"
|
|
|
|
return &c
|
|
}
|
|
|
|
func NewBeginRequestRecord() *Record {
|
|
role := uint16(FCGI_RESPONDER)
|
|
flags := byte(0)
|
|
// Create an 8-byte array as per the FastCGI specification.
|
|
var b [8]byte
|
|
|
|
// Split the 16-bit role into two bytes and assign them.
|
|
b[0] = byte(role >> 8) // High byte
|
|
b[1] = byte(role) // Low byte
|
|
|
|
// Set the flags.
|
|
b[2] = flags
|
|
|
|
// The reserved bytes (b[3] to b[7]) will remain zero by default.
|
|
|
|
// Return a begin request record
|
|
h := Header{}
|
|
h.init(FCGI_BEGIN_REQUEST, 1, len(b))
|
|
return &Record{
|
|
Header: h,
|
|
Content: b[:],
|
|
}
|
|
}
|
|
|
|
func (req *FCGIRequest) Script(path string) {
|
|
req.Context["SCRIPT_FILENAME"] = path
|
|
}
|
|
|
|
func (req *FCGIRequest) Method(m string) {
|
|
req.Context["REQUEST_METHOD"] = m
|
|
}
|
|
|
|
// Get issues a GET request to the fcgi responder.
|
|
func (r *FCGIRequest) TypeGet() {
|
|
|
|
r.Context["REQUEST_METHOD"] = "GET"
|
|
r.Context["CONTENT_LENGTH"] = "0"
|
|
}
|
|
|
|
// Get issues a Post request to the fcgi responder. with request body
|
|
// in the format that bodyType specified
|
|
func (r *FCGIRequest) TypePost(bodyType string, body io.Reader, l int) {
|
|
|
|
if len(r.Context["REQUEST_METHOD"]) == 0 || r.Context["REQUEST_METHOD"] == "GET" {
|
|
r.Context["REQUEST_METHOD"] = "POST"
|
|
}
|
|
r.Context["CONTENT_LENGTH"] = strconv.Itoa(l)
|
|
if len(bodyType) > 0 {
|
|
r.Context["CONTENT_TYPE"] = bodyType
|
|
} else {
|
|
r.Context["CONTENT_TYPE"] = "application/x-www-form-urlencoded"
|
|
}
|
|
}
|
|
|
|
// PostForm issues a POST to the fcgi responder, with form
|
|
// as a string key to a list values (url.Values)
|
|
func (r *FCGIRequest) TypePostForm(data url.Values) {
|
|
body := bytes.NewReader([]byte(data.Encode()))
|
|
r.TypePost("application/x-www-form-urlencoded", body, body.Len())
|
|
}
|
|
|
|
// PostFile issues a POST to the fcgi responder in multipart(RFC 2046) standard,
|
|
// with form as a string key to a list values (url.Values),
|
|
// and/or with file as a string key to a list file path.
|
|
// func (r *FCGIRequest) PostFile(p map[string]string, data url.Values, file map[string]string) {
|
|
// buf := &bytes.Buffer{}
|
|
// writer := multipart.NewWriter(buf)
|
|
// bodyType := writer.FormDataContentType()
|
|
//
|
|
// for key, val := range data {
|
|
// for _, v0 := range val {
|
|
// err = writer.WriteField(key, v0)
|
|
// if err != nil {
|
|
// return
|
|
// }
|
|
// }
|
|
// }
|
|
//
|
|
// for key, val := range file {
|
|
// fd, e := os.Open(val)
|
|
// if e != nil {
|
|
// return nil, e
|
|
// }
|
|
// defer fd.Close()
|
|
//
|
|
// part, e := writer.CreateFormFile(key, filepath.Base(val))
|
|
// if e != nil {
|
|
// return nil, e
|
|
// }
|
|
// _, err = io.Copy(part, fd)
|
|
// }
|
|
//
|
|
// err = writer.Close()
|
|
// if err != nil {
|
|
// return
|
|
// }
|
|
//
|
|
// // return client.Post(p, bodyType, buf, buf.Len())
|
|
// }
|
|
|
|
// Format the context for sending
|
|
// func (r *FCGIRequest) writePairs() error {
|
|
// w := newWriter(client, recType)
|
|
// b := make([]byte, 8)
|
|
// nn := 0
|
|
// for k, v := range r.Context {
|
|
// m := 8 + len(k) + len(v)
|
|
// if m > maxWrite {
|
|
// // param data size exceed 65535 bytes"
|
|
// vl := maxWrite - 8 - len(k)
|
|
// v = v[:vl]
|
|
// }
|
|
// n := encodeSize(b, uint32(len(k)))
|
|
// n += encodeSize(b[n:], uint32(len(v)))
|
|
// m = n + len(k) + len(v)
|
|
// if (nn + m) > maxWrite {
|
|
// w.Flush()
|
|
// nn = 0
|
|
// }
|
|
// nn += m
|
|
// 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
|
|
// }
|