oasis/fastcgi/request.go
2024-12-28 12:35:22 -05:00

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
}