175 lines
4.5 KiB
Go
175 lines
4.5 KiB
Go
package fastcgi
|
|
|
|
import (
|
|
"bytes"
|
|
"encoding/binary"
|
|
"io"
|
|
"log"
|
|
"net/http"
|
|
"net/url"
|
|
"strconv"
|
|
)
|
|
|
|
type FCGIRequest struct {
|
|
Id uint16
|
|
Context map[string]string
|
|
Body []byte
|
|
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 (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())
|
|
// }
|
|
|
|
// Spec: https://www.mit.edu/~yandros/doc/specs/fcgi-spec.html#S3
|
|
// Name value pairs such as: SCRIPT_PATH = /some/path
|
|
// Should be encoded as such:
|
|
// Name size
|
|
// Value size
|
|
// Name
|
|
// Value
|
|
// We'll encode the context correctly and return
|
|
// a slice of records to send.
|
|
func (req *FCGIRequest) EncodeContext() []Record {
|
|
records := []Record{}
|
|
for k, v := range req.Context {
|
|
// We'll use this to put together
|
|
// the body of the record
|
|
var buf bytes.Buffer
|
|
|
|
// Let's see how many bytes we have in total.
|
|
// Since we have to leave 8 bytes for encoding
|
|
// the sizes, we'll add it to the calculation.
|
|
// If the value is larger than what we can
|
|
// handle, we'll truncate it.
|
|
// log.Printf("Encoding %s(%d) = %s(%d)\n", k, len(k), v, len(v))
|
|
if (8 + len(k) + len(v)) > maxWrite {
|
|
valMaxLength := maxWrite - 8 - len(k)
|
|
v = v[:valMaxLength]
|
|
}
|
|
|
|
// The high bit of name size and value size is used for signaling
|
|
// how many bytes are used to store the length/size.
|
|
// If the size is > 127, we can just use one byte,
|
|
// and the high bit will be 0, otherwise, we use
|
|
// four bytes and the high bit will be 1
|
|
// So if length is encoded in 4 bytes it would look
|
|
// something like:
|
|
// 10000000000000000000010000100000
|
|
// For lengths < 127, we just use
|
|
// one byte with a high bit of 0
|
|
// 01001001
|
|
if len(k) > 127 {
|
|
size := uint32(len(k))
|
|
size |= 1 << 31 // Set the high bit to 1
|
|
b := make([]byte, 4)
|
|
binary.BigEndian.PutUint32(b, size)
|
|
buf.Write(b)
|
|
} else {
|
|
buf.Write([]byte{byte(len(k))})
|
|
}
|
|
|
|
if len(v) > 127 {
|
|
size := uint32(len(v))
|
|
size |= 1 << 31
|
|
b := make([]byte, 4)
|
|
binary.BigEndian.PutUint32(b, size)
|
|
buf.Write(b)
|
|
} else {
|
|
buf.Write([]byte{byte(len(v))})
|
|
}
|
|
|
|
// Now we just write our values to the buffer
|
|
buf.WriteString(k)
|
|
buf.WriteString(v)
|
|
|
|
records = append(records, *NewRecord(FCGI_PARAMS, buf.Bytes()))
|
|
buf.Reset()
|
|
}
|
|
|
|
log.Printf("We are sending %d FCGI_PARAMS records", len(records))
|
|
return records
|
|
}
|