Getting Started with WSGIby Jason R. Briggs
What is WSGI, and why should you care? WSGI, or the Web Server Gateway Interface, is a Python Enhancement Proposal (#333) authored by Philip Eby in an attempt to address the lack of standardization among Python web frameworks. Think of it as the servlet spec for the Python world, only simpler. Although the WSGI specification is primarily aimed at framework developers, you can also develop application components to it. One of the aims of PEP 333 is ease of implementation, which consequently carries through to the development of those components. For example, the "Hello world" example given in the WSGI specification could hardly be any simpler to implement:
def simple_app(environ, start_response): status = '200 OK' response_headers = [('Content-type','text/plain')] start_response(status, response_headers) return ['Hello world!\n']
With the recent release of Python 2.5, the reference implementation of WSGI is available as a standard module (
wsgiref), meaning that there is a direct path from developing application components to test and production environments. It's easy to imagine rapid development of components using
wsgiref, then using something more scalable for testing and production. That said, WSGI doesn't provide many features you might expect or want for webapp development; for sessions, cookies, and the like, you'd also need a suitable web framework (of which there are many), or perhaps middleware or utilities to provide those services.
As to why you should care: if you're a low-level person who prefers to bolt on utilities and modules to keep your development effort as free of constraint as possible, WSGI will hold considerable attraction. Even if you prefer the benefits offered by higher-level frameworks, chances are they'll be built on top of WSGI, and it's always useful to know what happens behind the scenes.
For those (like myself) running Python 2.4.x, the good news is that the
wsgiref module will still function.
wsgiref is available from the Python Subversion repository, or you can download it from the command line via:
svn co http://svn.python.org/projects/python/trunk/Lib/wsgiref
Copy the wsgiref directory into the site-packages directory of your Python distribution (in my case, /usr/lib/python2.4/site-packages/) and check whether you can import the module. If so, you should be able to type
import wsgiref in the Python console with no errors reported.
Testing the "Hello world" application shown earlier requires a few extra lines of code (see test1.py):
if __name__ == '__main__': from wsgiref import simple_server httpd = simple_server.make_server('', 8080, simple_app) try: httpd.serve_forever() except KeyboardInterrupt: pass
simple_server (an implementation of the
BaseHttpServer module) to provide basic web server facilities, and passes the name of the
simple_app function as an argument to the
make_server function. Run this program (
python test1.py) and direct your browser to http://localhost:8080 to see it in action.
And Using an Object...
You don't have to stick to simple functions for your applications--WSGI supports object instantiation for handling requests. To do so, create a class that implements the
__iter__ methods. For example, I've abstracted out some basic utilities in the following class. The
__iter__ method checks for a
do_ method matching the type of HTTP request (GET, PUT, etc.) and either calls that method to process, or sends an HTTP 405 in response. In addition, I've added a
parse_fields method to parse the
x-url-form-encoded parameters in the body of a request using the standard
cgi module. Note that, for both object instantiation and simple method calls, the arguments (
start_response) are positional--the order is important, not the name.
import cgi class BaseWSGI: def __init__(self, environ, start_response): self.environ = environ self.start = start_response def __iter__(self): method = 'do_%s' % self.environ['REQUEST_METHOD'] if not hasattr(self, method): status = '405 Method Not Allowed' response_headers = [('Content-type','text/plain')] self.start(status, response_headers) yield 'Method Not Allowed' else: m = getattr(self, method) yield m() def parse_fields(self): s = self.environ['wsgi.input'].read(int(self.environ['CONTENT_LENGTH'])) return cgi.parse_qs(s)
I can then subclass
BaseWSGI to create a simple number-guessing application (test2.py):
import random number = random.randint(1,100) class Test(BaseWSGI): def __init__(self, environ, start_response): BaseWSGI.__init__(self, environ, start_response) self.message = '' def do_GET(self): status = '200 OK' response_headers = [('Content-type','text/html')] self.start(status, response_headers) return ''' <html> <body> <form method="POST"> <p>%s</p> <p><input type="text" name="myparam" value="" /> <p><input type="submit" /></p> </form> </body> </html> ''' % self.message def do_POST(self): global number fields = self.parse_fields() if not fields.has_key('myparam'): self.message = 'You didn't guess' return self.do_GET() guess = int(fields['myparam']) if guess == number: self.message = 'You guessed correctly' number = random.randint(1,100) elif guess < number: self.message = 'Try again, the number is higher than your guess' else: self.message = 'Try again, the number is lower than your guess' return self.do_GET()
You may be thinking that all of this is somewhat like reinventing the wheel--which is true, to a point. However, the low-level nature of WSGI is designed to make implementing frameworks a straightforward process--and more standardized. If you don't want to reinvent the wheel from an application perspective, look to a higher-level web framework, but do read on for some alternatives.