diff --git a/django/middleware/gzip.py b/django/middleware/gzip.py index 7ccd00ac19..eb151d7ad5 100644 --- a/django/middleware/gzip.py +++ b/django/middleware/gzip.py @@ -1,7 +1,7 @@ from django.utils.cache import patch_vary_headers from django.utils.deprecation import MiddlewareMixin from django.utils.regex_helper import _lazy_re_compile -from django.utils.text import compress_sequence, compress_string +from django.utils.text import acompress_sequence, compress_sequence, compress_string re_accepts_gzip = _lazy_re_compile(r"\bgzip\b") @@ -32,18 +32,10 @@ class GZipMiddleware(MiddlewareMixin): if response.streaming: if response.is_async: - # pull to lexical scope to capture fixed reference in case - # streaming_content is set again later. - original_iterator = response.streaming_content - - async def gzip_wrapper(): - async for chunk in original_iterator: - yield compress_string( - chunk, - max_random_bytes=self.max_random_bytes, - ) - - response.streaming_content = gzip_wrapper() + response.streaming_content = acompress_sequence( + response.streaming_content, + max_random_bytes=self.max_random_bytes, + ) else: response.streaming_content = compress_sequence( response.streaming_content, diff --git a/django/utils/text.py b/django/utils/text.py index 26edde99e3..bad1da6729 100644 --- a/django/utils/text.py +++ b/django/utils/text.py @@ -393,6 +393,22 @@ def compress_sequence(sequence, *, max_random_bytes=None): yield buf.read() +async def acompress_sequence(sequence, *, max_random_bytes=None): + buf = StreamingBuffer() + filename = _get_random_filename(max_random_bytes) if max_random_bytes else None + with GzipFile( + filename=filename, mode="wb", compresslevel=6, fileobj=buf, mtime=0 + ) as zfile: + # Output headers... + yield buf.read() + async for item in sequence: + zfile.write(item) + data = buf.read() + if data: + yield data + yield buf.read() + + # Expression to match some_token and some_token="with spaces" (and similarly # for single-quoted strings). smart_split_re = _lazy_re_compile( diff --git a/tests/middleware/tests.py b/tests/middleware/tests.py index c4aac0552b..a61c4b147f 100644 --- a/tests/middleware/tests.py +++ b/tests/middleware/tests.py @@ -2,6 +2,7 @@ import gzip import random import re import struct +import zlib from io import BytesIO from unittest import mock from urllib.parse import quote @@ -880,8 +881,8 @@ class GZipMiddlewareTest(SimpleTestCase): @staticmethod def decompress(gzipped_string): - with gzip.GzipFile(mode="rb", fileobj=BytesIO(gzipped_string)) as f: - return f.read() + # Use zlib to ensure gzipped_string contains exactly one gzip stream. + return zlib.decompress(gzipped_string, zlib.MAX_WBITS | 16) @staticmethod def get_mtime(gzipped_string):