From dde370be2a238fe2ef9d78734ccf26d0a95812a3 Mon Sep 17 00:00:00 2001 From: albinmini Date: Wed, 22 Jul 2026 15:16:38 +0000 Subject: [PATCH] Decompress HTTP/1.1 responses regardless of who set Accept-Encoding HTTP/1.1 now decodes based on the response's actual Content-Encoding whenever DisableCompression is false, matching the HTTP/2 path, instead of only when "gzip" appears in Accept-Encoding (addedGzip). (The original patch also made DecompressBody strip Content-Encoding and Content-Length on all paths and leave identity responses untouched; upstream's DecompressBody now does both.) --- example_client_test.go | 25 ++++++++++++------------- transport.go | 21 ++++----------------- 2 files changed, 16 insertions(+), 30 deletions(-) diff --git a/example_client_test.go b/example_client_test.go index 00c3454d..c1d09554 100644 --- a/example_client_test.go +++ b/example_client_test.go @@ -328,13 +328,12 @@ func TestCompressionBrotli(t *testing.T) { } defer resp.Body.Close() - // HTTP/2 decodes the body even though the caller set Accept-Encoding, HTTP/1.1 only when it set it itself. - if resp.ProtoMajor == 2 { - if !resp.Uncompressed { - t.Fatal("brotli body was not decoded") - } - } else if h := resp.Header.Get("content-encoding"); h != "br" { - t.Fatalf("Got content-encoding header %v, expected br", h) + // Both protocols decode the body even though the caller set Accept-Encoding. + if !resp.Uncompressed { + t.Fatal("brotli body was not decoded") + } + if h := resp.Header.Get("content-encoding"); h != "" { + t.Fatalf("Got content-encoding header %v, expected none", h) } } @@ -370,9 +369,9 @@ func deflateServer(t *testing.T, zlibWrapped bool) *httptest.Server { })) } -// A caller that sets accept-encoding itself owns the decoding: the transport -// must not decompress the body or strip Content-Encoding. Verify that, and -// that the library decodes both deflate flavours. +// The transport decodes by the response's Content-Encoding even when the caller +// set accept-encoding itself, and strips the header. Verify that for both deflate +// flavours. func testCompressionDeflate(t *testing.T, zlibWrapped bool) { t.Helper() @@ -393,11 +392,11 @@ func testCompressionDeflate(t *testing.T, zlibWrapped bool) { } defer resp.Body.Close() - if h := resp.Header.Get("content-encoding"); h != "deflate" { - t.Fatalf("Expected content encoding deflate, got %q", h) + if h := resp.Header.Get("content-encoding"); h != "" { + t.Fatalf("Expected no content encoding, got %q", h) } - got, err := io.ReadAll(http.DecompressBodyByType(resp.Body, "deflate")) + got, err := io.ReadAll(resp.Body) if err != nil { t.Fatal(err) } diff --git a/transport.go b/transport.go index b5fbf4a4..d2892d6c 100644 --- a/transport.go +++ b/transport.go @@ -2190,7 +2190,7 @@ func (pc *persistConn) readLoop() { resp.Body = body - if rc.addedGzip { + if !pc.t.DisableCompression { resp.Body = DecompressBody(resp) } @@ -2467,11 +2467,6 @@ type requestAndChan struct { cancelKey cancelKey ch chan responseAndError // unbuffered; always send in select on callerGone - // whether the Transport (as opposed to the user client code) - // added the Accept-Encoding gzip header. If the Transport - // set it, only then do we transparently decode the gzip. - addedGzip bool - // Optional blocking chan for Expect: 100-continue (for send). // If the request has an "Expect: 100-continue" header and // the server responds 100 Continue, readLoop send a value @@ -2541,11 +2536,10 @@ func (pc *persistConn) roundTrip(req *transportRequest) (resp *Response, err err } // Ask for a compressed version if the caller didn't set their - // own value for Accept-Encoding. We only attempt to - // uncompress the gzip stream if we were the layer that - // requested it. + // own value for Accept-Encoding. The response body is + // transparently decoded based on the response's actual + // Content-Encoding regardless of which layer asked for it. - requestedGzip := false if !pc.t.DisableCompression && req.Header.Get("Accept-Encoding") == "" && req.Header.get("accept-encoding") == "" && @@ -2562,15 +2556,9 @@ func (pc *persistConn) roundTrip(req *transportRequest) (resp *Response, err err // We don't request gzip if the request is for a range, since // auto-decoding a portion of a gzipped document will just fail // anyway. See https://golang.org/issue/8923 - requestedGzip = true req.extraHeaders().Set("Accept-Encoding", "gzip, deflate, br, zstd") } - // Set gzip to true if the caller's Accept-Encoding includes gzip. - if !pc.t.DisableCompression && (strings.Contains(req.Header.Get("Accept-Encoding"), "gzip") || strings.Contains(req.Request.Header.get("accept-encoding"), "gzip")) { - requestedGzip = true - } - var continueCh chan struct{} if req.ProtoAtLeast(1, 1) && req.Body != nil && req.expectsContinue() { continueCh = make(chan struct{}, 1) @@ -2605,7 +2593,6 @@ func (pc *persistConn) roundTrip(req *transportRequest) (resp *Response, err err req: req.Request, cancelKey: req.cancelKey, ch: resc, - addedGzip: requestedGzip, continueCh: continueCh, callerGone: gone, }