[Django] #36655: GzipMiddleware buffers streaming responses

2 views
Skip to first unread message

Django

unread,
Oct 10, 2025, 12:02:19 PM10/10/25
to django-...@googlegroups.com
#36655: GzipMiddleware buffers streaming responses
-----------------------------------------+------------------------------
Reporter: Adam Johnson | Owner: Adam Johnson
Type: Bug | Status: assigned
Component: HTTP handling | Version: dev
Severity: Normal | Keywords:
Triage Stage: Unreviewed | Has patch: 0
Needs documentation: 0 | Needs tests: 0
Patch needs improvement: 0 | Easy pickings: 0
UI/UX: 0 |
-----------------------------------------+------------------------------
Currently, `GzipMiddleware`, via `compress_sequence()`, buffers the entire
response before sending it to the client. This can cause issues for
clients that expect to receive data in chunks, such as those using Server-
Sent Events (SSE) or WebSockets.

This issue was reported to me in the django-browser-reload project back in
[https://github.com/adamchainz/django-browser-reload/pull/161 Issue #161
(2023)], where a contributor fixed it with a workaround, and I didn't
think to investigate. Now, while implementing
[https://github.com/adamchainz/django-http-compression django-http-
compression], I have realized that it’s a proper bug that can be fixed by
adding a call to `zfile.flush()`, as done in its
[https://github.com/adamchainz/django-http-compression/pull/8 PR #8].

To reproduce the issue, use the below app, which can be run with `uv run
--script`. If you comment out `GzipMiddleware` and load the page in a
browser, you will see the numbers incrementing every second. If you
include `GzipMiddleware`, the page will never load. Adding the
`zfile.flush()` call in `compress_sequence()` fixes the issue.

{{{#!python
#!/usr/bin/env uv run --script
# /// script
# requires-python = ">=3.14"
# dependencies = [
# "django",
# ]
# ///
from __future__ import annotations

import os
import sys
import time

from django.conf import settings
from django.core.wsgi import get_wsgi_application
from django.http import StreamingHttpResponse
from django.urls import path

settings.configure(
# Dangerous: disable host header validation
ALLOWED_HOSTS=["*"],
# Use DEBUG=1 to enable debug mode
DEBUG=(os.environ.get("DEBUG", "") == "1"),
# Make this module the urlconf
ROOT_URLCONF=__name__,
# Only gzip middleware
MIDDLEWARE=[
"django.middleware.gzip.GZipMiddleware",
],
)


def clock(request):
def stream():
yield "<h1>Clock</h1>\n"
count = 1
while True:
yield f"<p>{count}</p>\n"
count += 1
time.sleep(1)

return StreamingHttpResponse(stream())


urlpatterns = [
path("", clock),
]

app = get_wsgi_application()

if __name__ == "__main__":
from django.core.management import execute_from_command_line

execute_from_command_line(sys.argv)
}}}
--
Ticket URL: <https://code.djangoproject.com/ticket/36655>
Django <https://code.djangoproject.com/>
The Web framework for perfectionists with deadlines.
Reply all
Reply to author
Forward
0 new messages