Posted on Nov. 17, 2008 at 9:26 P.M.

Most people in the Django community are deploying their apps these days with mod_wsgi. If not, then you're at least using WSGI as a communication layer with your application server, in one way or another. The great thing about WSGI is that it gives everyone a common interface through which to talk. It also has the added benefit of being a common abstraction that many people have built these great, really useful tools on top of.

Consider Repoze. If you navigate to the middleware section of their website, they have some really cool stuff available! There are utilities for logging, authentication, security, profiling, templating, etc. All of these pieces of middleware are designed to be totally pluggable, because they are designed to work solely based on what's available through WSGI.

My personal favorite of that lot is repoze.profile. It accumulates Python profiling information about whatever app is being run, and allows you to view that profile information via a web interface by visiting a special URL. There is absolutely no reason that the Pylons, TurboGears, or CherryPy guys should be able to get away with keeping this stuff for themselves, so I want to show just how easy it is to integrate this profiling module with Django.

First, though, here's a typical .wsgi file that might be used in conjunction with mod_wsgi:

import os, sys
sys.stdout = sys.stderr

os.environ['DJANGO_SETTINGS_MODULE'] = 'settings'

import django.core.handlers.wsgi

application = django.core.handlers.wsgi.WSGIHandler()

There's really nothing special going on here, and if you would like to learn more about how to set up this WSGI file, visit mod_wsgi's documentation on the subject. Now if you'll notice, application is simply an instance of WSGIHandler, which is simply a callable. A WSGI middleware is just a wrapper around that callable. Here's how easy it is to add the profiling middleware:

from repoze.profile.profiler import AccumulatingProfileMiddleware
application = AccumulatingProfileMiddleware(
    application,
    log_filename='/tmp/djangoprofile.log',
    discard_first_request=True,
    flush_at_shutdown=True,
    path='/__profile__')

There we go! We have imported the profiling middleware, and passed the Django WSGI application as the first argument. The rest is just setting options for the middleware. You can restart apache and the WSGI profiling middleware is already working.

Sometimes, though, you don't want all of Apache just to run some middleware. You want to be able to do the same thing, but locally. Believe it or not, Django's local development server is just a WSGI server itself, so one option would be to do the wrapping directly in django, right here. But you really don't want to be hacking inside of Django internals if you don't have to. Fortunately there are many alternative WSGI servers out there. Brian Rosner has created a custom management command to use the excellent CherryPy WSGI server with Django, on his blog.

Let's say you just want to try this out quickly after reading this blog post, though. If you're running Python 2.5 or greater, you're in luck, because a script less than 10 lines long can get you up and running:

#!/usr/bin/env python

import sys
from wsgiref.simple_server import make_server

if __name__ == "__main__":
    execfile(sys.argv[1])
    httpd = make_server('', 8000, application)
    httpd.serve_forever()

Now, to run it, simply invoke it like this:

python runserver.py my_wsgi_file.wsgi

Now, navigate around your app for a little bit and then point your browser to the profile url and see how freaking awesome middleware can be.

I'm not trying to stir up any controversy, I'm not saying we should stop making Django middleware or anything like that. But I seriously, seriously hope that someone tries this out and realizes the multitudes of great WSGI apps out there that can be taken advantage of. Mark Ramm wasn't full of hot air when he talked about this at DjangoCon or blogged about it later. He was right, and I for one wish I had listened sooner.

V
at 3:46 a.m.
on Nov. 18, 2008

"You are right, and I for one wish I am listening now." :)

at 9:47 a.m.
on Nov. 18, 2008

For those curious about what it looks like, check it out at http://bit.ly/C5qX

Zeke Harris
at 10:39 a.m.
on Nov. 18, 2008

K, now I'm curious how I get a .wsgi to work with Lighttpd! Any thoughts? I haven't been able to find anything except running it through fastcgi (which requires you to run ./runserver startfcgi blah blah blah every time your server restarts and that sucks). I know lighttpd is wsgi compatible...but I just want to be able to point at a simple .wsgi in my config and have it start & restart when the server does.

at 2:52 p.m.
on Nov. 18, 2008

I switched from lighttpd to nginx as a drop-in replacement while ago and I have been pretty pleased with the decision.

I would seriously recommend taking a look at Spawning. As far as restarting goes, I would just add a dependency on the Spawning application in the shutdown / startup hooks for the lighttpd/nginx rc script.

at 9:42 p.m.
on Nov. 18, 2008

This is a great point. I deployed a pretty big Django app this year using mod_wsgi/ with Apache forking only, not threading.

It seems like it is just a total shame if the larger web community can't reuse middleware written via the same spec. At PyWorks Jonathan LaCour mentioned something really sweet, that he has a WSGI app that commits all POST requests to the MySQL master, and the rest go to slaves.

at 5:18 a.m.
on Nov. 19, 2008

I haven't noticed it earlier, but in fact Django already uses a WSGI Middleware: If you use the runserver command to start the development server, a WSGI middleware is used to serve the admin-media.
For details see: django.core.servers.basehttp.AdminMediaHandler

So if you use the code above and wonder why your admin media isn't served just wrap the WSGIHandler in an AdminMediaHandler and it works like in the dev-server:

application = AdminMediaHandler(WSGIHandler())

Search

 

Badges

  • django badge
  • apache badge
  • GeoURL
  • XFN Friendly
  • Valid HTML 4.01 Transitional