WSGI: What Is It, Exactly?
Over time I have written and touted about how my blog is written using WSGI. But... here's a dark secret of mine: I never understood how WSGI works or even what it is, exactly.
But hey, now I do, and I'm going to tell you all about it! And myself, too, so I don't forget what it is ever again.
WSGI stands for Web Server Gateway Interface. More specifically, the Python Web Server Gateway Interface, but that's not in the name because Python did it first (before Ruby and others). In its essence, it is a simple specification of a web application. So... what is a web application?
A web application is something that receives input in the form of a URL and possible form data or cookies, and returns a status, and a webpage.
This could be coded as a function! For example:
def application(environment): # Use environment to come up with response return response
Well, almost. See, you want to know as soon as possible if the file was found, not found, if there was an error, etc. This has to happen before you start the file or data transfer, for the user's browser to receive the answer as soon as possible. Therefore, the web application will have to call some status notification before returning its data.
Since Python supports passing around functions as variables, this is very convenient to pass off a function to call when the response begins. The code can be written as:
def application(environ, start_response): data = '' # Do checks for, say, the file specified being found if found: start_response('404 File Not Found') data = 'Nothing here, file was not found' else: start_response('300 OK') data = some_method_that_gives_you_the_data() # Then we just need to return the data requested return data
This code is already very close to the WSGI specification. WSGI adds some extra features, such as the start_response function needing the HTTP headers as well as the response status. A second one is to return an iterable that contains possibly multiple return data elements. This is to provide compatibility with sites that require lots of processing, but can provide data progressively as they load. As such, a "Hello, World!" WSGI-compatible web application would look like:
def application(environ, start_response): start_response('300 OK', [('Content-Type','text/plain')]) return ['Hello, world!']
That's it! Well, I lied, the actual PEP 333 contains a bit more data about threadability and other HTTP features, but those are side-notes.
So, why is this useful? Well, it abstracts your application from the web server which might be running it. What's a web server? Let's have some examples.
PythonPaste is what my blog runs on. It's a very basic Python webserver that just... is a plain web server. It has various plugins you can add on, but for a simple WSGI app, you can run it like this:
from helloworld import application from paste import httpserver httpserver.serve(application, host='localhost', port='8080')
Poof, and it would be found at http://localhost:8080.
But say you get fed up with PythonPaste, and would rather serve your application directly through Apache. Well, first you install mod-wsgi, then include something like this in your server's configuration:
WSGIScriptAlias /helloworld /path/to/helloworld.py
And then your WSGI application would respond to anything at http://yourapacheserver/helloworld. But what if you wanted to do something fancy, like Google App Engine? Turns out GAE supports WSGI applications! Just put this in the controller Python file for your project:
from google.appengine.ext.webapp.util import run_wsgi_app from helloworld import application run_wsgi_app(application)
It's really that easy.
But wait, there's more!
What if... you nest this stuff? If you're using a simple web server like Paste, it doesn't come pre-packed with session cookies, so it's hard to know if you're being queried by the same computer, and to create persistence across multiple calls. Once you've coded a few apps, you will start getting headaches every time you have to write the session cookie again. Isn't there a simpler way to do this? Maybe:
As it turns out, PythonPaste actually supplies WSGI-compatible middleware for your applications to use. So, let's use it to make a hello world application that has a counter of the number of times you've loaded the page:
def application(environ, start_response): start_response('300 OK', [('Content-Type','text/plain')]) session = environ['paste.session.factory']() counter_key = 'helloworld.counter' if counter_key not in session: session[counter_key] = 0 session[counter_key] += 1 return ['Hello, world! This was load number: ', str(session[counter_key])]
And the code that you can use to get PythonPaste to run this:
from paste import httpserver from paste.session import SessionMiddleware from helloworld import application wrapped_app = SessionMiddleware(application) httpserver.serve(wrapped_app, 'localhost', '8080')
Boom! That's it.
Lastly, what does it mean when all of these newfangled web frameworks (Zope, web2py, Django, Beaker, etc.) claim to be "WSGI-compatible"? It means that their project has a file in it called app.wsgi or something of the sort which contains an application(environ, start_response) function that supports being run behind any web server via WSGI.
I don't know about you, but I think that all that... is awesome.