Fixed #10762, #17514 -- Prevented the GZip middleware from returning a response longer than the original content, allowed compression of non-200 responses, and added tests (there were none). Thanks cannona for the initial patch.

git-svn-id: http://code.djangoproject.com/svn/django/trunk@17365 bcc190cf-cafb-0310-a4f2-bffc1f526a37
This commit is contained in:
Aymeric Augustin 2012-01-09 21:42:03 +00:00
parent 62766f4248
commit 4288c8831b
3 changed files with 109 additions and 10 deletions

View File

@ -12,8 +12,8 @@ class GZipMiddleware(object):
on the Accept-Encoding header. on the Accept-Encoding header.
""" """
def process_response(self, request, response): def process_response(self, request, response):
# It's not worth compressing non-OK or really short responses. # It's not worth attempting to compress really short responses.
if response.status_code != 200 or len(response.content) < 200: if len(response.content) < 200:
return response return response
patch_vary_headers(response, ('Accept-Encoding',)) patch_vary_headers(response, ('Accept-Encoding',))
@ -32,7 +32,12 @@ class GZipMiddleware(object):
if not re_accepts_gzip.search(ae): if not re_accepts_gzip.search(ae):
return response return response
response.content = compress_string(response.content) # Return the compressed content only if it's actually shorter.
compressed_content = compress_string(response.content)
if len(compressed_content) >= len(response.content):
return response
response.content = compressed_content
response['Content-Encoding'] = 'gzip' response['Content-Encoding'] = 'gzip'
response['Content-Length'] = str(len(response.content)) response['Content-Length'] = str(len(response.content))
return response return response

View File

@ -82,22 +82,29 @@ addresses defined in the :setting:`INTERNAL_IPS` setting. This is used by
Django's :doc:`automatic documentation system </ref/contrib/admin/admindocs>`. Django's :doc:`automatic documentation system </ref/contrib/admin/admindocs>`.
Depends on :class:`~django.contrib.auth.middleware.AuthenticationMiddleware`. Depends on :class:`~django.contrib.auth.middleware.AuthenticationMiddleware`.
GZIP middleware GZip middleware
--------------- ---------------
.. module:: django.middleware.gzip .. module:: django.middleware.gzip
:synopsis: Middleware to serve gziped content for performance. :synopsis: Middleware to serve GZipped content for performance.
.. class:: GZipMiddleware .. class:: GZipMiddleware
Compresses content for browsers that understand gzip compression (all modern Compresses content for browsers that understand GZip compression (all modern
browsers). browsers).
It is suggested to place this first in the middleware list, so that the It is suggested to place this first in the middleware list, so that the
compression of the response content is the last thing that happens. Will not compression of the response content is the last thing that happens.
compress content bodies less than 200 bytes long, when the response code is
something other than 200, JavaScript files (for IE compatibility), or It will not compress content bodies less than 200 bytes long, when the
responses that have the ``Content-Encoding`` header already specified. ``Content-Encoding`` header is already set, or when the browser does not send
an ``Accept-Encoding`` header containing ``gzip``.
Content will also not be compressed when the browser is Internet Explorer and
the ``Content-Type`` header contains ``javascript`` or starts with anything
other than ``text/``. This is done to overcome a bug present in early versions
of Internet Explorer which caused decompression not to be performed on certain
content types.
GZip compression can be applied to individual views using the GZip compression can be applied to individual views using the
:func:`~django.views.decorators.http.gzip_page()` decorator. :func:`~django.views.decorators.http.gzip_page()` decorator.

View File

@ -1,6 +1,9 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
import gzip
import re import re
import random
import StringIO
from django.conf import settings from django.conf import settings
from django.core import mail from django.core import mail
@ -9,6 +12,7 @@ from django.http import HttpResponse
from django.middleware.clickjacking import XFrameOptionsMiddleware from django.middleware.clickjacking import XFrameOptionsMiddleware
from django.middleware.common import CommonMiddleware from django.middleware.common import CommonMiddleware
from django.middleware.http import ConditionalGetMiddleware from django.middleware.http import ConditionalGetMiddleware
from django.middleware.gzip import GZipMiddleware
from django.test import TestCase from django.test import TestCase
@ -495,3 +499,86 @@ class XFrameOptionsMiddlewareTest(TestCase):
r = OtherXFrameOptionsMiddleware().process_response(HttpRequest(), r = OtherXFrameOptionsMiddleware().process_response(HttpRequest(),
HttpResponse()) HttpResponse())
self.assertEqual(r['X-Frame-Options'], 'DENY') self.assertEqual(r['X-Frame-Options'], 'DENY')
class GZipMiddlewareTest(TestCase):
"""
Tests the GZip middleware.
"""
short_string = "This string is too short to be worth compressing."
compressible_string = 'a' * 500
uncompressible_string = ''.join(chr(random.randint(0, 255)) for _ in xrange(500))
def setUp(self):
self.req = HttpRequest()
self.req.META = {
'SERVER_NAME': 'testserver',
'SERVER_PORT': 80,
}
self.req.path = self.req.path_info = "/"
self.req.META['HTTP_ACCEPT_ENCODING'] = 'gzip, deflate'
self.req.META['HTTP_USER_AGENT'] = 'Mozilla/5.0 (Windows NT 5.1; rv:9.0.1) Gecko/20100101 Firefox/9.0.1'
self.resp = HttpResponse()
self.resp.status_code = 200
self.resp.content = self.compressible_string
self.resp['Content-Type'] = 'text/html; charset=UTF-8'
@staticmethod
def decompress(gzipped_string):
return gzip.GzipFile(mode='rb', fileobj=StringIO.StringIO(gzipped_string)).read()
def test_compress_response(self):
"""
Tests that compression is performed on responses with compressible content.
"""
r = GZipMiddleware().process_response(self.req, self.resp)
self.assertEqual(self.decompress(r.content), self.compressible_string)
self.assertEqual(r.get('Content-Encoding'), 'gzip')
self.assertEqual(r.get('Content-Length'), str(len(r.content)))
def test_compress_non_200_response(self):
"""
Tests that compression is performed on responses with a status other than 200.
See #10762.
"""
self.resp.status_code = 404
r = GZipMiddleware().process_response(self.req, self.resp)
self.assertEqual(self.decompress(r.content), self.compressible_string)
self.assertEqual(r.get('Content-Encoding'), 'gzip')
def test_no_compress_short_response(self):
"""
Tests that compression isn't performed on responses with short content.
"""
self.resp.content = self.short_string
r = GZipMiddleware().process_response(self.req, self.resp)
self.assertEqual(r.content, self.short_string)
self.assertEqual(r.get('Content-Encoding'), None)
def test_no_compress_compressed_response(self):
"""
Tests that compression isn't performed on responses that are already compressed.
"""
self.resp['Content-Encoding'] = 'deflate'
r = GZipMiddleware().process_response(self.req, self.resp)
self.assertEqual(r.content, self.compressible_string)
self.assertEqual(r.get('Content-Encoding'), 'deflate')
def test_no_compress_ie_js_requests(self):
"""
Tests that compression isn't performed on JavaScript requests from Internet Explorer.
"""
self.req.META['HTTP_USER_AGENT'] = 'Mozilla/4.0 (compatible; MSIE 5.00; Windows 98)'
self.resp['Content-Type'] = 'application/javascript; charset=UTF-8'
r = GZipMiddleware().process_response(self.req, self.resp)
self.assertEqual(r.content, self.compressible_string)
self.assertEqual(r.get('Content-Encoding'), None)
def test_no_compress_uncompressible_response(self):
"""
Tests that compression isn't performed on responses with uncompressible content.
"""
self.resp.content = self.uncompressible_string
r = GZipMiddleware().process_response(self.req, self.resp)
self.assertEqual(r.content, self.uncompressible_string)
self.assertEqual(r.get('Content-Encoding'), None)