Source code for pulsar.apps.wsgi.response
'''
.. _wsgi-response-middleware:
Response middleware are callable objects which can be used in conjunction
with pulsar :ref:`WsgiHandler <wsgi-handler>`.
They must return a :ref:`WsgiResponse <wsgi-response>` which can be the
same as the one passed to the callable or a brand new one.
Interface
=================
.. autoclass:: ResponseMiddleware
:members:
:member-order: bysource
GZip Middleware
=================
.. autoclass:: GZipMiddleware
:members:
:member-order: bysource
AccessControl
=================
.. autoclass:: AccessControl
:members:
:member-order: bysource
'''
import re
from gzip import GzipFile
from pulsar.utils.httpurl import BytesIO
re_accepts_gzip = re.compile(r'\bgzip\b')
re_media_type = re.compile(r'^(image|audio|video)/.+')
[docs]class ResponseMiddleware:
'''Base class for response middlewares.
A response middleware is used by a :ref:`WsgiHandler <wsgi-handler>`,
it is a callable used to manipulate a :ref:`WsgiResponse <wsgi-response>`.
The focus of this class is the :meth:`execute` method where
the middleware logic is implemented.
'''
def version(self, environ):
return environ.get('wsgi.version')
[docs] def available(self, environ, response):
'''Check if this :class:`ResponseMiddleware` can be applied to
the ``response`` object.
:param environ: a WSGI environ dictionary.
:param response: a :class:`pulsar.apps.wsgi.wrappers.WsgiResponse`
:return: ``True`` or ``False``.
'''
return True
def __call__(self, environ, response):
if not self.available(environ, response):
return response
resp = self.execute(environ, response)
return resp if resp is not None else response
[docs] def execute(self, environ, response):
'''Manipulate *response*, called only if the :meth:`available`
method returns ``True``.'''
pass
[docs]class AccessControl(ResponseMiddleware):
'''A response middleware which add the ``Access-Control-Allow-Origin``
response header.
'''
def __init__(self, origin='*', methods=None):
self.origin = origin
self.methods = methods
def available(self, environ, response):
return response.status_code == 200
def execute(self, environ, response):
response.headers['Access-Control-Allow-Origin'] = self.origin
if self.methods:
response.headers['Access-Control-Allow-Methods'] = self.methods
[docs]class GZipMiddleware(ResponseMiddleware):
"""A :class:`ResponseMiddleware` for compressing content if the request
allows gzip compression. It sets the Vary header accordingly.
The compression implementation is from
http://jython.xhaus.com/http-compression-in-python-and-jython
"""
def __init__(self, min_length=200):
self.min_length = min_length
def available(self, environ, response):
# It's not worth compressing non-OK or really short responses
try:
if response.status_code == 200 and not response.is_streamed:
if response.length() < self.min_length:
return False
headers = response.headers
ctype = headers.get('Content-Type', '').lower()
# Avoid gzipping if we've already got a content-encoding.
if 'Content-Encoding' in headers:
return False
# MSIE have issues with gzipped response of various
# content types.
if "msie" in environ.get('HTTP_USER_AGENT', '').lower():
if not ctype.startswith("text/") or "javascript" in ctype:
return False
ae = environ.get('HTTP_ACCEPT_ENCODING', '')
if not re_accepts_gzip.search(ae):
return False
if re_media_type.match(ctype):
return False
return True
except Exception:
raise
def execute(self, environ, response):
headers = response.headers
headers.add_header('Vary', 'Accept-Encoding')
content = b''.join(response.content)
response.content = (self.compress_string(content),)
response.headers['Content-Encoding'] = 'gzip'
def compress_string(self, s):
zbuf = BytesIO()
zfile = GzipFile(mode='wb', compresslevel=6, fileobj=zbuf)
zfile.write(s)
zfile.close()
return zbuf.getvalue()