Skip to content

Commit 22d5d09

Browse files
neildgopherbot
authored andcommitted
net/http/httputil: close hijacked connections when CloseWrite not available
CL 637939 changed ReverseProxy's handling of hijacked connections: After copying all data in one direction, it half-closes the outbound connection rather than fully closing both. Revert to the old behavior when the outbound connection does not support CloseWrite, avoiding a case where one side of the proxied connection closes but the other remains open. Fixes #72140 Change-Id: Ic0cacaa6323290f89ba48fd6cae737e86045a435 Reviewed-on: https://go-review.googlesource.com/c/go/+/655595 Reviewed-by: Jonathan Amsterdam <[email protected]> LUCI-TryBot-Result: Go LUCI <[email protected]> Auto-Submit: Damien Neil <[email protected]>
1 parent bc5f4a5 commit 22d5d09

File tree

2 files changed

+55
-4
lines changed

2 files changed

+55
-4
lines changed

Diff for: src/net/http/httputil/reverseproxy.go

+7-4
Original file line numberDiff line numberDiff line change
@@ -794,16 +794,19 @@ func (p *ReverseProxy) handleUpgradeResponse(rw http.ResponseWriter, req *http.R
794794
go spc.copyToBackend(errc)
795795
go spc.copyFromBackend(errc)
796796

797-
// wait until both copy functions have sent on the error channel
797+
// Wait until both copy functions have sent on the error channel,
798+
// or until one fails.
798799
err := <-errc
799800
if err == nil {
800801
err = <-errc
801802
}
802-
if err != nil {
803+
if err != nil && err != errCopyDone {
803804
p.getErrorHandler()(rw, req, fmt.Errorf("can't copy: %v", err))
804805
}
805806
}
806807

808+
var errCopyDone = errors.New("hijacked connection copy complete")
809+
807810
// switchProtocolCopier exists so goroutines proxying data back and
808811
// forth have nice names in stacks.
809812
type switchProtocolCopier struct {
@@ -822,7 +825,7 @@ func (c switchProtocolCopier) copyFromBackend(errc chan<- error) {
822825
return
823826
}
824827

825-
errc <- nil
828+
errc <- errCopyDone
826829
}
827830

828831
func (c switchProtocolCopier) copyToBackend(errc chan<- error) {
@@ -837,7 +840,7 @@ func (c switchProtocolCopier) copyToBackend(errc chan<- error) {
837840
return
838841
}
839842

840-
errc <- nil
843+
errc <- errCopyDone
841844
}
842845

843846
func cleanQueryParams(s string) string {

Diff for: src/net/http/httputil/reverseproxy_test.go

+48
Original file line numberDiff line numberDiff line change
@@ -1701,6 +1701,54 @@ func TestReverseProxyWebSocketHalfTCP(t *testing.T) {
17011701
}
17021702
}
17031703

1704+
func TestReverseProxyUpgradeNoCloseWrite(t *testing.T) {
1705+
// The backend hijacks the connection,
1706+
// reads all data from the client,
1707+
// and returns.
1708+
backendDone := make(chan struct{})
1709+
backend := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
1710+
w.Header().Set("Connection", "upgrade")
1711+
w.Header().Set("Upgrade", "u")
1712+
w.WriteHeader(101)
1713+
conn, _, err := http.NewResponseController(w).Hijack()
1714+
if err != nil {
1715+
t.Errorf("Hijack: %v", err)
1716+
}
1717+
io.Copy(io.Discard, conn)
1718+
close(backendDone)
1719+
}))
1720+
backendURL, err := url.Parse(backend.URL)
1721+
if err != nil {
1722+
t.Fatal(err)
1723+
}
1724+
1725+
// The proxy includes a ModifyResponse function which replaces the response body
1726+
// with its own wrapper, dropping the original body's CloseWrite method.
1727+
proxyHandler := NewSingleHostReverseProxy(backendURL)
1728+
proxyHandler.ModifyResponse = func(resp *http.Response) error {
1729+
type readWriteCloserOnly struct {
1730+
io.ReadWriteCloser
1731+
}
1732+
resp.Body = readWriteCloserOnly{resp.Body.(io.ReadWriteCloser)}
1733+
return nil
1734+
}
1735+
frontend := httptest.NewServer(proxyHandler)
1736+
defer frontend.Close()
1737+
1738+
// The client sends a request and closes the connection.
1739+
req, _ := http.NewRequest("GET", frontend.URL, nil)
1740+
req.Header.Set("Connection", "upgrade")
1741+
req.Header.Set("Upgrade", "u")
1742+
resp, err := frontend.Client().Do(req)
1743+
if err != nil {
1744+
t.Fatal(err)
1745+
}
1746+
resp.Body.Close()
1747+
1748+
// We expect that the client's closure of the connection is propagated to the backend.
1749+
<-backendDone
1750+
}
1751+
17041752
func TestUnannouncedTrailer(t *testing.T) {
17051753
backend := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
17061754
w.WriteHeader(http.StatusOK)

0 commit comments

Comments
 (0)