summaryrefslogtreecommitdiff
path: root/flup/middleware/gzip.py
diff options
context:
space:
mode:
Diffstat (limited to 'flup/middleware/gzip.py')
-rw-r--r--flup/middleware/gzip.py279
1 files changed, 0 insertions, 279 deletions
diff --git a/flup/middleware/gzip.py b/flup/middleware/gzip.py
deleted file mode 100644
index 59a800d..0000000
--- a/flup/middleware/gzip.py
+++ /dev/null
@@ -1,279 +0,0 @@
-# Copyright (c) 2005 Allan Saddi <allan@saddi.com>
-# All rights reserved.
-#
-# Redistribution and use in source and binary forms, with or without
-# modification, are permitted provided that the following conditions
-# are met:
-# 1. Redistributions of source code must retain the above copyright
-# notice, this list of conditions and the following disclaimer.
-# 2. Redistributions in binary form must reproduce the above copyright
-# notice, this list of conditions and the following disclaimer in the
-# documentation and/or other materials provided with the distribution.
-#
-# THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
-# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
-# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
-# ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
-# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
-# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
-# OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
-# HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
-# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
-# OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
-# SUCH DAMAGE.
-#
-# $Id$
-
-"""WSGI response gzipper middleware.
-
-This gzip middleware component differentiates itself from others in that
-it (hopefully) follows the spec more closely. Namely with regard to the
-application iterator and buffering. (It doesn't buffer.) See
-`Middleware Handling of Block Boundaries`_.
-
-Of course this all comes with a price... just LOOK at this mess! :)
-
-The inner workings of gzip and the gzip file format were gleaned from gzip.py.
-
-.. _Middleware Handling of Block Boundaries: http://www.python.org/dev/peps/pep-0333/#middleware-handling-of-block-boundaries
-"""
-
-__author__ = 'Allan Saddi <allan@saddi.com>'
-__version__ = '$Revision$'
-
-
-import struct
-import time
-import zlib
-import re
-
-
-__all__ = ['GzipMiddleware']
-
-
-def _gzip_header():
- """Returns a gzip header (with no filename)."""
- # See GzipFile._write_gzip_header in gzip.py
- return '\037\213' \
- '\010' \
- '\0' + \
- struct.pack('<L', long(time.time())) + \
- '\002' \
- '\377'
-
-
-class _GzipIterWrapper(object):
- """gzip application iterator wrapper.
-
- It ensures that: the application iterator's ``close`` method (if any) is
- called by the parent server; and at least one value is yielded each time
- the application's iterator yields a value.
-
- If the application's iterator yields N values, this iterator will yield
- N+1 values. This is to account for the gzip trailer.
- """
-
- def __init__(self, app_iter, gzip_middleware):
- self._g = gzip_middleware
- self._next = iter(app_iter).next
-
- self._last = False # True if app_iter has yielded last value.
- self._trailer_sent = False
-
- if hasattr(app_iter, 'close'):
- self.close = app_iter.close
-
- def __iter__(self):
- return self
-
- # This would've been a lot easier had I used a generator. But then I'd have
- # to wrap the generator anyway to ensure that any existing close() method
- # was called. (Calling it within the generator is not the same thing,
- # namely it does not ensure that it will be called no matter what!)
- def next(self):
- if not self._last:
- # Need to catch StopIteration here so we can append trailer.
- try:
- data = self._next()
- except StopIteration:
- self._last = True
-
- if not self._last:
- if self._g.gzip_ok:
- return self._g.gzip_data(data)
- else:
- return data
- else:
- # See if trailer needs to be sent.
- if self._g.header_sent and not self._trailer_sent:
- self._trailer_sent = True
- return self._g.gzip_trailer()
- # Otherwise, that's the end of this iterator.
- raise StopIteration
-
-
-class _GzipMiddleware(object):
- """The actual gzip middleware component.
-
- Holds compression state as well implementations of ``start_response`` and
- ``write``. Instantiated before each call to the underlying application.
-
- This class is private. See ``GzipMiddleware`` for the public interface.
- """
-
- def __init__(self, start_response, mime_types, compress_level):
- self._start_response = start_response
- self._mime_types = mime_types
-
- self.gzip_ok = False
- self.header_sent = False
-
- # See GzipFile.__init__ and GzipFile._init_write in gzip.py
- self._crc = zlib.crc32('')
- self._size = 0
- self._compress = zlib.compressobj(compress_level,
- zlib.DEFLATED,
- -zlib.MAX_WBITS,
- zlib.DEF_MEM_LEVEL,
- 0)
-
- def gzip_data(self, data):
- """Compresses the given data, prepending the gzip header if necessary.
-
- Returns the result as a string.
- """
- if not self.header_sent:
- self.header_sent = True
- out = _gzip_header()
- else:
- out = ''
-
- # See GzipFile.write in gzip.py
- length = len(data)
- if length > 0:
- self._size += length
- self._crc = zlib.crc32(data, self._crc)
- out += self._compress.compress(data)
- return out
-
- def gzip_trailer(self):
- """Returns the gzip trailer."""
- # See GzipFile.close in gzip.py
- return self._compress.flush() + \
- struct.pack('<l', self._crc) + \
- struct.pack('<L', self._size & 0xffffffffL)
-
- def start_response(self, status, headers, exc_info=None):
- """WSGI ``start_response`` implementation."""
- self.gzip_ok = False
-
- # Scan the headers. Only allow gzip compression if the Content-Type
- # is one that we're flagged to compress AND the headers do not
- # already contain Content-Encoding.
- for name,value in headers:
- name = name.lower()
- if name == 'content-type':
- value = value.split(';')[0].strip()
- for p in self._mime_types:
- if p.match(value) is not None:
- self.gzip_ok = True
- break # NB: Breaks inner loop only
- elif name == 'content-encoding':
- self.gzip_ok = False
- break
-
- if self.gzip_ok:
- # Remove Content-Length, if present, because compression will
- # most surely change it. (And unfortunately, we can't predict
- # the final size...)
- headers = [(name,value) for name,value in headers
- if name.lower() != 'content-length']
- headers.append(('Content-Encoding', 'gzip'))
-
- _write = self._start_response(status, headers, exc_info)
-
- if self.gzip_ok:
- def write_gzip(data):
- _write(self.gzip_data(data))
- return write_gzip
- else:
- return _write
-
-
-class GzipMiddleware(object):
- """WSGI middleware component that gzip compresses the application's
- response (if the client supports gzip compression - gleaned from the
- ``Accept-Encoding`` request header).
- """
-
- def __init__(self, application, mime_types=None, compress_level=9):
- """Initializes this GzipMiddleware.
-
- ``mime_types``
- A list of Content-Types that are OK to compress. Regular
- expressions are accepted. Defaults to ``[text/.*]`` if not
- specified.
-
- ``compress_level``
- The gzip compression level, an integer from 1 to 9; 1 is the
- fastest and produces the least compression, and 9 is the slowest,
- producing the most compression. The default is 9.
- """
- if mime_types is None:
- mime_types = ['text/', r'''application/(?:.+\+)?xml$''']
-
- self._application = application
- self._mime_types = [re.compile(m) for m in mime_types]
- self._compress_level = compress_level
-
- def __call__(self, environ, start_response):
- """WSGI application interface."""
- # If the client doesn't support gzip encoding, just pass through
- # directly to the application.
- if 'gzip' not in environ.get('HTTP_ACCEPT_ENCODING', ''):
- return self._application(environ, start_response)
-
- # All of the work is done in _GzipMiddleware and _GzipIterWrapper.
- g = _GzipMiddleware(start_response, self._mime_types,
- self._compress_level)
-
- result = self._application(environ, g.start_response)
-
- # See if it's a length 1 iterable...
- try:
- shortcut = len(result) == 1
- except:
- shortcut = False
-
- if shortcut:
- # Special handling if application returns a length 1 iterable:
- # also return a length 1 iterable!
- try:
- i = iter(result)
- # Hmmm, if we get a StopIteration here, the application's
- # broken (__len__ lied!)
- data = i.next()
- if g.gzip_ok:
- return [g.gzip_data(data) + g.gzip_trailer()]
- else:
- return [data]
- finally:
- if hasattr(result, 'close'):
- result.close()
-
- return _GzipIterWrapper(result, g)
-
-
-if __name__ == '__main__':
- def app(environ, start_response):
- start_response('200 OK', [('Content-Type', 'text/html')])
- yield 'Hello World!\n'
-
- from wsgiref import validate
- app = validate.validator(app)
- app = GzipMiddleware(app)
- app = validate.validator(app)
-
- from flup.server.ajp import WSGIServer
- import logging
- WSGIServer(app, loggingLevel=logging.DEBUG).run()