Example: HTTP Client Handle Compressed HTTP Responses
Go's http.Transport can transparently decompress gzip responses. When it does, resp.Body contains the decompressed data, not the bytes received from the server.
That affects response metadata, size limits, and connection reuse.
Transparent gzip Decompression
The default Transport automatically requests gzip when:
DisableCompressionisfalse- the request has no
Accept-Encodingheader - the request has no
Rangeheader - the method is not
HEAD
It adds:
If the server returns a gzip-encoded response, Transport decompresses it before the application reads resp.Body.
Transparent decompression also changes the response metadata:
resp.Uncompressed is the reliable signal that net/http performed the decompression. An empty Content-Encoding header is not: an ordinary uncompressed response has the same value.
If the application needs the compressed response instead, disable automatic compression handling:
DisableCompression controls the Transport's automatic gzip behavior. It is unrelated to connection reuse; DisableKeepAlives controls whether the Transport uses HTTP keep-alive connections.
Application-Managed gzip
Set Accept-Encoding explicitly when the application needs to handle gzip itself.
Because the application supplied Accept-Encoding, the Transport does not transparently decode the gzip response. gzip.Reader receives the compressed stream directly from resp.Body.
There are two distinct resources:
gzip.Reader, which the application createdresp.Body, which the HTTP client owns
gzip.Reader.Close does not close the underlying resp.Body. Both must be closed.
The defer resp.Body.Close() is registered immediately after Do succeeds, so it also covers errors from gzip.NewReader. There is no need to drain an invalid response merely because gzip initialization failed.
Reading a response to EOF and closing it has a different purpose: it can allow an HTTP/1.x keep-alive connection to be reused. Closing an unread body releases the response body, but the underlying connection may not be reusable.
Limit the Decompressed Data
Content-Length describes the HTTP representation on the wire. It is not a safe limit for the amount of data produced by decompression.
A small gzip payload can expand into a very large response.
When Transport has already decompressed the response, put the limit directly around resp.Body:
Reading one byte beyond the limit distinguishes an acceptable response from an oversized one.
Do not use Content-Length for this check. After transparent decompression, resp.ContentLength is -1; even before decompression, the compressed length says nothing about the size of the decompressed data.
When gzip is handled by the application, put the limit around gzip.Reader:
The limit now applies to decompressed bytes.
If the limit is exceeded, stop reading and close resp.Body. Do not continue consuming an untrusted response simply to preserve an HTTP/1.x connection.
The trade-off is straightforward:
When a gzip.Reader is stopped before EOF, the complete gzip stream may not be consumed, so its final checksum may not be verified. That is acceptable when the response has already exceeded the application's size limit.
Brotli and Zstandard
The standard net/http Transport provides transparent gzip handling. It does not provide equivalent automatic decompression for Brotli (br) or Zstandard (zstd).
If a client needs those encodings, decompression can be implemented in a custom http.RoundTripper.
The wrapper should:
- call the underlying
RoundTripper - inspect
Content-Encoding - create the appropriate decompressor
- replace
resp.Bodywith anio.ReadCloserthat owns both layers - update the response metadata to match the new body
The body wrapper should close the decompressor before closing the original response body:
For a real implementation, close should close the decompressor and then the original resp.Body, preserving the first error if both operations fail.
The response metadata must be updated consistently as well. Simply setting:
does not reproduce the standard library's transparent-decompression behavior.
If resp.Body now yields decompressed data, the compressed Content-Length must not remain as though it described the new body. Content-Encoding must likewise reflect what the application actually receives.
A reusable decompression layer should therefore define both its body ownership and its response metadata semantics explicitly.
What to Keep in Mind
The important rules are small:
resp.Uncompressedtells you thatnet/httpperformed transparent gzip decompression.- Setting
Accept-Encodingexplicitly transfers decompression responsibility to the application. gzip.Readerandresp.Bodyhave separate ownership and must both be closed.- Size limits should apply to decompressed data when decompression can expand an untrusted response.
- Reading to EOF can preserve HTTP/1.x connection reuse; stopping early may not.
- A hard response-size limit should not be weakened just to preserve a keep-alive connection.