Using Coconuts - a Pythonic Blog

Username:

Password:


Don't have an account? Get one!

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.

/upload/wtf-cat

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.

/upload/wsgi-basic

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.

/upload/wsgi-response

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!']
http://www.opensourcenerd.com/resources/easy.png

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.

/upload/shocked-cat

But wait, there's more!

/upload/cat-tell-me-when-its-over

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:

/upload/wsgi-middleware

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.

New Comment
You're not logged in! Log in to be awesome!
Format: BBCode ReStructured Text

Author (max. 20 characters):