Opened 3 weeks ago
Last modified 3 weeks ago
#37319 assigned Bug
GZipMiddleware should skip responses without body.
| Reported by: | Vladimir Nani | Owned by: | Md. Saikat Islam |
|---|---|---|---|
| Component: | HTTP handling | Version: | 6.1 |
| Severity: | Normal | Keywords: | GZipMiddleware |
| Cc: | Triage Stage: | Accepted | |
| Has patch: | yes | 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 (8)
comment:1 by , 3 weeks ago
comment:2 by , 3 weeks ago
I reproduced this on current Django main through the WSGI handler.
A StreamingHttpResponse([], status=304) passed through GZipMiddleware with Accept-Encoding: gzip is returned with Content-Encoding: gzip and a non-empty gzip payload. The same behavior occurs with status 204.
RFC 9112 section 6.3 states that responses with 1xx, 204, or 304 status codes cannot contain a message body:
https://www.rfc-editor.org/rfc/rfc9112.html#section-6.3
The issue therefore appears valid and actionable. I suggest classifying it as:
- Type: Bug
- Component: HTTP handling
- Severity: Normal
- Triage stage: Accepted
I would also suggest narrowing the summary to:
GZipMiddleware compresses streaming 204 and 304 responses.
“Responses without body” is slightly ambiguous because an empty 200 response may legally be gzip encoded. The problem here is responses whose status code prohibits content.
Before implementing the fix, the complete scope should be considered. RFC 9112 also covers 1xx responses, while RFC 9110 prohibits content in 205 responses. HEAD responses have separate semantics and may still include representation metadata, so they should not automatically be treated identically.
This does not appear to be a Django 6.1 regression; the relevant middleware behavior is also present in older supported branches.
comment:3 by , 3 weeks ago
I will wait untill a maintainer set the triage stage to accepted. After that I will assign myself to this issue.
comment:4 by , 3 weeks ago
It looks like gunicorn has added the checks for 304 and 204 as well. https://github.com/benoitc/gunicorn/commit/9bc5891b4b06f25a8ce0e707053dcb2fb9bf638c#diff-0b3107decc367439e32c2eb51c3a8270d4399fa51c45e89f6a5e740e3c631188R353
comment:5 by , 3 weeks ago
https://github.com/django/django/commit/324434140cf30b4e1923f3bb251107afcdafc6db have a patch to fix this
comment:6 by , 3 weeks ago
| Component: | Uncategorized → HTTP handling |
|---|---|
| Has patch: | set |
| Type: | Uncategorized → Bug |
| Version: | 6.1 |
comment:7 by , 3 weeks ago
According to the triaging guidelines, another community member should review and reproduce the issue before it’s marked as “Accepted”. Reference: https://docs.djangoproject.com/en/dev/internals/contributing/triaging-tickets/
Since Yassin Bahri independently reproduced the issue, and I was also able to reproduce it, I’m marking this ticket as accepted.
comment:8 by , 3 weeks ago
| Owner: | set to |
|---|---|
| Status: | new → assigned |
| Triage Stage: | Unreviewed → Accepted |
| Version: | → 6.1 |
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 responseFor 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):Streaming (
test_streaming.py):In the streaming case, even the 204 and 304 responses with empty content still get
Content-Encoding: gzipand 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.