gzipper.py 3.5 KB

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