162 lines
3.8 KiB
Go
162 lines
3.8 KiB
Go
package fastcgi
|
|
|
|
import (
|
|
"bufio"
|
|
"bytes"
|
|
"encoding/binary"
|
|
"io"
|
|
"net/http"
|
|
"net/http/httputil"
|
|
"net/textproto"
|
|
"strconv"
|
|
"strings"
|
|
"sync"
|
|
)
|
|
|
|
type FCGIClient struct {
|
|
mutex sync.Mutex
|
|
rwc io.ReadWriteCloser
|
|
h Header
|
|
buf bytes.Buffer
|
|
keepAlive bool
|
|
reqId uint16
|
|
}
|
|
|
|
// Close fcgi connnection
|
|
func (client *FCGIClient) Close() {
|
|
client.rwc.Close()
|
|
}
|
|
|
|
func (client *FCGIClient) writeRecord(recType FCGIRequestType, content []byte) (err error) {
|
|
client.mutex.Lock()
|
|
defer client.mutex.Unlock()
|
|
client.buf.Reset()
|
|
// Initialize the record
|
|
header := Header{}
|
|
header.init(recType, 1, len(content))
|
|
rec := Record{
|
|
Header: header,
|
|
Content: content,
|
|
}
|
|
|
|
// Write the record to the connection
|
|
b, err := rec.toBytes()
|
|
_, err = client.rwc.Write(b)
|
|
return err
|
|
}
|
|
|
|
func (client *FCGIClient) writeEndRequest(appStatus int, protocolStatus uint8) error {
|
|
b := make([]byte, 8)
|
|
binary.BigEndian.PutUint32(b, uint32(appStatus))
|
|
b[4] = protocolStatus
|
|
return client.writeRecord(FCGI_END_REQUEST, b)
|
|
}
|
|
|
|
func (client *FCGIClient) writePairs(recType FCGIRequestType, pairs map[string]string) error {
|
|
w := newWriter(client, recType)
|
|
b := make([]byte, 8)
|
|
nn := 0
|
|
for k, v := range pairs {
|
|
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
|
|
}
|
|
|
|
// 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) Do(req *FCGIRequest) (http.Response, error) {
|
|
beginRequestRecord := NewBeginRequestRecord()
|
|
err := client.writeRecord(beginRequestRecord.Header.Type, beginRequestRecord.Content)
|
|
if err != nil {
|
|
return http.Response{}, err
|
|
}
|
|
|
|
err = client.writePairs(FCGI_PARAMS, req.Context)
|
|
if err != nil {
|
|
return http.Response{}, err
|
|
}
|
|
|
|
// body := newWriter(client, FCGI_STDIN)
|
|
// if req != nil {
|
|
// io.Copy(body, req)
|
|
// }
|
|
// body.Close()
|
|
|
|
r := &streamReader{c: client}
|
|
rb := bufio.NewReader(r)
|
|
tp := textproto.NewReader(rb)
|
|
resp := new(http.Response)
|
|
// Parse the first line of the response.
|
|
line, err := tp.ReadLine()
|
|
if err != nil {
|
|
if err == io.EOF {
|
|
err = io.ErrUnexpectedEOF
|
|
}
|
|
return http.Response{}, err
|
|
}
|
|
if i := strings.IndexByte(line, ' '); i == -1 {
|
|
err = &badStringError{"malformed HTTP response", line}
|
|
} else {
|
|
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)
|
|
// TODO: fixTransferEncoding ?
|
|
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(rb))
|
|
} else {
|
|
resp.Body = io.NopCloser(rb)
|
|
}
|
|
|
|
return *resp, nil
|
|
}
|