Skip to content

Commit 457fd1d

Browse files
committed
net/http: support full-duplex HTTP/1 responses
Add support for concurrently reading from an HTTP/1 request body while writing the response. Normally, the HTTP/1 server automatically consumes any remaining request body before starting to write a response, to avoid deadlocking clients which attempt to write a complete request before reading the response. Add a ResponseController.EnableFullDuplex method which disables this behavior. For #15527 For #57786 Change-Id: Ie7ee8267d8333e9b32b82b9b84d4ad28ab8edf01 Reviewed-on: https://go-review.googlesource.com/c/go/+/472636 TryBot-Result: Gopher Robot <[email protected]> Run-TryBot: Damien Neil <[email protected]> Reviewed-by: Roland Shoemaker <[email protected]>
1 parent 73ca5c0 commit 457fd1d

File tree

4 files changed

+91
-8
lines changed

4 files changed

+91
-8
lines changed

api/next/57786.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
pkg net/http, method (*ResponseController) EnableFullDuplex() error #57786

src/net/http/responsecontroller.go

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ type ResponseController struct {
3131
// Hijack() (net.Conn, *bufio.ReadWriter, error)
3232
// SetReadDeadline(deadline time.Time) error
3333
// SetWriteDeadline(deadline time.Time) error
34+
// EnableFullDuplex() error
3435
//
3536
// If the ResponseWriter does not support a method, ResponseController returns
3637
// an error matching ErrNotSupported.
@@ -115,6 +116,30 @@ func (c *ResponseController) SetWriteDeadline(deadline time.Time) error {
115116
}
116117
}
117118

119+
// EnableFullDuplex indicates that the request handler will interleave reads from Request.Body
120+
// with writes to the ResponseWriter.
121+
//
122+
// For HTTP/1 requests, the Go HTTP server by default consumes any unread portion of
123+
// the request body before beginning to write the response, preventing handlers from
124+
// concurrently reading from the request and writing the response.
125+
// Calling EnableFullDuplex disables this behavior and permits handlers to continue to read
126+
// from the request while concurrently writing the response.
127+
//
128+
// For HTTP/2 requests, the Go HTTP server always permits concurrent reads and responses.
129+
func (c *ResponseController) EnableFullDuplex() error {
130+
rw := c.rw
131+
for {
132+
switch t := rw.(type) {
133+
case interface{ EnableFullDuplex() error }:
134+
return t.EnableFullDuplex()
135+
case rwUnwrapper:
136+
rw = t.Unwrap()
137+
default:
138+
return errNotSupported()
139+
}
140+
}
141+
}
142+
118143
// errNotSupported returns an error that Is ErrNotSupported,
119144
// but is not == to it.
120145
func errNotSupported() error {

src/net/http/responsecontroller_test.go

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -263,3 +263,51 @@ func testWrappedResponseController(t *testing.T, mode testMode) {
263263
io.Copy(io.Discard, res.Body)
264264
defer res.Body.Close()
265265
}
266+
267+
func TestResponseControllerEnableFullDuplex(t *testing.T) {
268+
run(t, testResponseControllerEnableFullDuplex)
269+
}
270+
func testResponseControllerEnableFullDuplex(t *testing.T, mode testMode) {
271+
cst := newClientServerTest(t, mode, HandlerFunc(func(w ResponseWriter, req *Request) {
272+
ctl := NewResponseController(w)
273+
if err := ctl.EnableFullDuplex(); err != nil {
274+
// TODO: Drop test for HTTP/2 when x/net is updated to support
275+
// EnableFullDuplex. Since HTTP/2 supports full duplex by default,
276+
// the rest of the test is fine; it's just the EnableFullDuplex call
277+
// that fails.
278+
if mode != http2Mode {
279+
t.Errorf("ctl.EnableFullDuplex() = %v, want nil", err)
280+
}
281+
}
282+
w.WriteHeader(200)
283+
ctl.Flush()
284+
for {
285+
var buf [1]byte
286+
n, err := req.Body.Read(buf[:])
287+
if n != 1 || err != nil {
288+
break
289+
}
290+
w.Write(buf[:])
291+
ctl.Flush()
292+
}
293+
}))
294+
pr, pw := io.Pipe()
295+
res, err := cst.c.Post(cst.ts.URL, "text/apocryphal", pr)
296+
if err != nil {
297+
t.Fatal(err)
298+
}
299+
defer res.Body.Close()
300+
for i := byte(0); i < 10; i++ {
301+
if _, err := pw.Write([]byte{i}); err != nil {
302+
t.Fatalf("Write: %v", err)
303+
}
304+
var buf [1]byte
305+
if n, err := res.Body.Read(buf[:]); n != 1 || err != nil {
306+
t.Fatalf("Read: %v, %v", n, err)
307+
}
308+
if buf[0] != i {
309+
t.Fatalf("read byte %v, want %v", buf[0], i)
310+
}
311+
}
312+
pw.Close()
313+
}

src/net/http/server.go

Lines changed: 17 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -460,6 +460,10 @@ type response struct {
460460
// Content-Length.
461461
closeAfterReply bool
462462

463+
// When fullDuplex is false (the default), we consume any remaining
464+
// request body before starting to write a response.
465+
fullDuplex bool
466+
463467
// requestBodyLimitHit is set by requestTooLarge when
464468
// maxBytesReader hits its max size. It is checked in
465469
// WriteHeader, to make sure we don't consume the
@@ -497,6 +501,11 @@ func (c *response) SetWriteDeadline(deadline time.Time) error {
497501
return c.conn.rwc.SetWriteDeadline(deadline)
498502
}
499503

504+
func (c *response) EnableFullDuplex() error {
505+
c.fullDuplex = true
506+
return nil
507+
}
508+
500509
// TrailerPrefix is a magic prefix for ResponseWriter.Header map keys
501510
// that, if present, signals that the map entry is actually for
502511
// the response trailers, and not the response headers. The prefix
@@ -1354,14 +1363,14 @@ func (cw *chunkWriter) writeHeader(p []byte) {
13541363
w.closeAfterReply = true
13551364
}
13561365

1357-
// Per RFC 2616, we should consume the request body before
1358-
// replying, if the handler hasn't already done so. But we
1359-
// don't want to do an unbounded amount of reading here for
1360-
// DoS reasons, so we only try up to a threshold.
1361-
// TODO(bradfitz): where does RFC 2616 say that? See Issue 15527
1362-
// about HTTP/1.x Handlers concurrently reading and writing, like
1363-
// HTTP/2 handlers can do. Maybe this code should be relaxed?
1364-
if w.req.ContentLength != 0 && !w.closeAfterReply {
1366+
// We do this by default because there are a number of clients that
1367+
// send a full request before starting to read the response, and they
1368+
// can deadlock if we start writing the response with unconsumed body
1369+
// remaining. See Issue 15527 for some history.
1370+
//
1371+
// If full duplex mode has been enabled with ResponseController.EnableFullDuplex,
1372+
// then leave the request body alone.
1373+
if w.req.ContentLength != 0 && !w.closeAfterReply && !w.fullDuplex {
13651374
var discard, tooBig bool
13661375

13671376
switch bdy := w.req.Body.(type) {

0 commit comments

Comments
 (0)