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 }