Browse Source

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.
Kevin Christopher Henry 8 years ago
parent
commit
ad332e5ca9
3 changed files with 39 additions and 15 deletions
  1. 6 2
      django/middleware/gzip.py
  2. 3 0
      docs/ref/middleware.txt
  3. 30 13
      tests/middleware/tests.py

+ 6 - 2
django/middleware/gzip.py

@@ -41,8 +41,12 @@ class GZipMiddleware(MiddlewareMixin):
             response.content = compressed_content
             response['Content-Length'] = str(len(response.content))
 
-        if response.has_header('ETag'):
-            response['ETag'] = re.sub('"$', ';gzip"', response['ETag'])
+        # If there is a strong ETag, make it weak to fulfill the requirements
+        # 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'
 
         return response

+ 3 - 0
docs/ref/middleware.txt

@@ -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
   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
 :func:`~django.views.decorators.gzip.gzip_page()` decorator.
 

+ 30 - 13
tests/middleware/tests.py

@@ -855,27 +855,44 @@ class GZipMiddlewareTest(SimpleTestCase):
 @override_settings(USE_ETAGS=True)
 class ETagGZipMiddlewareTest(SimpleTestCase):
     """
-    Tests if the ETagMiddleware behaves correctly with GZipMiddleware.
+    ETags are handled properly by GZipMiddleware.
     """
     rf = RequestFactory()
     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 = GZipMiddleware().process_response(
-            request,
-            CommonMiddleware().process_response(request, HttpResponse(self.compressible_string))
-        )
-        gzip_etag = response.get('ETag')
+        response = HttpResponse(self.compressible_string)
+        response['ETag'] = '"eggs"'
+        gzip_response = GZipMiddleware().process_response(request, response)
+        self.assertEqual(gzip_response['ETag'], 'W/"eggs"')
 
-        request = self.rf.get('/', HTTP_ACCEPT_ENCODING='')
+    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')
         response = GZipMiddleware().process_response(
             request,
-            CommonMiddleware().process_response(request, HttpResponse(self.compressible_string))
+            ConditionalGetMiddleware().process_response(request, HttpResponse(self.compressible_string))
         )
-        nogzip_etag = response.get('ETag')
-
-        self.assertNotEqual(gzip_etag, nogzip_etag)
+        gzip_etag = response['ETag']
+        next_request = self.rf.get('/', HTTP_ACCEPT_ENCODING='gzip, deflate', HTTP_IF_NONE_MATCH=gzip_etag)
+        next_response = ConditionalGetMiddleware().process_response(
+            next_request,
+            HttpResponse(self.compressible_string)
+        )
+        self.assertEqual(next_response.status_code, 304)