Partially addresses #1095.
self.assertEqual(response.headers["Content-Encoding"], "gzip")
self.assertNotEqual(response.body, b"asdfqwer")
# Our test data gets bigger when gzipped. Oops. :)
+ # Chunked encoding bypasses the MIN_LENGTH check.
self.assertEqual(len(response.body), 34)
f = gzip.GzipFile(mode="r", fileobj=response.buffer)
self.assertEqual(f.read(), b"asdfqwer")
from tornado.testing import AsyncHTTPTestCase, ExpectLog, gen_test
from tornado.test.util import unittest
from tornado.util import u, ObjectDict, unicode_type, timedelta_to_seconds
-from tornado.web import RequestHandler, authenticated, Application, asynchronous, url, HTTPError, StaticFileHandler, _create_signature_v1, create_signed_value, decode_signed_value, ErrorHandler, UIModule, MissingArgumentError, stream_request_body, Finish, removeslash, addslash, RedirectHandler as WebRedirectHandler, get_signature_key_version
+from tornado.web import RequestHandler, authenticated, Application, asynchronous, url, HTTPError, StaticFileHandler, _create_signature_v1, create_signed_value, decode_signed_value, ErrorHandler, UIModule, MissingArgumentError, stream_request_body, Finish, removeslash, addslash, RedirectHandler as WebRedirectHandler, get_signature_key_version, GZipContentEncoding
import binascii
import contextlib
def get(self):
if self.get_argument('vary', None):
self.set_header('Vary', self.get_argument('vary'))
- self.write('hello world')
+ # Must write at least MIN_LENGTH bytes to activate compression.
+ self.write('hello world' + ('!' * GZipContentEncoding.MIN_LENGTH))
def get_app_kwargs(self):
return dict(
CONTENT_TYPES = set(["application/javascript", "application/x-javascript",
"application/xml", "application/atom+xml",
"application/json", "application/xhtml+xml"])
- MIN_LENGTH = 5
+ # Python's GzipFile defaults to level 9, while most other gzip
+ # tools (including gzip itself) default to 6, which is probably a
+ # better CPU/size tradeoff.
+ GZIP_LEVEL = 6
+ # Responses that are too short are unlikely to benefit from gzipping
+ # after considering the "Content-Encoding: gzip" header and the header
+ # inside the gzip encoding.
+ # Note that responses written in multiple chunks will be compressed
+ # regardless of size.
+ MIN_LENGTH = 1024
def __init__(self, request):
self._gzipping = "gzip" in request.headers.get("Accept-Encoding", "")
if self._gzipping:
headers["Content-Encoding"] = "gzip"
self._gzip_value = BytesIO()
- self._gzip_file = gzip.GzipFile(mode="w", fileobj=self._gzip_value)
+ self._gzip_file = gzip.GzipFile(mode="w", fileobj=self._gzip_value,
+ compresslevel=self.GZIP_LEVEL)
chunk = self.transform_chunk(chunk, finishing)
if "Content-Length" in headers:
# The original content length is no longer correct.
self._compressor = None
def _create_compressor(self):
- return zlib.compressobj(-1, zlib.DEFLATED, -self._max_wbits)
+ return zlib.compressobj(tornado.web.GZipContentEncoding.GZIP_LEVEL,
+ zlib.DEFLATED, -self._max_wbits)
def compress(self, data):
compressor = self._compressor or self._create_compressor()