Turtles, all the way down : WSGI middleware

WSGI (Web Standard Gateway Interface) provides a standard for connecting a Python web application or framework to a web server. The framework provides a function, or callable, that is called by code on the server side, for example a CGI or FastCGI script. Here’s an example, from Wikipedia:

def app(environ, start_response):
    start_response('200 OK', [('Content-Type', 'text/plain')])
    return ['Hello Worldn']

As you can see, it’s pretty basic and low-level. However, it is a complete WSGI application. As an aside, Paste, which is used by Pylons, is a WSGI implementation, with building blocks for building and deploying your own WSGI frameworks.

The nice thing about it is that you can add middleware between the server and your application. Middleware is just functions (or callables) like the one shown above, but you can implement both application and server-side code. This is a powerful,yet beautifully simple concept. Here is an example:

class PrintTimeMiddleware(object):
       def __init__(self, app):
            self.app = app
       def __call__(self, environ, start_response):
            print str(datetime.now()
            return self.app(environ,start_response)

We then add this into our server script as so:

app = PrintTimeMiddleware(app)

The “app” in question is the instance of any previous middleware, so it will be executed in that order. Each middleware is called in that order as app(environ, start_response). In this case we simply print the current time to STDOUT. Middleware can be pretty much anything you imagine…for example, it can do XSLT transformations, user authentication, database connection pooling, browser checking…

The Pylons framework itself is middleware. If you look at a Pylons application, under config/middleware.py, you see this line:

app = pylons.wsgiapp.PylonsApp(config,helpers=myapp.lib.helpers,g=app_globals.Globals)

Now for something actually useful. In a Pylons application, or any other WSGI-based framework, you can of course add your own middleware. In a previous article, “Using Elixir with Pylons”, I mentioned that you need to create a SQLAlchemy session with each new HTTP request thread, to ensure your SQLAlchemy or Elixir objects can connect to the engine in that thread. In addition, when starting the application, we need to create a new engine for our database which is then cached for later use. Now, we could do it in our code (for example in BaseController) but it is a lot more elegant to add it to our middleware. Let’s create a little middleware class for this:

myapp/models/__init__.py
from elixir import metadata, objectstore
from pylons.database import create_engine, make_session

session_context = objectstore.context
engine = None

# call this with every thread
def resync():
    session_context.current = make_session()

# connects engine to metadata
def connect():
    global engine
    if not engine:
        engine = create_engine()
        metadata.connect(engine)

def flush_all():
    objectstore.flush()

myapp/models/middleware.py

from myapp import models as model

class ModelMiddleware(object):
    def __init__(self, app):
        self.app = app
        model.connect()
    def __call__(self, environ, start_response):
        model.resync()
        return self.app(environ,start_response)

Then, in config/middeware.py, under the # YOUR MIDDLEWARE # comment, we add the middleware instance:

app = ModelMiddleware(app)

When the middleware is initialized, it creates the engine once in model.connect(). When it is called, the model.resync() line ensures a session instance is created and passed to the Elixir session context. This will now happen with each HTTP request.

We might want to modify it slightly, so that all changes are committed to the database at the end of the request. Personally, I don’t like to do this, as I prefer a more fine-grained approach, but others might want to do so.Let’s change __call__ slightly:

def __call__(self, environ, start_response):
    model.resync()
    app = self.app(environ,start_response)
    model.flush_all()
    return app

As you can see, you can hook into any part of the request lifecycle. One thing to remember is that this will happen to all requests; if I wanted more fine-grained control over individual requests, then the place for that would be the application code itself. For example, in Pylons, I can implement __before__ and __after__ methods in my controllers.

Advertisements

One response to “Turtles, all the way down : WSGI middleware

  1. Hi! Just a question!
    In resync, session_context shouldn’t be global?

Leave a Reply

Please log in using one of these methods to post your comment:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s