232 lines
5.4 KiB
Go
232 lines
5.4 KiB
Go
package fastcgi
|
|
|
|
import (
|
|
"bufio"
|
|
"bytes"
|
|
"encoding/binary"
|
|
"io"
|
|
"log"
|
|
"net/http"
|
|
"net/http/httputil"
|
|
"net/textproto"
|
|
"os"
|
|
"strconv"
|
|
"strings"
|
|
"sync"
|
|
)
|
|
|
|
type FCGIClient struct {
|
|
mutex sync.Mutex
|
|
rwc io.ReadWriteCloser
|
|
h Header
|
|
buf bytes.Buffer
|
|
keepAlive bool
|
|
reqId uint16
|
|
}
|
|
|
|
// Do made the request and returns a io.Reader that translates the data read
|
|
// from fcgi responder out of fcgi packet before returning it.
|
|
func (client *FCGIClient) Forward(r *http.Request, root string, script string) (http.Response, error) {
|
|
req := RequestFromHttp(r)
|
|
req.Root(root)
|
|
req.Script(script)
|
|
log.Printf("______REQUEST %d______", req.Id)
|
|
|
|
log.Println("Begin request")
|
|
client.beginRequest(req)
|
|
|
|
// Write the request context as a stream
|
|
log.Println("Sending FCGI_PARAMS")
|
|
client.writeStream(req, req.EncodeContext())
|
|
log.Println("Done")
|
|
|
|
// Write the body (if any)
|
|
body := req.EncodeBody()
|
|
if len(body) > 0 {
|
|
log.Println("Sending body")
|
|
client.writeStream(req, body)
|
|
}
|
|
|
|
// Read the app response from the FCGI_STDOUT stream
|
|
log.Println("Reading response")
|
|
respContent := client.readResponse()
|
|
|
|
log.Printf("______END REQUEST %d______", req.Id)
|
|
|
|
f, _ := os.Create("./resp.txt")
|
|
defer f.Close()
|
|
f.Write(respContent)
|
|
|
|
return parseHttp(respContent)
|
|
}
|
|
|
|
// Close fcgi connnection
|
|
func (client *FCGIClient) Close() {
|
|
client.rwc.Close()
|
|
}
|
|
|
|
func (c *FCGIClient) beginRequest(req *FCGIRequest) error {
|
|
err := c.writeRecord(req.NewBeginRequestRecord())
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (client *FCGIClient) writeRecord(r *Record) (err error) {
|
|
client.mutex.Lock()
|
|
defer client.mutex.Unlock()
|
|
client.buf.Reset()
|
|
|
|
// Write the record to the connection
|
|
b, err := r.toBytes()
|
|
_, err = client.rwc.Write(b)
|
|
return err
|
|
}
|
|
|
|
// We write long data such as FCGI_PARAMS or FCGI_STDIN
|
|
// as a stream. Sending an empty record of the same
|
|
// type to signal the end.
|
|
func (c *FCGIClient) writeStream(req *FCGIRequest, records []Record) error {
|
|
if len(records) == 0 {
|
|
return nil
|
|
}
|
|
|
|
for _, r := range records {
|
|
c.writeRecord(&r)
|
|
}
|
|
|
|
// Send an empty record to end the stream.
|
|
// all the records should be of the same
|
|
// type so we'll just use the type from
|
|
// the first item in the slice.
|
|
end := req.NewRecord(records[0].Header.Type, nil)
|
|
c.writeRecord(end)
|
|
return nil
|
|
}
|
|
|
|
func readRecord(r io.Reader) (*Record, error) {
|
|
// It's easier to read the header piece of the record
|
|
// into the struct as opposed to doing it piece by
|
|
// piece. But we'll do it this way to be explicit.
|
|
// Just know that you could also do:
|
|
// h := Header{}
|
|
// binary.Read(r, binary.BigEndian, &h)
|
|
var version uint8
|
|
var recType FCGIRecordType
|
|
var id uint16
|
|
var contentlength uint16
|
|
var paddinglength uint8
|
|
var reserved uint8
|
|
|
|
// Let's read the header fields of the record.
|
|
binary.Read(r, binary.BigEndian, &version)
|
|
binary.Read(r, binary.BigEndian, &recType)
|
|
binary.Read(r, binary.BigEndian, &id)
|
|
binary.Read(r, binary.BigEndian, &contentlength)
|
|
binary.Read(r, binary.BigEndian, &paddinglength)
|
|
binary.Read(r, binary.BigEndian, &reserved)
|
|
|
|
readLength := int(contentlength) + int(paddinglength)
|
|
content := make([]byte, readLength)
|
|
|
|
if _, err := io.ReadFull(r, content); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
// Remove any padding from the content
|
|
content = content[:contentlength]
|
|
|
|
rec := Record{}
|
|
rec.Header.Version = version
|
|
rec.Header.Type = recType
|
|
rec.Header.Id = id
|
|
rec.Header.ContentLength = contentlength
|
|
rec.Header.PaddingLength = paddinglength
|
|
rec.Header.Reserved = reserved
|
|
rec.Content = content
|
|
|
|
return &rec, nil
|
|
}
|
|
|
|
func (c *FCGIClient) readResponse() []byte {
|
|
var response bytes.Buffer
|
|
|
|
for {
|
|
r, err := readRecord(c.rwc)
|
|
|
|
if err != nil {
|
|
log.Printf("Encountered error when reading the stream: %s", err.Error())
|
|
}
|
|
|
|
if r.Header.Type == FCGI_END_REQUEST {
|
|
break
|
|
}
|
|
|
|
response.Write(r.Content)
|
|
}
|
|
|
|
return response.Bytes()
|
|
}
|
|
|
|
func parseHttp(raw []byte) (http.Response, error) {
|
|
log.Println("Parsing http")
|
|
bf := bufio.NewReader(bytes.NewReader(raw))
|
|
tp := textproto.NewReader(bf)
|
|
resp := new(http.Response)
|
|
// Ensure we have a valid http response
|
|
line, err := tp.ReadLine()
|
|
if err != nil {
|
|
if err == io.EOF {
|
|
err = io.ErrUnexpectedEOF
|
|
}
|
|
return http.Response{}, err
|
|
}
|
|
|
|
i := strings.IndexByte(line, ' ')
|
|
|
|
if i == -1 {
|
|
return http.Response{}, &badStringError{"malformed HTTP response", line}
|
|
}
|
|
|
|
resp.Proto = line[:i]
|
|
resp.Status = strings.TrimLeft(line[i+1:], " ")
|
|
statusCode := resp.Status
|
|
if i := strings.IndexByte(resp.Status, ' '); i != -1 {
|
|
statusCode = resp.Status[:i]
|
|
}
|
|
if len(statusCode) != 3 {
|
|
err = &badStringError{"malformed HTTP status code", statusCode}
|
|
}
|
|
resp.StatusCode, err = strconv.Atoi(statusCode)
|
|
if err != nil || resp.StatusCode < 0 {
|
|
err = &badStringError{"malformed HTTP status code", statusCode}
|
|
}
|
|
var ok bool
|
|
if resp.ProtoMajor, resp.ProtoMinor, ok = http.ParseHTTPVersion(resp.Proto); !ok {
|
|
err = &badStringError{"malformed HTTP version", resp.Proto}
|
|
}
|
|
// Parse the response headers.
|
|
mimeHeader, err := tp.ReadMIMEHeader()
|
|
if err != nil {
|
|
if err == io.EOF {
|
|
err = io.ErrUnexpectedEOF
|
|
}
|
|
return http.Response{}, err
|
|
}
|
|
resp.Header = http.Header(mimeHeader)
|
|
resp.TransferEncoding = resp.Header["Transfer-Encoding"]
|
|
resp.ContentLength, _ = strconv.ParseInt(resp.Header.Get("Content-Length"), 10, 64)
|
|
|
|
if chunked(resp.TransferEncoding) {
|
|
resp.Body = io.NopCloser(httputil.NewChunkedReader(bf))
|
|
} else {
|
|
resp.Body = io.NopCloser(bf)
|
|
}
|
|
|
|
log.Println("Done")
|
|
|
|
return *resp, nil
|
|
}
|