On this episode, we will investigate Django middleware and see where it goes in your project. In the process, you’ll see why middleware is useful and how you can work with it.
Listen at Spotify.
Last Episode
On the last episode, we’re going to look at working with users in a Django project. We’ll see Django’s tools for identifying users and checking what those users are permitted to do on your website.
How Should I Think About Middleware?
Middleware is code that exists
in the middle.
“In the middle of what?”
you might ask.
The “middle” is the code between
when an HttpRequest
is created
by the framework
to the time
where your view code is called
by Django.
The “middle” can also refer
to the time
after your view completes
but before the HttpResponse
is translated to bytes
by Django
to send over the network
to a browser.
What’s shown below is a diagram
of all the default middleware
that is included
when you run the startproject
command.
+--------- SecurityMiddleware --------------+
|+-------- SessionMiddleware --------------+|
||+------- CommonMiddleware --------------+||
|||+------ CsrfViewMiddleware -----------+|||
||||+----- AuthenticationMiddleware ----+||||
|||||+---- MessageMiddleware ----------+|||||
||||||+--- XFrameOptionsMiddleware ---+||||||
||||||| |||||||
HttpRequest =================> view function ==================> HttpResponse
||||||| |||||||
How does Django make this layering work?
When you start Django
with an application server
like Gunicorn,
you have to give the application server the path
to your WSGI module.
If your project directory containing your settings file
is called project
,
then calling Gunicorn looks like:
$ gunicorn project.wsgi
Remember way back
in the first episode
that WSGI stands
for the Web Server Gateway Interface
and is the common layer
that synchronous Python web apps must implement
in order to work
with Python application servers.
Inside this project.wsgi
module
is a function called get_wsgi_application
.
get_wsgi_application
does two things:
- Calls
django.setup
which does all the startup configuration that we saw in the last article - Returns a
WSGIHandler
instance
The base handler includes a load_middleware
method.
This method has the job
of iterating through all the middleware listed
in your MIDDLEWARE
setting.
As it iterates through the MIDDLEWARE
,
the method’s primary objective is
to include each middleware
in the middleware chain.
The chain represents each instance of Django middleware, layered together, to produce the desired effect of allowing a request and response to pass through each middleware.
Aside from building the middleware chain,
load_middleware
must do some other important configuration.
- The method handles synchronous and asynchronous middleware.
As Django increases its support
of async development (a future topic in this series),
the internals of Django need to manage the differences.
load_middleware
makes some alterations depending on what it can discover about a middleware class. - The method registers a middleware with certain sets of middleware based on the presence of various hook methods. We’ll discuss those hooks later in this article.
AuthenticationMiddleware
has the singular job
of adding a user
property
to every HttpRequest
object
that passes through the application
before the request gets
to view code.
The AuthenticationMiddleware
highlights some qualities
that are good for middleware
in Django.
- A middleware should ideally have a narrow or singular objective.
- A middleware should run a minimal amount of code.
How Can I Write My Own Custom Middleware?
You can begin
with an empty middleware definition.
In my example,
we’re going to put the middleware
in a middleware.py
file.
class AwesomeMiddleware:
def __init__(self, get_response):
self.get_response = get_response
def __call__(self, request):
return self.get_response(request)
After creating the middleware, you add it to your settings.
# project/settings.py
MIDDLEWARE = [
'django.middleware.security.SecurityMiddleware',
...,
'middleware.AwesomeMiddleware',
]
We can break down how this class works.
- The
__init__
method gets a callable that is conventionally namedget_response
. The middleware is created duringload_middleware
and the callable is a key part of what makes the middleware chain work. The callable will either call the next middleware or the view depending on where the current middleware is in the chain. - The
__call__
method transforms the middleware instance itself into a callable. The method must callget_response
to ensure that the chain is unbroken.
If you want to do extra work,
you can make changes
to the __call__
method.
You can modify __call__
to process changes
before or after the call
of get_response
.
In the request/response lifecycle,
changes before get_response
occur before the view is called
while changes after get_response
can handle the response
itself
or any other post-request processing.
import logging
import time
logger = logging.getLogger(__name__)
class AwesomeMiddleware:
def __init__(self, get_response):
self.get_response = get_response
def __call__(self, request):
before_timestamp = time.time()
logger.info(f"Tracking {before_timestamp}")
response = self.get_response(request)
after_timestamp = time.time()
delta = after_timestamp - before_timestamp
logger.info(f"Tracking {after_timestamp} for a delta of {delta}")
return response
A Django middleware can define any of three different hook methods that Django will run at different parts of the request/response lifecycle. The three methods are:
process_exception
- This hook is called whenever a view raises an exception. This could include an uncaught exception from the view, but the hook will also receive exceptions that are intentionally raised likeHttp404
.process_template_response
- This hook is called whenever a view returns a response that looks like a template response (i.e., the response object has arender
method).process_view
- This hook is called right before the view.
import logging
import time
logger = logging.getLogger(__name__)
class AwesomeMiddleware:
def __init__(self, get_response):
self.get_response = get_response
def __call__(self, request):
before_timestamp = time.time()
logger.info(f"Tracking {before_timestamp}")
response = self.get_response(request)
after_timestamp = time.time()
delta = after_timestamp - before_timestamp
logger.info(f"Tracking {after_timestamp} for a delta of {delta}")
return response
def process_view(self, request, view_func, view_args, view_kwargs):
logger.info(f"Running {view_func.__name__} view")
Now our middleware uses Python’s reflection capabilities to record the view’s name. If accessing the Django admin with an unauthenticated user, the log might record something like:
Tracking 1607438038.232886
Running login view
Tracking 1607438038.261855 for a delta of 0.02896881103515625
Want to learn more about hooks? You can see all the details about these hooks in the middleware documentation.
What Middleware Does Django Include?
We’ve looked at the mental model for middleware and all the details of how an individual middleware works. What middleware does Django include in the framework?
The full list of built-in middleware is available in the middleware reference. I’ll describe what I think are the most common or useful middleware classes that Django includes.
AuthenticationMiddleware
- We’ve already encountered this middleware in the exploration of the auth system. The job of this middleware is to add theuser
attribute to anHttpRequest
object. That one littleuser
attribute powers many of the features of the auth system.CommonMiddleware
- The common middleware is a bit of an oddball. This middleware handles a variety of Django settings to control certain aspects of your project. For instance, theAPPEND_SLASH
setting will redirect a request likeexample.com/accounts
toexample.com/accounts/
. This setting only works if theCommonMiddleware
is included.CsrfViewMiddleware
- In the forms article, I mentioned the CSRF token. As a reminder, this is a security feature that helps protect your project against malicious sources that want to send bad data to your site. TheCsrfViewMiddleware
ensures that the CSRF token is present and valid on form submissions.LocaleMiddleware
- This middleware is for handling translations if you choose to internationalize your project. We’ll look into internationalization in a future article.MessageMiddleware
- The message middleware is for “flash messages.” These are one-time messages that you’d likely see after submitting a form, though they can be used in many places. We’ll discuss messages more when we get to the sessions topic.SecurityMiddleware
- The security middleware includes a number of checks to help keep your site secure. We saw the example of checking for HTTPS earlier in this article. This middleware also handles things like XSS, HSTS, and a bunch of other acronyms (😛) that will be seen with the future security topic.SessionMiddleware
- The session middleware manages session state for a user. Sessions are crucial for many parts of Django like user auth.
As you can see from this incomplete list, Django’s middleware can do a lot to enrich your project in a wide variety of ways. Middleware is an extremely powerful concept for Django projects and a great tool to extend your application’s request handling.
Summary
In this episode, we explored middleware.
That exposed you to:
- The mental model for considering middleware
- How to write your own middleware
- Some of the middleware classes that come with Django
Next Time
On the next episode, I’ll chat about static files. Static files are CSS, JavaScript, images, and the like. We will look at how Django works with those kinds of files in your project.
You can follow the show on Spotify. Or follow me or the show on X at @mblayman or @djangoriffs.
Please rate or review on Apple Podcasts, Spotify, or from wherever you listen to podcasts. Your rating will help others discover the podcast, and I would be very grateful.
Django Riffs is supported by listeners like you. If you can contribute financially to cover hosting and production costs, please check out my Patreon page to see how you can help out.