1
0
mirror of https://github.com/golang/go synced 2024-11-23 05:30:07 -07:00

net/http/fcgi: fix a shutdown race

If a handler didn't consume all its Request.Body, child.go was
closing the socket while the host was still writing to it,
causing the child to send a RST and the host (at least nginx)
to send an empty response body.

Now, we tell the host we're done with the request/response
first, and then close our input pipe after consuming a bit of
it. Consuming the body fixes the problem, and flushing to the
host first to tell it that we're done increases the chance
that the host cuts off further data to us, meaning we won't
have much to consume.

No new tests, because this package is lacking in tests.
Tested by hand with nginx.  See issue for testing details.

Fixes #4183

R=golang-dev, rsc
CC=golang-dev
https://golang.org/cl/7939045
This commit is contained in:
Brad Fitzpatrick 2013-03-21 14:07:24 -07:00
parent aafc444b74
commit d7c1f67cb9

View File

@ -162,14 +162,15 @@ func (c *child) handleRecord(rec *record) error {
// The spec says to ignore unknown request IDs. // The spec says to ignore unknown request IDs.
return nil return nil
} }
if ok && rec.h.Type == typeBeginRequest {
// The server is trying to begin a request with the same ID
// as an in-progress request. This is an error.
return errors.New("fcgi: received ID that is already in-flight")
}
switch rec.h.Type { switch rec.h.Type {
case typeBeginRequest: case typeBeginRequest:
if req != nil {
// The server is trying to begin a request with the same ID
// as an in-progress request. This is an error.
return errors.New("fcgi: received ID that is already in-flight")
}
var br beginRequest var br beginRequest
if err := br.read(rec.content()); err != nil { if err := br.read(rec.content()); err != nil {
return err return err
@ -179,6 +180,7 @@ func (c *child) handleRecord(rec *record) error {
return nil return nil
} }
c.requests[rec.h.Id] = newRequest(rec.h.Id, br.flags) c.requests[rec.h.Id] = newRequest(rec.h.Id, br.flags)
return nil
case typeParams: case typeParams:
// NOTE(eds): Technically a key-value pair can straddle the boundary // NOTE(eds): Technically a key-value pair can straddle the boundary
// between two packets. We buffer until we've received all parameters. // between two packets. We buffer until we've received all parameters.
@ -187,6 +189,7 @@ func (c *child) handleRecord(rec *record) error {
return nil return nil
} }
req.parseParams() req.parseParams()
return nil
case typeStdin: case typeStdin:
content := rec.content() content := rec.content()
if req.pw == nil { if req.pw == nil {
@ -207,24 +210,29 @@ func (c *child) handleRecord(rec *record) error {
} else if req.pw != nil { } else if req.pw != nil {
req.pw.Close() req.pw.Close()
} }
return nil
case typeGetValues: case typeGetValues:
values := map[string]string{"FCGI_MPXS_CONNS": "1"} values := map[string]string{"FCGI_MPXS_CONNS": "1"}
c.conn.writePairs(typeGetValuesResult, 0, values) c.conn.writePairs(typeGetValuesResult, 0, values)
return nil
case typeData: case typeData:
// If the filter role is implemented, read the data stream here. // If the filter role is implemented, read the data stream here.
return nil
case typeAbortRequest: case typeAbortRequest:
println("abort")
delete(c.requests, rec.h.Id) delete(c.requests, rec.h.Id)
c.conn.writeEndRequest(rec.h.Id, 0, statusRequestComplete) c.conn.writeEndRequest(rec.h.Id, 0, statusRequestComplete)
if !req.keepConn { if !req.keepConn {
// connection will close upon return // connection will close upon return
return errCloseConn return errCloseConn
} }
return nil
default: default:
b := make([]byte, 8) b := make([]byte, 8)
b[0] = byte(rec.h.Type) b[0] = byte(rec.h.Type)
c.conn.writeRecord(typeUnknownType, 0, b) c.conn.writeRecord(typeUnknownType, 0, b)
return nil
} }
return nil
} }
func (c *child) serveRequest(req *request, body io.ReadCloser) { func (c *child) serveRequest(req *request, body io.ReadCloser) {
@ -238,9 +246,19 @@ func (c *child) serveRequest(req *request, body io.ReadCloser) {
httpReq.Body = body httpReq.Body = body
c.handler.ServeHTTP(r, httpReq) c.handler.ServeHTTP(r, httpReq)
} }
body.Close()
r.Close() r.Close()
c.conn.writeEndRequest(req.reqId, 0, statusRequestComplete) c.conn.writeEndRequest(req.reqId, 0, statusRequestComplete)
// Consume the entire body, so the host isn't still writing to
// us when we close the socket below in the !keepConn case,
// otherwise we'd send a RST. (golang.org/issue/4183)
// TODO(bradfitz): also bound this copy in time. Or send
// some sort of abort request to the host, so the host
// can properly cut off the client sending all the data.
// For now just bound it a little and
io.CopyN(ioutil.Discard, body, 100<<20)
body.Close()
if !req.keepConn { if !req.keepConn {
c.conn.Close() c.conn.Close()
} }