Monday 1 June 2009

Middleware: Like An Onion But With Less Crying

This week will be the first part of a look into the middleware system that Django provides. This is an extremely powerful system that allows you to create pieces of code to run on each and every request that comes in.

If you've done even a small project in Django you'll no doubt have come across middleware, possibly without even realising it. The main starting point is located in the settings.py for a project. If you inspect this file you should find an entry similar to:

#...
MIDDLEWARE_CLASSES = (
'django.middleware.common.CommonMiddleware',
'django.contrib.sessions.middleware.SessionMiddleware',
'django.contrib.auth.middleware.AuthenticationMiddleware',
)
#...

This tuple of classes is what Django will refer to when it loads up its middleware and not only defines the items that will be loaded but also the order in which they will be executed. The items in the example given here represent a very simple set and are all that is needed for a very basic site. CommonMiddleware provides some core properties that are used by the other classes, SessionMiddleware is responsible for attaching the session object to a request (allowing you to store information from one request to another) and also for ensuring that the session is saved back to the database once the request has completed being processed. Finally AuthenticationMiddleware is responsible for adding things like the user object to the request - not a necessity as such but still extremely handy none the less.

There are four separate phases that middleware can act on. Any individual item of middleware can act on any number of these phases and executes each middleware class in a specific order which are:


  1. The process_request phase - middleware is executed in the order specified;

  2. The process_view phase - middleware is executed in the order specified;

  3. The process_response phase - middleware is executed in the reverse of the order specified;

  4. The process_exception phase - middleware is executed in the reverse of the order specified;

The process_request phase is the earliest available point that middleware can be called. After Django receives a request and generates a request object then it calls all the middleware classes that implement the process_request function passing them the newly created request object. This phase is generally the best place to run any essential checks or add things to the session that would cause the system to break if they were missing.

The process_view phase is the second phase to run. It occurs after the request has been processed and the view that will be run has been determined. At this point all the middleware classes that implement the process_response function are called and given access not only to the request object but also to the view object that is about to be executed along with any arguments that are going to be passed to the view object. This phase is a good place to put checks where you may want to redirect the user to a page asking them to log in, redirecting the user to somewhere requesting them to complete filling in profile details or initialising a log entry (as you know at this point more information about what the user is doing).

The process_response phase runs at the point after a view has been executed and Django has been given a response object to display back to the user. All classes that implement the process_response function will be provided with the original request object as well as the response object that was generated. This phase is good for things like tidying up anything that should be stored between requests or completing and writing out log entries.

The process_exception phase is a slightly special case that runs whenever an exception cuts in and interrupts the execution of a request. All classes that implement the process_exception function will be provided with the original request and the exception that has been thrown. This phase is good for logging any problems that have occurred and for trying to either send the user to a friendly error page or to try and process something that wont cause an error instead.

Any of the functions that are implemented in the class have two choices of what to return. If they return None then that processing will continue with the next item of middleware in order otherwise they can return a response object which will then be used straight away, if the phase returning the new response is the request, view or exception phase then things will jump straight to the response phase and only middleware in that phase will be run subsequently. If the phase returning the new response is the response phase then no more middleware will be run.

A quick note on the ordering - be aware that during the response and exception phase middleware is run in a reverse order. This means that for things like the session middleware. Anything listed after it in the settings.py file can add things to the session at any point and it will still be saved since the session will be set up early on (as the request and view phase run in the normal order) and it wont be saved until the latest possible moment.

That concludes this weeks look at what goes into the middleware. Next week I will be providing some examples of some interesting things that you can do with middleware.

1 comment: