aboutsummaryrefslogtreecommitdiff
path: root/src/net/http/httputil/reverseproxy_test.go
diff options
context:
space:
mode:
authorDamien Neil <dneil@google.com>2025-03-06 13:24:58 -0800
committerGopher Robot <gobot@golang.org>2025-03-10 08:11:43 -0700
commit22d5d09f1e39bf0ef77bfcf80388c676e7e91574 (patch)
tree0b954411e0ffe53785d642942f315660f2a3e273 /src/net/http/httputil/reverseproxy_test.go
parentbc5f4a555e933e6861d12edba4c2d87ef6caf8e6 (diff)
downloadgo-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.go48
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)