1 | # (c) 2005 Ian Bicking and contributors; written for Paste (http://pythonpaste.org) |
---|
2 | # Licensed under the MIT license: http://www.opensource.org/licenses/mit-license.php |
---|
3 | |
---|
4 | # (c) 2005 Ian Bicking and contributors; written for Paste (http://pythonpaste.org) |
---|
5 | # Licensed under the MIT license: http://www.opensource.org/licenses/mit-license.php |
---|
6 | |
---|
7 | """ |
---|
8 | WSGI middleware |
---|
9 | |
---|
10 | Gzip-encodes the response. |
---|
11 | """ |
---|
12 | |
---|
13 | import gzip |
---|
14 | from paste.response import header_value, remove_header |
---|
15 | from paste.httpheaders import CONTENT_LENGTH |
---|
16 | |
---|
17 | try: |
---|
18 | from cStringIO import StringIO |
---|
19 | except ImportError: |
---|
20 | from StringIO import StringIO |
---|
21 | |
---|
22 | class GzipOutput(object): |
---|
23 | pass |
---|
24 | |
---|
25 | class middleware(object): |
---|
26 | |
---|
27 | def __init__(self, application, compress_level=6): |
---|
28 | self.application = application |
---|
29 | self.compress_level = int(compress_level) |
---|
30 | |
---|
31 | def __call__(self, environ, start_response): |
---|
32 | if 'gzip' not in environ.get('HTTP_ACCEPT_ENCODING', ''): |
---|
33 | # nothing for us to do, so this middleware will |
---|
34 | # be a no-op: |
---|
35 | return self.application(environ, start_response) |
---|
36 | response = GzipResponse(start_response, self.compress_level) |
---|
37 | app_iter = self.application(environ, |
---|
38 | response.gzip_start_response) |
---|
39 | if app_iter: |
---|
40 | response.finish_response(app_iter) |
---|
41 | |
---|
42 | return response.write() |
---|
43 | |
---|
44 | class GzipResponse(object): |
---|
45 | |
---|
46 | def __init__(self, start_response, compress_level): |
---|
47 | self.start_response = start_response |
---|
48 | self.compress_level = compress_level |
---|
49 | self.buffer = StringIO() |
---|
50 | self.compressible = False |
---|
51 | self.content_length = None |
---|
52 | |
---|
53 | def gzip_start_response(self, status, headers, exc_info=None): |
---|
54 | self.headers = headers |
---|
55 | ct = header_value(headers,'content-type') |
---|
56 | ce = header_value(headers,'content-encoding') |
---|
57 | self.compressible = False |
---|
58 | if ct and (ct.startswith('text/') or ct.startswith('application/')) \ |
---|
59 | and 'zip' not in ct: |
---|
60 | self.compressible = True |
---|
61 | if ce: |
---|
62 | self.compressible = False |
---|
63 | if self.compressible: |
---|
64 | headers.append(('content-encoding', 'gzip')) |
---|
65 | remove_header(headers, 'content-length') |
---|
66 | self.headers = headers |
---|
67 | self.status = status |
---|
68 | return self.buffer.write |
---|
69 | |
---|
70 | def write(self): |
---|
71 | out = self.buffer |
---|
72 | out.seek(0) |
---|
73 | s = out.getvalue() |
---|
74 | out.close() |
---|
75 | return [s] |
---|
76 | |
---|
77 | def finish_response(self, app_iter): |
---|
78 | if self.compressible: |
---|
79 | output = gzip.GzipFile(mode='wb', compresslevel=self.compress_level, |
---|
80 | fileobj=self.buffer) |
---|
81 | else: |
---|
82 | output = self.buffer |
---|
83 | try: |
---|
84 | for s in app_iter: |
---|
85 | output.write(s) |
---|
86 | if self.compressible: |
---|
87 | output.close() |
---|
88 | finally: |
---|
89 | if hasattr(app_iter, 'close'): |
---|
90 | app_iter.close() |
---|
91 | content_length = self.buffer.tell() |
---|
92 | CONTENT_LENGTH.update(self.headers, content_length) |
---|
93 | self.start_response(self.status, self.headers) |
---|
94 | |
---|
95 | def filter_factory(application, **conf): |
---|
96 | import warnings |
---|
97 | warnings.warn( |
---|
98 | 'This function is deprecated; use make_gzip_middleware instead', |
---|
99 | DeprecationWarning, 2) |
---|
100 | def filter(application): |
---|
101 | return middleware(application) |
---|
102 | return filter |
---|
103 | |
---|
104 | def make_gzip_middleware(app, global_conf, compress_level=6): |
---|
105 | """ |
---|
106 | Wrap the middleware, so that it applies gzipping to a response |
---|
107 | when it is supported by the browser and the content is of |
---|
108 | type ``text/*`` or ``application/*`` |
---|
109 | """ |
---|
110 | compress_level = int(compress_level) |
---|
111 | return middleware(app, compress_level=compress_level) |
---|