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