"net/http/cgi"
"os"
"strings"
- "sync"
"time"
)
conn *conn
handler http.Handler
- mu sync.Mutex // protects requests:
requests map[uint16]*request // keyed by request ID
}
var ErrConnClosed = errors.New("fcgi: connection to web server closed")
func (c *child) handleRecord(rec *record) error {
- c.mu.Lock()
req, ok := c.requests[rec.h.Id]
- c.mu.Unlock()
if !ok && rec.h.Type != typeBeginRequest && rec.h.Type != typeGetValues {
// The spec says to ignore unknown request IDs.
return nil
return nil
}
req = newRequest(rec.h.Id, br.flags)
- c.mu.Lock()
c.requests[rec.h.Id] = req
- c.mu.Unlock()
return nil
case typeParams:
// NOTE(eds): Technically a key-value pair can straddle the boundary
// TODO(eds): This blocks until the handler reads from the pipe.
// If the handler takes a long time, it might be a problem.
req.pw.Write(content)
- } else if req.pw != nil {
- req.pw.Close()
+ } else {
+ delete(c.requests, req.reqId)
+ if req.pw != nil {
+ req.pw.Close()
+ }
}
return nil
case typeGetValues:
// If the filter role is implemented, read the data stream here.
return nil
case typeAbortRequest:
- c.mu.Lock()
delete(c.requests, rec.h.Id)
- c.mu.Unlock()
c.conn.writeEndRequest(rec.h.Id, 0, statusRequestComplete)
if req.pw != nil {
req.pw.CloseWithError(ErrRequestAborted)
// Make sure we serve something even if nothing was written to r
r.Write(nil)
r.Close()
- c.mu.Lock()
- delete(c.requests, req.reqId)
- c.mu.Unlock()
c.conn.writeEndRequest(req.reqId, 0, statusRequestComplete)
// Consume the entire body, so the host isn't still writing to
}
func (c *child) cleanUp() {
- c.mu.Lock()
- defer c.mu.Unlock()
for _, req := range c.requests {
if req.pw != nil {
// race with call to Close in c.serveRequest doesn't matter because
"net/http"
"strings"
"testing"
+ "time"
)
var sizeTests = []struct {
})
}
}
+
+type signallingNopCloser struct {
+ io.Reader
+ closed chan bool
+}
+
+func (*signallingNopCloser) Write(buf []byte) (int, error) {
+ return len(buf), nil
+}
+
+func (rc *signallingNopCloser) Close() error {
+ close(rc.closed)
+ return nil
+}
+
+// Test whether server properly closes connection when processing slow
+// requests
+func TestSlowRequest(t *testing.T) {
+ pr, pw := io.Pipe()
+ go func(w io.Writer) {
+ for _, buf := range [][]byte{
+ streamBeginTypeStdin,
+ makeRecord(typeStdin, 1, nil),
+ } {
+ pw.Write(buf)
+ time.Sleep(100 * time.Millisecond)
+ }
+ }(pw)
+
+ rc := &signallingNopCloser{pr, make(chan bool)}
+ handlerDone := make(chan bool)
+
+ c := newChild(rc, http.HandlerFunc(func(
+ w http.ResponseWriter,
+ r *http.Request,
+ ) {
+ w.WriteHeader(200)
+ close(handlerDone)
+ }))
+ go c.serve()
+ defer c.cleanUp()
+
+ timeout := time.After(2 * time.Second)
+
+ <-handlerDone
+ select {
+ case <-rc.closed:
+ t.Log("FastCGI child closed connection")
+ case <-timeout:
+ t.Error("FastCGI child did not close socket after handling request")
+ }
+}