Skip to content

Commit 4b0bc7c

Browse files
committed
net/http: relax recently-updated rules and behavior of CloseNotifier
The CloseNotifier implementation and documentation was substantially changed in https://golang.org/cl/17750 but it was a bit too aggressive. Issue #13666 highlighted that in addition to breaking external projects, even the standard library (httputil.ReverseProxy) didn't obey the new rules about not using CloseNotifier until the Request.Body is fully consumed. So, instead of fixing httputil.ReverseProxy, dial back the rules a bit. It's now okay to call CloseNotify before consuming the request body. The docs now say CloseNotifier may wait to fire before the request body is fully consumed, but doesn't say that the behavior is undefined anymore. Instead, we just wait until the request body is consumed and start watching for EOF from the client then. This CL also adds a test to ReverseProxy (using a POST request) that would've caught this earlier. Fixes #13666 Change-Id: Ib4e8c29c4bfbe7511f591cf9ffcda23a0f0b1269 Reviewed-on: https://go-review.googlesource.com/18144 Reviewed-by: Russ Cox <[email protected]> Run-TryBot: Brad Fitzpatrick <[email protected]>
1 parent 66f1f89 commit 4b0bc7c

File tree

5 files changed

+176
-13
lines changed

5 files changed

+176
-13
lines changed

src/net/http/httputil/reverseproxy_test.go

Lines changed: 41 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ package httputil
88

99
import (
1010
"bufio"
11+
"bytes"
1112
"io"
1213
"io/ioutil"
1314
"log"
@@ -104,7 +105,6 @@ func TestReverseProxy(t *testing.T) {
104105
if g, e := res.Trailer.Get("X-Trailer"), "trailer_value"; g != e {
105106
t.Errorf("Trailer(X-Trailer) = %q ; want %q", g, e)
106107
}
107-
108108
}
109109

110110
func TestXForwardedFor(t *testing.T) {
@@ -384,3 +384,43 @@ func TestReverseProxyGetPutBuffer(t *testing.T) {
384384
t.Errorf("Log events = %q; want %q", log, wantLog)
385385
}
386386
}
387+
388+
func TestReverseProxy_Post(t *testing.T) {
389+
const backendResponse = "I am the backend"
390+
const backendStatus = 200
391+
var requestBody = bytes.Repeat([]byte("a"), 1<<20)
392+
backend := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
393+
slurp, err := ioutil.ReadAll(r.Body)
394+
if err != nil {
395+
t.Error("Backend body read = %v", err)
396+
}
397+
if len(slurp) != len(requestBody) {
398+
t.Errorf("Backend read %d request body bytes; want %d", len(slurp), len(requestBody))
399+
}
400+
if !bytes.Equal(slurp, requestBody) {
401+
t.Error("Backend read wrong request body.") // 1MB; omitting details
402+
}
403+
w.Write([]byte(backendResponse))
404+
}))
405+
defer backend.Close()
406+
backendURL, err := url.Parse(backend.URL)
407+
if err != nil {
408+
t.Fatal(err)
409+
}
410+
proxyHandler := NewSingleHostReverseProxy(backendURL)
411+
frontend := httptest.NewServer(proxyHandler)
412+
defer frontend.Close()
413+
414+
postReq, _ := http.NewRequest("POST", frontend.URL, bytes.NewReader(requestBody))
415+
res, err := http.DefaultClient.Do(postReq)
416+
if err != nil {
417+
t.Fatalf("Do: %v", err)
418+
}
419+
if g, e := res.StatusCode, backendStatus; g != e {
420+
t.Errorf("got res.StatusCode %d; expected %d", g, e)
421+
}
422+
bodyBytes, _ := ioutil.ReadAll(res.Body)
423+
if g, e := string(bodyBytes), backendResponse; g != e {
424+
t.Errorf("got body %q; expected %q", g, e)
425+
}
426+
}

src/net/http/request.go

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -692,7 +692,13 @@ func putTextprotoReader(r *textproto.Reader) {
692692
}
693693

694694
// ReadRequest reads and parses an incoming request from b.
695-
func ReadRequest(b *bufio.Reader) (req *Request, err error) { return readRequest(b, true) }
695+
func ReadRequest(b *bufio.Reader) (req *Request, err error) { return readRequest(b, deleteHostHeader) }
696+
697+
// Constants for readRequest's deleteHostHeader parameter.
698+
const (
699+
deleteHostHeader = true
700+
keepHostHeader = false
701+
)
696702

697703
func readRequest(b *bufio.Reader, deleteHostHeader bool) (req *Request, err error) {
698704
tp := newTextprotoReader(b)

src/net/http/serve_test.go

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2482,6 +2482,59 @@ func TestHijackAfterCloseNotifier(t *testing.T) {
24822482
}
24832483
}
24842484

2485+
func TestHijackBeforeRequestBodyRead(t *testing.T) {
2486+
defer afterTest(t)
2487+
var requestBody = bytes.Repeat([]byte("a"), 1<<20)
2488+
bodyOkay := make(chan bool, 1)
2489+
gotCloseNotify := make(chan bool, 1)
2490+
ts := httptest.NewServer(HandlerFunc(func(w ResponseWriter, r *Request) {
2491+
defer close(bodyOkay) // caller will read false if nothing else
2492+
2493+
reqBody := r.Body
2494+
r.Body = nil // to test that server.go doesn't use this value.
2495+
2496+
gone := w.(CloseNotifier).CloseNotify()
2497+
slurp, err := ioutil.ReadAll(reqBody)
2498+
if err != nil {
2499+
t.Error("Body read: %v", err)
2500+
return
2501+
}
2502+
if len(slurp) != len(requestBody) {
2503+
t.Errorf("Backend read %d request body bytes; want %d", len(slurp), len(requestBody))
2504+
return
2505+
}
2506+
if !bytes.Equal(slurp, requestBody) {
2507+
t.Error("Backend read wrong request body.") // 1MB; omitting details
2508+
return
2509+
}
2510+
bodyOkay <- true
2511+
select {
2512+
case <-gone:
2513+
gotCloseNotify <- true
2514+
case <-time.After(5 * time.Second):
2515+
gotCloseNotify <- false
2516+
}
2517+
}))
2518+
defer ts.Close()
2519+
2520+
conn, err := net.Dial("tcp", ts.Listener.Addr().String())
2521+
if err != nil {
2522+
t.Fatal(err)
2523+
}
2524+
defer conn.Close()
2525+
2526+
fmt.Fprintf(conn, "POST / HTTP/1.1\r\nHost: foo\r\nContent-Length: %d\r\n\r\n%s",
2527+
len(requestBody), requestBody)
2528+
if !<-bodyOkay {
2529+
// already failed.
2530+
return
2531+
}
2532+
conn.Close()
2533+
if !<-gotCloseNotify {
2534+
t.Error("timeout waiting for CloseNotify")
2535+
}
2536+
}
2537+
24852538
func TestOptions(t *testing.T) {
24862539
uric := make(chan string, 2) // only expect 1, but leave space for 2
24872540
mux := NewServeMux()

src/net/http/server.go

Lines changed: 54 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -126,7 +126,7 @@ type CloseNotifier interface {
126126
// single value (true) when the client connection has gone
127127
// away.
128128
//
129-
// CloseNotify is undefined before Request.Body has been
129+
// CloseNotify may wait to notify until Request.Body has been
130130
// fully read.
131131
//
132132
// After the Handler has returned, there is no guarantee
@@ -135,7 +135,7 @@ type CloseNotifier interface {
135135
// If the protocol is HTTP/1.1 and CloseNotify is called while
136136
// processing an idempotent request (such a GET) while
137137
// HTTP/1.1 pipelining is in use, the arrival of a subsequent
138-
// pipelined request will cause a value to be sent on the
138+
// pipelined request may cause a value to be sent on the
139139
// returned channel. In practice HTTP/1.1 pipelining is not
140140
// enabled in browsers and not seen often in the wild. If this
141141
// is a problem, use HTTP/2 or only use CloseNotify on methods
@@ -353,7 +353,9 @@ type response struct {
353353
dateBuf [len(TimeFormat)]byte
354354
clenBuf [10]byte
355355

356-
closeNotifyCh <-chan bool // guarded by conn.mu
356+
// closeNotifyCh is non-nil once CloseNotify is called.
357+
// Guarded by conn.mu
358+
closeNotifyCh <-chan bool
357359
}
358360

359361
// declareTrailer is called for each Trailer header when the
@@ -693,7 +695,7 @@ func (c *conn) readRequest() (w *response, err error) {
693695
peek, _ := c.bufr.Peek(4) // ReadRequest will get err below
694696
c.bufr.Discard(numLeadingCRorLF(peek))
695697
}
696-
req, err := readRequest(c.bufr, false)
698+
req, err := readRequest(c.bufr, keepHostHeader)
697699
c.mu.Unlock()
698700
if err != nil {
699701
if c.r.hitReadLimit() {
@@ -986,7 +988,7 @@ func (cw *chunkWriter) writeHeader(p []byte) {
986988
}
987989

988990
if discard {
989-
_, err := io.CopyN(ioutil.Discard, w.req.Body, maxPostHandlerReadBytes+1)
991+
_, err := io.CopyN(ioutil.Discard, w.reqBody, maxPostHandlerReadBytes+1)
990992
switch err {
991993
case nil:
992994
// There must be even more data left over.
@@ -995,7 +997,7 @@ func (cw *chunkWriter) writeHeader(p []byte) {
995997
// Body was already consumed and closed.
996998
case io.EOF:
997999
// The remaining body was just consumed, close it.
998-
err = w.req.Body.Close()
1000+
err = w.reqBody.Close()
9991001
if err != nil {
10001002
w.closeAfterReply = true
10011003
}
@@ -1540,14 +1542,57 @@ func (w *response) CloseNotify() <-chan bool {
15401542
var once sync.Once
15411543
notify := func() { once.Do(func() { ch <- true }) }
15421544

1545+
if requestBodyRemains(w.reqBody) {
1546+
// They're still consuming the request body, so we
1547+
// shouldn't notify yet.
1548+
registerOnHitEOF(w.reqBody, func() {
1549+
c.mu.Lock()
1550+
defer c.mu.Unlock()
1551+
startCloseNotifyBackgroundRead(c, notify)
1552+
})
1553+
} else {
1554+
startCloseNotifyBackgroundRead(c, notify)
1555+
}
1556+
return ch
1557+
}
1558+
1559+
// c.mu must be held.
1560+
func startCloseNotifyBackgroundRead(c *conn, notify func()) {
15431561
if c.bufr.Buffered() > 0 {
1544-
// A pipelined request or unread request body data is available
1545-
// unread. Per the CloseNotifier docs, fire immediately.
1562+
// They've consumed the request body, so anything
1563+
// remaining is a pipelined request, which we
1564+
// document as firing on.
15461565
notify()
15471566
} else {
15481567
c.r.startBackgroundRead(notify)
15491568
}
1550-
return ch
1569+
}
1570+
1571+
func registerOnHitEOF(rc io.ReadCloser, fn func()) {
1572+
switch v := rc.(type) {
1573+
case *expectContinueReader:
1574+
registerOnHitEOF(v.readCloser, fn)
1575+
case *body:
1576+
v.registerOnHitEOF(fn)
1577+
default:
1578+
panic("unexpected type " + fmt.Sprintf("%T", rc))
1579+
}
1580+
}
1581+
1582+
// requestBodyRemains reports whether future calls to Read
1583+
// on rc might yield more data.
1584+
func requestBodyRemains(rc io.ReadCloser) bool {
1585+
if rc == eofReader {
1586+
return false
1587+
}
1588+
switch v := rc.(type) {
1589+
case *expectContinueReader:
1590+
return requestBodyRemains(v.readCloser)
1591+
case *body:
1592+
return v.bodyRemains()
1593+
default:
1594+
panic("unexpected type " + fmt.Sprintf("%T", rc))
1595+
}
15511596
}
15521597

15531598
// The HandlerFunc type is an adapter to allow the use of

src/net/http/transfer.go

Lines changed: 21 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -621,10 +621,11 @@ type body struct {
621621
closing bool // is the connection to be closed after reading body?
622622
doEarlyClose bool // whether Close should stop early
623623

624-
mu sync.Mutex // guards closed, and calls to Read and Close
624+
mu sync.Mutex // guards following, and calls to Read and Close
625625
sawEOF bool
626626
closed bool
627-
earlyClose bool // Close called and we didn't read to the end of src
627+
earlyClose bool // Close called and we didn't read to the end of src
628+
onHitEOF func() // if non-nil, func to call when EOF is Read
628629
}
629630

630631
// ErrBodyReadAfterClose is returned when reading a Request or Response
@@ -684,6 +685,10 @@ func (b *body) readLocked(p []byte) (n int, err error) {
684685
}
685686
}
686687

688+
if b.sawEOF && b.onHitEOF != nil {
689+
b.onHitEOF()
690+
}
691+
687692
return n, err
688693
}
689694

@@ -818,6 +823,20 @@ func (b *body) didEarlyClose() bool {
818823
return b.earlyClose
819824
}
820825

826+
// bodyRemains reports whether future Read calls might
827+
// yield data.
828+
func (b *body) bodyRemains() bool {
829+
b.mu.Lock()
830+
defer b.mu.Unlock()
831+
return !b.sawEOF
832+
}
833+
834+
func (b *body) registerOnHitEOF(fn func()) {
835+
b.mu.Lock()
836+
defer b.mu.Unlock()
837+
b.onHitEOF = fn
838+
}
839+
821840
// bodyLocked is a io.Reader reading from a *body when its mutex is
822841
// already held.
823842
type bodyLocked struct {

0 commit comments

Comments
 (0)