Refs #19705 -- Made GZipMiddleware make ETags weak.

Django's conditional request processing can now produce 304 Not Modified
responses for content that is subject to compression.
This commit is contained in:
Kevin Christopher Henry 2016-10-11 03:42:00 -04:00 committed by Tim Graham
parent 816eae3508
commit ad332e5ca9
3 changed files with 39 additions and 15 deletions

View File

@ -41,8 +41,12 @@ class GZipMiddleware(MiddlewareMixin):
response.content = compressed_content response.content = compressed_content
response['Content-Length'] = str(len(response.content)) response['Content-Length'] = str(len(response.content))
if response.has_header('ETag'): # If there is a strong ETag, make it weak to fulfill the requirements
response['ETag'] = re.sub('"$', ';gzip"', response['ETag']) # of RFC 7232 section-2.1 while also allowing conditional request
# matches on ETags.
etag = response.get('ETag')
if etag and etag.startswith('"'):
response['ETag'] = 'W/' + etag
response['Content-Encoding'] = 'gzip' response['Content-Encoding'] = 'gzip'
return response return response

View File

@ -155,6 +155,9 @@ It will NOT compress content if any of the following are true:
* The request (the browser) hasn't sent an ``Accept-Encoding`` header * The request (the browser) hasn't sent an ``Accept-Encoding`` header
containing ``gzip``. containing ``gzip``.
If the response has an ``ETag`` header, the ETag is made weak to comply with
:rfc:`7232#section-2.1`.
You can apply GZip compression to individual views using the You can apply GZip compression to individual views using the
:func:`~django.views.decorators.gzip.gzip_page()` decorator. :func:`~django.views.decorators.gzip.gzip_page()` decorator.

View File

@ -855,27 +855,44 @@ class GZipMiddlewareTest(SimpleTestCase):
@override_settings(USE_ETAGS=True) @override_settings(USE_ETAGS=True)
class ETagGZipMiddlewareTest(SimpleTestCase): class ETagGZipMiddlewareTest(SimpleTestCase):
""" """
Tests if the ETagMiddleware behaves correctly with GZipMiddleware. ETags are handled properly by GZipMiddleware.
""" """
rf = RequestFactory() rf = RequestFactory()
compressible_string = b'a' * 500 compressible_string = b'a' * 500
def test_compress_response(self): def test_strong_etag_modified(self):
""" """
ETag is changed after gzip compression is performed. GZipMiddleware makes a strong ETag weak.
"""
request = self.rf.get('/', HTTP_ACCEPT_ENCODING='gzip, deflate')
response = HttpResponse(self.compressible_string)
response['ETag'] = '"eggs"'
gzip_response = GZipMiddleware().process_response(request, response)
self.assertEqual(gzip_response['ETag'], 'W/"eggs"')
def test_weak_etag_not_modified(self):
"""
GZipMiddleware doesn't modify a weak ETag.
"""
request = self.rf.get('/', HTTP_ACCEPT_ENCODING='gzip, deflate')
response = HttpResponse(self.compressible_string)
response['ETag'] = 'W/"eggs"'
gzip_response = GZipMiddleware().process_response(request, response)
self.assertEqual(gzip_response['ETag'], 'W/"eggs"')
def test_etag_match(self):
"""
GZipMiddleware allows 304 Not Modified responses.
""" """
request = self.rf.get('/', HTTP_ACCEPT_ENCODING='gzip, deflate') request = self.rf.get('/', HTTP_ACCEPT_ENCODING='gzip, deflate')
response = GZipMiddleware().process_response( response = GZipMiddleware().process_response(
request, request,
CommonMiddleware().process_response(request, HttpResponse(self.compressible_string)) ConditionalGetMiddleware().process_response(request, HttpResponse(self.compressible_string))
) )
gzip_etag = response.get('ETag') gzip_etag = response['ETag']
next_request = self.rf.get('/', HTTP_ACCEPT_ENCODING='gzip, deflate', HTTP_IF_NONE_MATCH=gzip_etag)
request = self.rf.get('/', HTTP_ACCEPT_ENCODING='') next_response = ConditionalGetMiddleware().process_response(
response = GZipMiddleware().process_response( next_request,
request, HttpResponse(self.compressible_string)
CommonMiddleware().process_response(request, HttpResponse(self.compressible_string))
) )
nogzip_etag = response.get('ETag') self.assertEqual(next_response.status_code, 304)
self.assertNotEqual(gzip_etag, nogzip_etag)