Part II · The request cycle

Chapter 3: Handlers and middleware

The handler is the one object a web server calls for every request. It builds the middleware chain once, when it is made, and from then on sends each request down that chain to a view and hands the response back.

A handler does not listen on a socket. A server does that: Gunicorn, uWSGI, Daphne, Uvicorn, or Django’s own development server. The server talks to the Python application it hosts through one of two standard interfaces: WSGI, in which a request is a function call, or ASGI, in which it is a coroutine. Django’s half of either contract is a single callable object, the handler. The server is given the handler once, when the process starts, and calls it for each request. Nearly everything Django does for a request, from parsing its headers to rendering a template, happens inside that one call.

There are two handlers, WSGIHandler and ASGIHandler, and most of what they do is inherited from one base class, BaseHandler. A handler turns what the server gives it into an HttpRequest, sends that request through the middleware chain to get an HttpResponse back, and turns the response into what the server expects. The first and the last of these differ between WSGI and ASGI. The middle one is BaseHandler’s, which has it in a synchronous and an asynchronous form, and it is where most of this chapter is spent.

The chain is an onion

Middleware is configured as a flat list, settings.MIDDLEWARE, but it does not run as a list. When a handler is made, BaseHandler.load_middleware turns the list into a nest of callables, one inside the next. At the centre is BaseHandler._get_response, the method that finds the view for a URL and calls it (under ASGI its coroutine twin, BaseHandler._get_response_async). Around it is the last middleware in the setting; around that, the one before; and so on out to the first. Each layer is called with a request, may do some work, calls the layer inside it, may do more work on the response that comes back, and returns it.

Figure 3.1 With MIDDLEWARE = [A, B, C], a request enters at A and reaches the view through B and C, and the response returns through the same layers in the opposite order. Each boundary is where an exception raised inside is turned into a response.

This shape explains most of how middleware behaves.

The order in the setting is the order on the way in, and the reverse on the way out. The first middleware listed sees the request first and the response last. A middleware that reads something another one puts on the request has to come after it in the list: AuthenticationMiddleware refuses to run unless the session middleware has already set the session on the request.

Any layer can answer without calling the layer inside it. A middleware that returns a response of its own, a redirect or a page from the cache, cuts the request short. Nothing inside it runs, the view included, and the response travels back out through the layers outside it only.

The nest is built once and kept on the handler as BaseHandler._middleware_chain. Serving a request is one call of that object, and nothing about the chain is decided per request.

One request, in order

What follows was recorded from a running Django. Three middleware, A, B and C, write down each hook as Django calls it. A script plays the server’s part: it calls a WSGIHandler with a request for a view that returns a plain response, reads the body, and closes the response.

WSGI, GET /plain/: a view that returns a response
    signal: request_started
    A.process_request
    B.process_request
    C.process_request
    A.process_view
    B.process_view
    C.process_view
    the view
    C.process_response(200)
    B.process_response(200)
    A.process_response(200)
    start_response("200 OK", headers)
    the server closes the response
    signal: request_finished

Read against the code:

  1. The handler’s entry. The server calls WSGIHandler.__call__. It sends the request_started signal, builds a WSGIRequest from what the server passed, and gives it to BaseHandler.get_response, which calls the chain.
  2. The way in. Each layer’s process_request runs, outermost first. The handler never calls that method, or knows of it. What the handler calls is the middleware itself, and process_request is how MiddlewareMixin, the base class nearly all of Django’s middleware use, spends its turn on the way in.
  3. The centre. BaseHandler._get_response resolves the URL to a view, runs the process_view hooks in the same outermost-first order, and calls the view.
  4. The way out. Each layer’s process_response runs, innermost first. Back in WSGIHandler.__call__, the status and the headers go to the server’s start_response function, and the response object itself is returned as the body. When the server has sent the body it closes the response, and closing it is what sends request_finished.

The whole request ran on the thread that called the handler, as one ordinary chain of function calls. Under ASGI the same chain runs, with the complication that some of its layers may be synchronous and others not; Sync and async in one chain is about that.

Where exceptions go

An exception raised inside the chain rarely reaches the server. As the chain is built, each layer is wrapped in convert_exception_to_response, which catches an exception from the layer and returns a response in its place: a 404 for Http404, a 403 for PermissionDenied, a 500 for anything it does not recognise. Because each layer is wrapped separately, the layer outside receives a response, and its own work on the way out still runs. How an exception becomes a response has the whole table, and says what does get through.

The neighbours

The handler is the hub of the request cycle, and each thing it touches has a chapter of its own. A handler is made by get_wsgi_application or get_asgi_application, after django.setup has loaded the settings and the apps (Settings, apps and startup). The request it builds and the response it sends are the subject of Requests and responses. At the centre of the chain it hands the path to the URL resolver (URL routing) and calls what comes back (Views). And most of the middleware Django ships belong to other subjects: sessions and authentication, CSRF and the security headers, the locale, the cache. This chapter is about the machinery they all plug into.