| 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) |
|---|