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:
parent
aafc444b74
commit
d7c1f67cb9
@ -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()
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user