zoom.middleware module

zoom.middleware

A set of functions that can be placed between the HTTP server and the application layer. These functions can provide various services such as content serving, caching, error trapping, security, etc..

zoom.middleware.capture_stdout(request, handler, *rest)

Capture printed output for debugging purposes

>>> request = zoom.request.build('http://localhost')
>>> def get_content(request):
...     print('hey')
...     return zoom.response.HTMLResponse('I said {*stdout*}')
>>> response = capture_stdout(request, get_content)
>>> response.content
'I said hey\n'
zoom.middleware.check_csrf(request, handler, *rest)

Check csrf token

>>> zoom.system.providers = []
>>> data = dict(csrf_token='1234', name='Pat')
>>> request = zoom.request.build('http://localhost/', data)
>>> request.session = zoom.utils.Bunch()
>>> request.site = zoom.sites.Site()
>>> result = check_csrf(request, lambda a: False)
>>> isinstance(result, zoom.response.RedirectResponse)
True
>>> data = dict(csrf_token='1234', name='Pat')
>>> request = zoom.request.build('http://localhost/', data)
>>> request.session = zoom.utils.Bunch(csrf_token='4321')
>>> request.site = zoom.sites.Site()
>>> result = check_csrf(request, lambda a: False)
>>> isinstance(result, zoom.response.RedirectResponse)
True
>>> data = dict(csrf_token='1234', name='Pat')
>>> request = zoom.request.build('http://localhost/', data)
>>> request.session = zoom.utils.Bunch(csrf_token='1234')
>>> request.site = zoom.sites.Site()
>>> result = check_csrf(request, lambda a: False)
>>> isinstance(result, zoom.response.RedirectResponse)
False
zoom.middleware.debug(request)

Debugging page

>>> type(debug(zoom.request.build('http://localhost/')))
<class 'zoom.response.HTMLResponse'>
zoom.middleware.display_errors(request, handler, *rest)

Display errors for developers

>>> request = zoom.request.build('http://localhost')
>>> request.app = zoom.utils.Bunch(theme='default', templates_paths=[])
>>> request.host = 'localhost'
>>> request.site = zoom.sites.Site()
>>> request.site.theme = 'default'
>>> request.site.request = request
>>> zoom.system.request = request
>>> zoom.system.site = request.site
>>> zoom.system.user = request.site.users.first(username='admin')
>>> zoom.system.user.is_admin = True
>>> def throw(request):
...     raise Exception('ouch!')
>>> response = display_errors(request, throw)
>>> isinstance(response, HTMLResponse)
True
>>> zoom.system.user.is_admin = False
>>> response = display_errors(request, throw)
>>> isinstance(response, HTMLResponse)
True
zoom.middleware.get_csrf_token(session)

generate a csrf token

>>> zoom.system.request.session = session = zoom.utils.Bunch()
>>> hasattr(session, 'csrf_token')
False
>>> bool(get_csrf_token(session))
True
>>> hasattr(session, 'csrf_token')
False
>>> _ = zoom.forms.form_for('test')
>>> hasattr(session, 'csrf_token')
True
zoom.middleware.handle(request, handlers=None)

handle a request

zoom.middleware.not_found(request)

return a 404 page for site

>>> zoom.system.site = site = zoom.sites.Site()
>>> site.theme = 'default'
>>> site.title = 'My Site'
>>> request = zoom.request.build('http://localhost')
>>> request.app = zoom.utils.Bunch(theme='default')
>>> request.site = site
>>> response = not_found(request)
>>> response.status
'404 Not Found'
zoom.middleware.reset_modules(request, handler, *rest)

reset the modules to a known starting set

Memorizes the modules in use on the first round. Then on every subsequent round, it removes any extra modules before passing the request on.

>>> def loader(request):
...     import zoom.audit
>>> def not_a_loader(request):
...     pass
>>> if 'zoom.audit' in sys.modules:
...     del sys.modules['zoom.audit']
>>> fresh = list(sys.modules)
>>> reset_modules(zoom.request.build('http://localhost'), loader)
>>> fresh == list(sys.modules)
False
>>> reset_modules(zoom.request.build('http://localhost'), not_a_loader)
>>> fresh == list(sys.modules)
True
zoom.middleware.serve_favicon(request, handler, *rest)

Serve a favicon file

This function only handles the case where the favicon reference is /favicon.ico, as would be typical in a local dev environment. For production environments the favicon would typically be either part of the theme or would be stored statically, both of which would typically be served up by your proxy or your web server depending on your config.

If your web sever is not configured to handle static or themes for you, Zoom will happily serve them up via the serve_theme or serve_static middleware found elsewhere in this module.

>>> from zoom.request import Request
>>> def content_handler(request, *rest):
...     return '200 OK', [], 'nuthin'
>>> request = Request(
...     dict(REQUEST_URI='/favicon.ico'),
... )
>>> response = serve_favicon(
...     request,
...     content_handler,
... )
>>> isinstance(response, ICOResponse)
True
>>> request = Request(
...     dict(REQUEST_URI='/'),
... )
>>> status, _, content = serve_favicon(
...     request,
...     content_handler,
... )
>>> content
'nuthin'
zoom.middleware.serve_html(request, handler, *rest)

Serve HTML from the Content app

Direct a request for an HTML page to the content app

>>> site = zoom.sites.Site()
>>> url = 'http://localhost/index.html'
>>> request = zoom.request.build(url)
>>> request.path == '/index.html'
True
>>> response = serve_html(request, lambda a: None)
>>> request.path == '/content/index.html'
True
>>> url = 'http://localhost/index.css'
>>> request = zoom.request.build(url)
>>> request.path == '/index.css'
True
>>> response = serve_html(request, lambda a: None)
>>> request.path == '/index.css'
True
zoom.middleware.serve_images(request, handler, *rest)

Serve an image file

>>> url = 'http://localhost/images/banner_logo.png'
>>> request = zoom.request.build(url)
>>> request.site = zoom.sites.Site()
>>> result = serve_images(request, lambda a: False)
>>> isinstance(result, PNGResponse)
True
>>> url = 'http://localhost/notimages/banner_logo.png'
>>> request = zoom.request.build(url)
>>> serve_images(request, lambda a: False)
False
zoom.middleware.serve_redirects(request, handler, *rest)

Serves redirects

>>> request = zoom.request.build('http://localhost')
>>> result = serve_redirects(request, lambda a: False)
>>> print(result)
False
>>> request.site = zoom.utils.Bunch(abs_url='http://localhost')
>>> redirect_home = zoom.response.RedirectResponse('/home')
>>> result = serve_redirects(request, lambda a: redirect_home)
>>> print(result.headers)
OrderedDict([('Location', '/home')])
zoom.middleware.serve_response(*path)

Serve up various respones with their correct response type

>>> zoom_js = zoom.tools.zoompath('web/www/static/zoom/zoom.js')
>>> response = serve_response(zoom_js)
>>> isinstance(response, JavascriptResponse)
True
>>> zoom_path = zoom.tools.zoompath('web')
>>> response = serve_response(zoom_path, 'www/static/zoom/nada.js')
>>> isinstance(response, JavascriptResponse)
False
>>> response.content
"file not found: 'www/static/zoom/nada.js'"
>>> response.status
'404 Not Found'
>>> zoom_path = zoom.tools.zoompath('web')
>>> response = serve_response(zoom_path, 'www/static/zoom/images')
>>> isinstance(response, JavascriptResponse)
False
>>> response.content
"unknown file type ''"
>>> response.status
'415 Unsupported Media Type'
zoom.middleware.serve_static(request, handler, *rest)

Serve a static file

Static files can be served in serveral ways. This particular middleware intended to be inserted into the middleware stack near the front before most other things. The reason being is that in many cases the static files are not site specific or app specific but rather are shared by the entire instance. In addition, many times static files require no authorization or special rights. This means that in many cases static files can be served without knowing who is asking or what site they are for. That is what this middleware does.

This handler is mainly intended for development purposes as in a production evironment this type of content would typically be served up by the HTTP server or a caching layer.

Note: This layer looks in four separate places for static files to serve. Three related to the zoom instance, and one, as a last resort, from the zoom library itself. If you are concerned about exposing the wrong files, be careful what you put in those directories or better yet, use a proxy.

In addition to this instance wide static serving Sites and Apps may implement static file serving. If they do, this layer is unaware of that fact.

The locations this handler will look are relative to the zoom instance. They are:

<instance>/static <instance>/www/static <instance>/../static zoom/web/www/static
>>> url = 'http://localhost/static/zoom/zoom.js'
>>> request = zoom.request.build(url)
>>> result = serve_static(request, lambda a: False)
>>> isinstance(result, JavascriptResponse)
True
>>> url = 'http://localhost/notstatic/zoom/zoom.js'
>>> request = zoom.request.build(url)
>>> result = serve_static(request, lambda a: False)
>>> isinstance(result, JavascriptResponse)
False
zoom.middleware.serve_themes(request, handler, *rest)

Serve a theme file

>>> url = 'http://localhost/themes/default/css/style.css'
>>> request = zoom.request.build(url)
>>> request.site = zoom.sites.Site()
>>> result = serve_themes(request, lambda a: False)
>>> isinstance(result, CSSResponse)
True
>>> url = 'http://localhost/notthemes/default/default.html'
>>> request = zoom.request.build(url)
>>> serve_themes(request, lambda a: False)
False
zoom.middleware.trap_errors(request, handler, *rest)

Trap exceptions and raise a server error

>>> def exception_handler(request, *rest):
...     raise Exception('error!')
>>> def content_handler(request, *rest):
...     return HTMLResponse('nuthin')
>>> request = {}
>>> response = trap_errors(request, content_handler)
>>> status, headers, content = response.as_wsgi()
>>> content
b'nuthin'
>>> status
'200 OK'
>>> response = trap_errors(request, exception_handler)
>>> status, headers, content = response.as_wsgi()
>>> status
'500 Internal Server Error'
>>> 'Exception: error!' in str(content)
True