oasis/fastcgi/client.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
}