diff options
| author | Damien Neil <dneil@google.com> | 2025-03-06 13:24:58 -0800 |
|---|---|---|
| committer | Gopher Robot <gobot@golang.org> | 2025-03-10 08:11:43 -0700 |
| commit | 22d5d09f1e39bf0ef77bfcf80388c676e7e91574 (patch) | |
| tree | 0b954411e0ffe53785d642942f315660f2a3e273 /src/net/http/httputil/reverseproxy_test.go | |
| parent | bc5f4a555e933e6861d12edba4c2d87ef6caf8e6 (diff) | |
| download | go-22d5d09f1e39bf0ef77bfcf80388c676e7e91574.tar.xz | |
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 <jba@google.com>
LUCI-TryBot-Result: Go LUCI <golang-scoped@luci-project-accounts.iam.gserviceaccount.com>
Auto-Submit: Damien Neil <dneil@google.com>
Diffstat (limited to 'src/net/http/httputil/reverseproxy_test.go')
| -rw-r--r-- | src/net/http/httputil/reverseproxy_test.go | 48 |
1 files changed, 48 insertions, 0 deletions
diff --git a/src/net/http/httputil/reverseproxy_test.go b/src/net/http/httputil/reverseproxy_test.go index a826dc82fe..1acbc296c3 100644 --- a/src/net/http/httputil/reverseproxy_test.go +++ b/src/net/http/httputil/reverseproxy_test.go @@ -1701,6 +1701,54 @@ func TestReverseProxyWebSocketHalfTCP(t *testing.T) { } } +func TestReverseProxyUpgradeNoCloseWrite(t *testing.T) { + // The backend hijacks the connection, + // reads all data from the client, + // and returns. + backendDone := make(chan struct{}) + backend := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + w.Header().Set("Connection", "upgrade") + w.Header().Set("Upgrade", "u") + w.WriteHeader(101) + conn, _, err := http.NewResponseController(w).Hijack() + if err != nil { + t.Errorf("Hijack: %v", err) + } + io.Copy(io.Discard, conn) + close(backendDone) + })) + backendURL, err := url.Parse(backend.URL) + if err != nil { + t.Fatal(err) + } + + // The proxy includes a ModifyResponse function which replaces the response body + // with its own wrapper, dropping the original body's CloseWrite method. + proxyHandler := NewSingleHostReverseProxy(backendURL) + proxyHandler.ModifyResponse = func(resp *http.Response) error { + type readWriteCloserOnly struct { + io.ReadWriteCloser + } + resp.Body = readWriteCloserOnly{resp.Body.(io.ReadWriteCloser)} + return nil + } + frontend := httptest.NewServer(proxyHandler) + defer frontend.Close() + + // The client sends a request and closes the connection. + req, _ := http.NewRequest("GET", frontend.URL, nil) + req.Header.Set("Connection", "upgrade") + req.Header.Set("Upgrade", "u") + resp, err := frontend.Client().Do(req) + if err != nil { + t.Fatal(err) + } + resp.Body.Close() + + // We expect that the client's closure of the connection is propagated to the backend. + <-backendDone +} + func TestUnannouncedTrailer(t *testing.T) { backend := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { w.WriteHeader(http.StatusOK) |
