oasis/fastcgi/request.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
// }