Skip to content

Commit 8f38f28

Browse files
jameshartigbradfitz
authored andcommitted
net/http/httputil: make ReverseProxy panic on error while copying body
Fixes #23643. Change-Id: I4f8195a36be817d79b9e7c61a5301f153b681493 Reviewed-on: https://go-review.googlesource.com/91675 Reviewed-by: Brad Fitzpatrick <[email protected]> Run-TryBot: Brad Fitzpatrick <[email protected]>
1 parent 08304e8 commit 8f38f28

File tree

2 files changed

+61
-14
lines changed

2 files changed

+61
-14
lines changed

src/net/http/httputil/reverseproxy.go

Lines changed: 17 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -237,7 +237,14 @@ func (p *ReverseProxy) ServeHTTP(rw http.ResponseWriter, req *http.Request) {
237237
fl.Flush()
238238
}
239239
}
240-
p.copyResponse(rw, res.Body)
240+
err = p.copyResponse(rw, res.Body)
241+
if err != nil {
242+
defer res.Body.Close()
243+
// Since we're streaming the response, if we run into an error all we can do
244+
// is abort the request. Issue 23643: ReverseProxy should use ErrAbortHandler
245+
// on read error while copying body
246+
panic(http.ErrAbortHandler)
247+
}
241248
res.Body.Close() // close now, instead of defer, to populate res.Trailer
242249

243250
if len(res.Trailer) == announcedTrailers {
@@ -265,7 +272,7 @@ func removeConnectionHeaders(h http.Header) {
265272
}
266273
}
267274

268-
func (p *ReverseProxy) copyResponse(dst io.Writer, src io.Reader) {
275+
func (p *ReverseProxy) copyResponse(dst io.Writer, src io.Reader) error {
269276
if p.FlushInterval != 0 {
270277
if wf, ok := dst.(writeFlusher); ok {
271278
mlw := &maxLatencyWriter{
@@ -282,13 +289,14 @@ func (p *ReverseProxy) copyResponse(dst io.Writer, src io.Reader) {
282289
var buf []byte
283290
if p.BufferPool != nil {
284291
buf = p.BufferPool.Get()
292+
defer p.BufferPool.Put(buf)
285293
}
286-
p.copyBuffer(dst, src, buf)
287-
if p.BufferPool != nil {
288-
p.BufferPool.Put(buf)
289-
}
294+
_, err := p.copyBuffer(dst, src, buf)
295+
return err
290296
}
291297

298+
// copyBuffer returns any write errors or non-EOF read errors, and the amount
299+
// of bytes written.
292300
func (p *ReverseProxy) copyBuffer(dst io.Writer, src io.Reader, buf []byte) (int64, error) {
293301
if len(buf) == 0 {
294302
buf = make([]byte, 32*1024)
@@ -312,6 +320,9 @@ func (p *ReverseProxy) copyBuffer(dst io.Writer, src io.Reader, buf []byte) (int
312320
}
313321
}
314322
if rerr != nil {
323+
if rerr == io.EOF {
324+
rerr = nil
325+
}
315326
return written, rerr
316327
}
317328
}

src/net/http/httputil/reverseproxy_test.go

Lines changed: 44 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -649,18 +649,22 @@ func TestReverseProxy_CopyBuffer(t *testing.T) {
649649
var proxyLog bytes.Buffer
650650
rproxy := NewSingleHostReverseProxy(rpURL)
651651
rproxy.ErrorLog = log.New(&proxyLog, "", log.Lshortfile)
652-
frontendProxy := httptest.NewServer(rproxy)
652+
donec := make(chan bool, 1)
653+
frontendProxy := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
654+
defer func() { donec <- true }()
655+
rproxy.ServeHTTP(w, r)
656+
}))
653657
defer frontendProxy.Close()
654658

655-
resp, err := http.Get(frontendProxy.URL)
656-
if err != nil {
657-
t.Fatalf("failed to reach proxy: %v", err)
658-
}
659-
defer resp.Body.Close()
660-
661-
if _, err := ioutil.ReadAll(resp.Body); err == nil {
659+
if _, err = frontendProxy.Client().Get(frontendProxy.URL); err == nil {
662660
t.Fatalf("want non-nil error")
663661
}
662+
// The race detector complains about the proxyLog usage in logf in copyBuffer
663+
// and our usage below with proxyLog.Bytes() so we're explicitly using a
664+
// channel to ensure that the ReverseProxy's ServeHTTP is done before we
665+
// continue after Get.
666+
<-donec
667+
664668
expected := []string{
665669
"EOF",
666670
"read",
@@ -813,3 +817,35 @@ func (cc *checkCloser) Close() error {
813817
func (cc *checkCloser) Read(b []byte) (int, error) {
814818
return len(b), nil
815819
}
820+
821+
// Issue 23643: panic on body copy error
822+
func TestReverseProxy_PanicBodyError(t *testing.T) {
823+
backendServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
824+
out := "this call was relayed by the reverse proxy"
825+
// Coerce a wrong content length to induce io.ErrUnexpectedEOF
826+
w.Header().Set("Content-Length", fmt.Sprintf("%d", len(out)*2))
827+
fmt.Fprintln(w, out)
828+
}))
829+
defer backendServer.Close()
830+
831+
rpURL, err := url.Parse(backendServer.URL)
832+
if err != nil {
833+
t.Fatal(err)
834+
}
835+
836+
rproxy := NewSingleHostReverseProxy(rpURL)
837+
838+
// Ensure that the handler panics when the body read encounters an
839+
// io.ErrUnexpectedEOF
840+
defer func() {
841+
err := recover()
842+
if err == nil {
843+
t.Fatal("handler should have panicked")
844+
}
845+
if err != http.ErrAbortHandler {
846+
t.Fatal("expected ErrAbortHandler, got", err)
847+
}
848+
}()
849+
req, _ := http.NewRequest("GET", "http://foo.tld/", nil)
850+
rproxy.ServeHTTP(httptest.NewRecorder(), req)
851+
}

0 commit comments

Comments
 (0)