Chapter 10 ■ http ServerS
170
Other programmers wanted to serve dynamic pages from a web server that could also serve static content, such
as images and stylesheets. So, mod_python was written: an Apache module that allowed properly registered Python
functions to provide custom Apache handlers that could provide authentication, logging, and content. The API was
unique to Apache. Handlers written in Python received a special Apache request object as their argument and could
call special functions in the apache module to interact with the web server. Applications that used mod_python bore
little resemblance to those written against either CGI or http.server.
This situation meant that each HTTP application written in Python tended to be anchored to one particular
mechanism for interfacing with the web server. A service written for CGI would need, at the very least, a partial rewrite
to work with http.server, and both would need modification before they could run under Apache. This made Python
web services difficult to migrate to new platforms.
The community responded with PEP 333, the Web Server Gateway Interface (WSGI).
As David Wheeler famously said, “All problems in computer science can be solved by another level of
indirection,” and the WSGI standard created the extra level of indirection that was necessary for a Python HTTP
service to interoperate with any web server. It specified a calling convention that, if implemented for all major web
servers, would allow both low-level services and full web frameworks to be plugged into any web server that they
wanted to use. The effort to implement WSGI everywhere succeeded quickly, and it is now the standard way for
Python to speak HTTP.
The standard defines a WSGI application as a callable that takes two arguments. An example is shown in
Listing 10-1, where the callable is a simple Python function. (Other possibilities would be a Python class, which is
another kind of callable, or even class instance with a call() method.) The first parameter, environ, receives a
dictionary that provides an extended version of the old familiar CGI set of environment variables. The second parameter
is itself a callable, conventionally named start_response(), with which the WSGI app should declare its response
headers. After it has been called, the app either can begin yielding byte strings (if it is itself a generator) or can return
an iterable that yields byte strings when iterated across (returning a simple Python list is sufficient, for example).
Listing 10-1. A Simple HTTP Service Written as a WSGI Client
#!/usr/bin/env python3
Foundations of Python Network Programming, Third Edition
https://github.com/brandon-rhodes/fopnp/blob/m/py3/chapter10/wsgi_env.py
A simple HTTP service built directly against the low-level WSGI spec.
from pprint import pformat
from wsgiref.simple_server import make_server
def app(environ, start_response):
headers = {'Content-Type': 'text/plain; charset=utf-8'}
start_response('200 OK', list(headers.items()))
yield 'Here is the WSGI environment:\r\n\r\n'.encode('utf-8')
yield pformat(environ).encode('utf-8')
if name == 'main':
httpd = make_server('', 8000, app)
host, port = httpd.socket.getsockname()
print('Serving on', host, 'port', port)
httpd.serve_forever()
Listing 10-1 might make WSGI appear simple, but that is only because the listing is choosing to behave in a
simple manner and not make full use of the specification. The level of complexity is greater when implementing
the server side of the specification because in that case the code must be prepared for applications that take full
advantage of the many caveats and edge cases described in the standard. You can read PEP 3333, the modern
Python 3 version of WSGI, if you want an idea of what is involved.