Utility functions for WSGI applications
Return a middleware application that modifies the WSGI environment so that the X_FORWARDED_* headers are observed and generated URIs are correct in a proxied environment.
Use this whenever the WSGI application server is sitting behind Apache or another proxy server.
HTTP_X_FORWARDED_FOR is substituted for REMOTE_ADDR and HTTP_X_FORWARDED_HOST for SERVER_NAME. If HTTP_X_FORWARDED_SSL is set, then the wsgi.url_scheme is modified to https and HTTPS is set to on.
Example:
>>> from pesto.core import to_wsgi
>>> from pesto.testing import TestApp
>>> def app(request):
... return Response(["URL is ", request.request_uri, "; REMOTE_ADDR is ", request.remote_addr])
...
>>> app = TestApp(use_x_forwarded()(to_wsgi(app)))
>>> response = app.get('/',
... SERVER_NAME='wsgiserver-name',
... SERVER_PORT='8080',
... HTTP_HOST='wsgiserver-name:8080',
... REMOTE_ADDR='127.0.0.1',
... HTTP_X_FORWARDED_HOST='real-name:81',
... HTTP_X_FORWARDED_FOR='1.2.3.4'
... )
>>> response.body
'URL is http://real-name:81/; REMOTE_ADDR is 1.2.3.4'
>>> response = app.get('/',
... SERVER_NAME='wsgiserver-name',
... SERVER_PORT='8080',
... HTTP_HOST='wsgiserver-name:8080',
... REMOTE_ADDR='127.0.0.1',
... HTTP_X_FORWARDED_HOST='real-name:443',
... HTTP_X_FORWARDED_FOR='1.2.3.4',
... HTTP_X_FORWARDED_SSL='on'
... )
>>> response.body
'URL is https://real-name/; REMOTE_ADDR is 1.2.3.4'
In a non-forwarded environment, the environ dictionary will not be changed:
>>> response = app.get('/',
... SERVER_NAME='wsgiserver-name',
... SERVER_PORT='8080',
... HTTP_HOST='wsgiserver-name:8080',
... REMOTE_ADDR='127.0.0.1',
... )
>>> response.body
'URL is http://wsgiserver-name:8080/; REMOTE_ADDR is 127.0.0.1'
Create a composite application with different mount points.
Synopsis:
>>> def app1(e, sr):
... return [1]
...
>>> def app2(e, sr):
... return [2]
...
>>> app = mount_app({
... '/path/one' : app1,
... '/path/two' : app2,
... })
Replace the SCRIPT_NAME and QUERY_STRING WSGI environment variables with ones taken from Apache’s REDIRECT_URL and REDIRECT_QUERY_STRING environment variable, if set.
If an application is mounted as CGI and Apache RewriteRules are used to route requests, the SCRIPT_NAME and QUERY_STRING parts of the environment may not be meaningful for reconstructing URLs.
In this case Apache puts an extra key, REDIRECT_URL into the path which contains the full path as requested.
See also:
Example: assume a handler similar to the below has been made available at the address /cgi-bin/myhandler.cgi:
>>> from pesto import to_wsgi
>>> @to_wsgi
... def app(request):
... return Response(["My URL is " + request.request_uri])
...
Apache has been configured to redirect requests using the following RewriteRules in a .htaccess file in the server’s document root, or the equivalents in the apache configuration file:
RewriteEngine On
RewriteBase /
RewriteRule ^pineapple(.*)$ /cgi-bin/myhandler.cgi [PT]
The following code creates a simulation of the request headers Apache will pass to the application with the above rewrite rules. Without the middleware, the output will be as follows:
>>> from pesto.testing import TestApp
>>> TestApp(app).get(
... SERVER_NAME = 'example.com',
... REDIRECT_URL = '/pineapple/cake',
... SCRIPT_NAME = '/myhandler.cgi',
... PATH_INFO = '/cake',
... ).body
'My URL is http://example.com/myhandler.cgi/cake'
The use_redirect_url middleware will correctly set the SCRIPT_NAME and QUERY_STRING values:
>>> app = use_redirect_url()(app)
With this change the application will now output the correct values:
>>> TestApp(app).get(
... SERVER_NAME = 'example.com',
... REDIRECT_URL = '/pineapple/cake',
... SCRIPT_NAME = '/myhandler.cgi',
... PATH_INFO = '/cake',
... ).body
'My URL is http://example.com/pineapple/cake'
Return an absolute url from url, based on the current url.
Synopsis:
>>> from pesto.testing import make_environ
>>> environ = make_environ(wsgi_url_scheme='https', SERVER_NAME='example.com', SERVER_PORT='443', PATH_INFO='/foo')
>>> make_absolute_url(environ, '/bar')
'https://example.com/bar'
>>> make_absolute_url(environ, 'baz')
'https://example.com/foo/baz'
>>> make_absolute_url(environ, 'http://anotherhost/bar')
'http://anotherhost/bar'
Note that the URL is constructed using the PEP-333 URL reconstruction method (http://www.python.org/dev/peps/pep-0333/#url-reconstruction) and the returned URL is normalized:
>>> environ = make_environ(
... wsgi_url_scheme='https',
... SERVER_NAME='example.com',
... SERVER_PORT='443',
... SCRIPT_NAME='/colors',
... PATH_INFO='/red',
... )
>>> make_absolute_url(environ, 'blue')
'https://example.com/colors/red/blue'
>>> make_absolute_url(environ, '../blue')
'https://example.com/colors/blue'
>>> make_absolute_url(environ, 'blue/')
'https://example.com/colors/red/blue/'
Run application application as a CGI script
Turn a string into something suitable for a URI component.
Synopsis:
>>> import pesto.wsgiutils
>>> pesto.wsgiutils.make_uri_component(u"How now brown cow")
'how-now-brown-cow'
Unicode characters are mapped to ASCII equivalents where appropriate, and characters which would normally have to be escaped are translated into hyphens to ease readability of the generated URI.
Create a simple WSGI static file server application
Synopsis:
>>> from pesto.dispatch import dispatcher_app
>>> dispatcher = dispatcher_app()
>>> dispatcher.match('/static/<path:path>',
... GET=static_server('/docroot'),
... HEAD=static_server('/docroot')
... )
Serve a static file located at path. It is the responsibility of the caller to check that the path is valid and allowed.
Synopsis:
>>> from pesto.dispatch import dispatcher_app
>>> def view_important_document(request):
... return serve_static_file(request, '/path/to/very_important_document.pdf')
...
>>> def download_important_document(request):
... return serve_static_file(request, '/path/to/very_important_document.pdf').add_headers(
... content_disposition='attachment; filename=very_important_document.pdf'
... )
...
Example:
>>> uri_join('http://example.org/', 'http://example.com/')
'http://example.com/'
>>> uri_join('http://example.com/', '../styles/main.css')
'http://example.com/styles/main.css'
>>> uri_join('http://example.com/subdir/', '../styles/main.css')
'http://example.com/styles/main.css'
>>> uri_join('http://example.com/login', '?error=failed+auth')
'http://example.com/login?error=failed+auth'
>>> uri_join('http://example.com/login', 'register')
'http://example.com/register'
Return a query string formed from the given dictionary data.
Note that the pairs are separated using a semicolon, in accordance with the W3C recommendation
If no encoding is given, unicode values are encoded using the character set specified by pesto.DEFAULT_CHARSET.
Synopsis:
>>> # Basic usage
>>> make_query({ 'eat' : u'more cake', 'drink' : u'more tea' })
'drink=more+tea;eat=more+cake'
>>> # Use an ampersand as the separator
>>> make_query({ 'eat' : u'more cake', 'drink' : u'more tea' }, separator='&')
'drink=more+tea&eat=more+cake'
>>> # Can also be called using ``**kwargs`` style
>>> make_query(eat=u'more cake', drink=u'more tea')
'drink=more+tea;eat=more+cake'
>>> # Non-string values
>>> make_query(eat=[1, 2], drink=3.4)
'drink=3.4;eat=1;eat=2'
>>> # Multiple values per key
>>> make_query(eat=[u'more', u'cake'], drink=u'more tea')
'drink=more+tea;eat=more;eat=cake'
>>> # Anything with a value of ``None`` is excluded from the query
>>> make_query(x='1', y=None)
'x=1'
Function decorator to map request query/form arguments to function arguments.
Synopsis:
>>> from pesto.dispatch import dispatcher_app
>>> from pesto import to_wsgi
>>> from pesto.testing import TestApp
>>>
>>> dispatcher = dispatcher_app()
>>>
>>> @dispatcher.match('/recipes/<category:unicode>/view', 'GET')
... @with_request_args(id=int)
... def my_handler(request, category, id):
... return Response([
... u'Recipe #%d in category "%s".' % (id, category)
... ])
...
>>> print TestApp(dispatcher).get('/recipes/rat-stew/view', QUERY_STRING='id=2')
200 OK
Content-Type: text/html; charset=UTF-8
Recipe #2 in category "rat-stew".
If specified arguments are not present in the request (and no default value is given in the function signature), or a ValueError is thrown during type conversion an appropriate error will be raised:
>>> print TestApp(dispatcher).get('/recipes/rat-stew/view')
Traceback (most recent call last):
...
RequestArgsKeyError: 'id'
>>> print TestApp(dispatcher).get('/recipes/rat-stew/view?id=elephant')
Traceback (most recent call last):
...
RequestArgsConversionError: Could not convert parameter 'id' to requested type (invalid literal for int() with base 10: 'elephant')
A default argument value in the handler function will protect against this:
>>> dispatcher = dispatcher_app()
>>> @dispatcher.match('/recipes/<category:unicode>/view', 'GET')
... @with_request_args(category=unicode, id=int)
... def my_handler(request, category, id=1):
... return Response([
... u'Recipe #%d in category "%s".' % (id, category)
... ])
...
>>> print TestApp(dispatcher).get('/recipes/mouse-pie/view')
200 OK
Content-Type: text/html; charset=UTF-8
Recipe #1 in category "mouse-pie".
Sometimes it is necessary to map multiple request values to a single argument, for example in a form where two or more input fields have the same name. To do this, put the type-casting function into a list when calling with_request_args:
>>> @to_wsgi
... @with_request_args(actions=[unicode])
... def app(request, actions):
... return Response([
... u', '.join(actions)
... ])
...
>>> print TestApp(app).get('/', QUERY_STRING='actions=up;actions=up;actions=and+away%21')
200 OK
Content-Type: text/html; charset=UTF-8
up, up, and away!