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 }