| 1 |
import struct |
|---|
| 2 |
import time |
|---|
| 3 |
import zlib |
|---|
| 4 |
|
|---|
| 5 |
import cherrypy |
|---|
| 6 |
from basefilter import BaseFilter |
|---|
| 7 |
|
|---|
| 8 |
class GzipFilter(BaseFilter): |
|---|
| 9 |
"""Filter that gzips the response.""" |
|---|
| 10 |
|
|---|
| 11 |
def before_finalize(self): |
|---|
| 12 |
if not cherrypy.config.get('gzip_filter.on', False): |
|---|
| 13 |
return |
|---|
| 14 |
|
|---|
| 15 |
response = cherrypy.response |
|---|
| 16 |
if not response.body: |
|---|
| 17 |
|
|---|
| 18 |
return |
|---|
| 19 |
|
|---|
| 20 |
def zipit(): |
|---|
| 21 |
|
|---|
| 22 |
varies = response.headers.get("Vary", "") |
|---|
| 23 |
varies = [x.strip() for x in varies.split(",") if x.strip()] |
|---|
| 24 |
if "Accept-Encoding" not in varies: |
|---|
| 25 |
varies.append("Accept-Encoding") |
|---|
| 26 |
response.headers['Vary'] = ", ".join(varies) |
|---|
| 27 |
|
|---|
| 28 |
response.headers['Content-Encoding'] = 'gzip' |
|---|
| 29 |
level = cherrypy.config.get('gzip_filter.compresslevel', 9) |
|---|
| 30 |
response.body = self.zip_body(response.body, level) |
|---|
| 31 |
|
|---|
| 32 |
response.headers.pop("Content-Length", None) |
|---|
| 33 |
|
|---|
| 34 |
acceptable = cherrypy.request.headers.elements('Accept-Encoding') |
|---|
| 35 |
if not acceptable: |
|---|
| 36 |
|
|---|
| 37 |
|
|---|
| 38 |
|
|---|
| 39 |
|
|---|
| 40 |
|
|---|
| 41 |
|
|---|
| 42 |
|
|---|
| 43 |
return |
|---|
| 44 |
|
|---|
| 45 |
ct = response.headers.get('Content-Type').split(';')[0] |
|---|
| 46 |
ct = ct in cherrypy.config.get('gzip_filter.mime_types', ['text/html', 'text/plain']) |
|---|
| 47 |
for coding in acceptable: |
|---|
| 48 |
if coding.value == 'identity' and coding.qvalue != 0: |
|---|
| 49 |
return |
|---|
| 50 |
if coding.value in ('gzip', 'x-gzip'): |
|---|
| 51 |
if coding.qvalue == 0: |
|---|
| 52 |
return |
|---|
| 53 |
if ct: |
|---|
| 54 |
zipit() |
|---|
| 55 |
return |
|---|
| 56 |
cherrypy.HTTPError(406, "identity, gzip").set_response() |
|---|
| 57 |
|
|---|
| 58 |
def write_gzip_header(self): |
|---|
| 59 |
"""Adapted from the gzip.py standard module code""" |
|---|
| 60 |
|
|---|
| 61 |
header = '\037\213' |
|---|
| 62 |
header += '\010' |
|---|
| 63 |
header += '\0' |
|---|
| 64 |
header += struct.pack("<L", long(time.time())) |
|---|
| 65 |
header += '\002' |
|---|
| 66 |
header += '\377' |
|---|
| 67 |
return header |
|---|
| 68 |
|
|---|
| 69 |
def write_gzip_trailer(self, crc, size): |
|---|
| 70 |
footer = struct.pack("<l", crc) |
|---|
| 71 |
footer += struct.pack("<L", size & 0xFFFFFFFFL) |
|---|
| 72 |
return footer |
|---|
| 73 |
|
|---|
| 74 |
def zip_body(self, body, compress_level): |
|---|
| 75 |
|
|---|
| 76 |
yield self.write_gzip_header() |
|---|
| 77 |
crc = zlib.crc32("") |
|---|
| 78 |
size = 0 |
|---|
| 79 |
zobj = zlib.compressobj(compress_level, |
|---|
| 80 |
zlib.DEFLATED, -zlib.MAX_WBITS, |
|---|
| 81 |
zlib.DEF_MEM_LEVEL, 0) |
|---|
| 82 |
for line in body: |
|---|
| 83 |
size += len(line) |
|---|
| 84 |
crc = zlib.crc32(line, crc) |
|---|
| 85 |
yield zobj.compress(line) |
|---|
| 86 |
yield zobj.flush() |
|---|
| 87 |
yield self.write_gzip_trailer(crc, size) |
|---|