Opened 50 minutes ago

Last modified 11 minutes ago

#37319 new Uncategorized

GZipMiddleware should skip responses without body.

Reported by: Vladimir Nani Owned by:
Component: Uncategorized Version: 6.1
Severity: Normal Keywords: GZipMiddleware
Cc: Triage Stage: Unreviewed
Has patch: no Needs documentation: no
Needs tests: no Patch needs improvement: no
Easy pickings: no UI/UX: no

Description

GZipMiddleware compresses responses with status 304 that are not allowed to have content body.

Negative effect:

  • streaming response with 304 status code and empty body is passed to GZipMiddleware
  • Middleware check doesnt apply for streaming response and proceeds with compression
  • In situation where connections are pooled, for example with Traefik, the body is not read (according to rfc 9112).
  • The empty body compressed is left as a garbage in the sockets buffer.
  • When the socket is read for the next time instead of starting with "HTTP" it reads "\x1f\x8b\x08\x08\x00\x00\x00\x00\x00\xffaaaa"
  • The next unrelated request that reuses the socket fails with malformed http:
{"level":"debug","error":"net/http: HTTP/1.x transport connection broken: malformed HTTP version \"\\x1f\\x8b\\b\\b\\x00\\x00\\x00\\x00\\x00\\xffaaaaaaaaaaaaaaaa\\x00\\x03\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00HTTP/1.1\"","time":"2026-09-04T12:33:17+02:00","caller":"github.com/traefik/traefik/v3/pkg/proxy/httputil/proxy.go:117","message":"500 Internal Server Error"}

Proposal:
Check for 204 and 304 and do not compress these responses.

https://www.rfc-editor.org/rfc/rfc9112.html#section-6.3-2.1

Change History (1)

comment:1 by Md. Saikat Islam, 11 minutes ago

You are right. The middleware currently handles 204, 304, and short responses under 200 bytes only in the non-streaming case. This is the guard in GZipMiddleware:

if not response.streaming and len(response.content) < 200:
    return response

For streaming responses, this guard does not apply, and the middleware compresses them anyway — which it should not do.

I tested this to verify for 204, 304, short response, and long response.

Non-streaming (test.py):

204 204 None b''
304 304 None b''
short 200 200 None b'short body'
long 200 200 gzip b'\x1f\x8b\x08\x08\x00\x00\x00\x00\x00\x03aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\x00\xab\xa8\x18\x05#\r\x00\x00Q\xce\xb2\n\xf4\x01\x00\x00'

Streaming (test_streaming.py):

streaming 304 empty 304 gzip b'\x1f\x8b\x08\x08\x00\x00\x00\x00\x00\xffaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\x00\x03\x00\x00\x00\x00\x00\x00\x00\x00\x00'
streaming 204 empty 204 gzip b'\x1f\x8b\x08\x08\x00\x00\x00\x00\x00\xffaaaaaaa\x00\x03\x00\x00\x00\x00\x00\x00\x00\x00\x00'
streaming short 200 gzip b'\x1f\x8b\x08\x08\x00\x00\x00\x00\x00\xffaa\x00*\xce\xc8/*QH\xcaO\xa9\x04\x00\x00\x00\xff\xff\x03\x009\xb2\xed\xc0\n\x00\x00\x00'
streaming 200 long 200 gzip b'\x1f\x8b\x08\x08\x00\x00\x00\x00\x00\xffaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\x00JL\x1c\x05#\r\x00\x00\x00\x00\xff\xff\x03\x00\x11A\x92\x05\xf4\x01\x00\x00'

In the streaming case, even the 204 and 304 responses with empty content still get Content-Encoding: gzip and non-empty compressed bytes — gzip-compressing an empty sequence still produces gzip header/footer framing, so it's never truly empty. This matches the bug behavior described in the report.

I can provide the test files here if needed.

I would like to work on this issue.

Note: See TracTickets for help on using tickets.
Back to Top