Chapter 3 · Handlers and middleware
3.2 Building the middleware chain
How BaseHandler turns the list in settings into one callable: the order it works in, what it asks of a middleware, and the three lists of hooks it fills on the way.
Each entry of settings is the dotted path of a factory: something that is given the next layer inwards and returns the middleware itself, a callable that takes a request and returns a response. A class whose __init__ accepts the inner layer and which defines __call__ is the usual form, and by convention the argument is called get_response. A function that returns a function serves as well.
When the chain is built
WSGIHandler and ASGIHandler each end by calling load_middleware on the handler being made, the second with is_async=True. A process makes its handler once, so the chain is built once, as the server starts, and that is when each factory runs. A middleware’s __init__ is startup code: what it reads from the settings it reads then, and it is not called again for any request.
The loop
BaseHandler starts at the centre and works outwards.
It begins with BaseHandler, or with BaseHandler when the handler is asynchronous, wrapped in convert_exception_to_response. That is the first value of a local variable holding the chain so far. Then, for each path in settings, taken in reverse:
- It imports the factory with
import_string. - It decides whether this layer will run synchronously or asynchronously and, if the chain so far runs the other way, puts an adapter around the chain so far. Sync and async in one chain explains the decision.
- It calls the factory with the chain so far. What comes back is the middleware: the new layer.
- It looks on the new layer for three optional methods, and files each one it finds in a list.
- It wraps the new layer in
convert_exception_to_response, and that wrapper becomes the chain so far.
After the loop it adapts the outermost wrapper to the handler’s own mode and stores it as BaseHandler. That assignment is the method’s last statement on purpose. The attribute is None until then, and doubles as the sign that the handler is ready: the test client’s handlers test it on each request to decide whether the chain has still to be built.
In this recording of a WSGIHandler being made with MIDDLEWARE = [A, B, C], each middleware writes a line when its factory is called, saying what it was given.
Making a WSGIHandler builds the chain, last middleware first
C(get_response) get_response is the wrapper around BaseHandler._get_response
B(get_response) get_response is the wrapper around C
A(get_response) get_response is the wrapper around B
The third step can end otherwise.
A factory may decline. If it raises MiddlewareNotUsed, the loop goes on to the next path and the chain so far stays as it was, so the layer is simply absent. When settings.DEBUG is true the fact is logged at the debug level. This is the way for a middleware to take itself out according to configuration. None of the middleware Django ships does so: the exception exists for the middleware a project writes.
A factory must return something. If it returns None, load_middleware raises ImproperlyConfigured, with a message that names the factory.
The three lists of hooks
Besides being a layer of the chain, a middleware may define any of three methods that the centre of the chain calls directly. load_middleware collects them as it goes, into three lists on the handler.
| a middleware’s method | kept in | order | called |
|---|---|---|---|
process_view |
kept in Base |
order as in the setting | called before the view, with the view and its arguments |
process_ |
kept in Base |
order the setting reversed | called after the view, when the response can still be rendered |
process_ |
kept in Base |
order the setting reversed | called when the view, or the rendering of a template response, raises an exception |
The orders fall out of the loop. It runs through the setting backwards, and it puts each process_view at the front of its list and each of the other two at the end of theirs. The result agrees with the onion: a hook that runs before the view runs outermost first, and a hook that runs after it runs innermost first.
Each method is adapted to the handler’s own mode as it is filed, so that the centre can call it directly. The process_exception hooks are different: they are filed as synchronous whatever the mode, and a comment at that line says the exception-handling stack is, for now, always synchronous.
What the handler does not know about
process_request and process_response, the two best-known middleware methods, appear nowhere in the handler. They belong to MiddlewareMixin, and what they mean is a few lines of MiddlewareMixin:
- If the class has a
process_request, call it with the request. - If that returned a response, keep it. Otherwise call the layer inside.
- If the class has a
process_response, pass the response through it. - Return the response.
So the rule that a process_request returning a response skips the view is no rule of the handler’s. It is the second step above, and a middleware that is not built on MiddlewareMixin has these two hooks only if it implements them itself.