A Comprehensive Guide to Django Middleware
Tags: python, django, webdev
Published: 2020-10-12 17:39:38

# What is Middleware?
As the name suggests, middleware is basically a mechanism that comes in the _middle_ of the usual request-response cycle, usually to provide some sort of intermediate functionality. A middleware component takes an HTTP request, performs some operation upon it, and then passes it on to the next component in line, which may be either another middleware or the final view.

![Django Middleware Diagram](https://dev-to-uploads.s3.amazonaws.com/i/uffuu4vich9mvnkeg593.png)
_Image by [Jody Boucher](https://jodyboucher.com/blog/django-middleware-overview)_

Middleware can also return a response directly instead of forwarding the request further down the chain. For example, Django's [CSRF Middleware](https://docs.djangoproject.com/en/3.1/ref/csrf/) checks if the request comes from a valid origin (usually the same domain as the server) and immediately returns with a 403 Forbidden response if not. This helps prevent a potentially malicious request from entering the system.

Middleware is always _global_, that is, it is applied to every request entering the system.

# Uses for Middleware

## 1. Filtering Requests

Middleware can be written to filter out invalid or potentially malicious requests and have them return immediately (usually with an error response), thus blocking them from proceeding further. One contrived example is the CSRF middleware mentioned above, which filters out requests sent from different domains. Another possible example could be an RBAC ([Role-Based Access Control](https://www.techopedia.com/definition/23933/role-based-access-control-rbac)) middleware, that prevents users from reading or modifying resources they are not authorised to access. Or, yet another example is geo-blocking middleware that filters out requests from certain geographical locations.

## 2. Injecting Data into Requests

Middleware can be used to inject additional data into the request that can be used further inside the application. We can take the example of Django's [Authentication Middleware](https://docs.djangoproject.com/en/3.1/ref/middleware/#module-django.contrib.auth.middleware), which adds a `user` object to every valid request. This is a convenient way for the view and other middleware to access details of the logged in user, simply by calling `request.user`.

## 3. Performing Logging, Analytics and Other Miscellaneous Tasks

Some middleware don't actually directly modify the request/response at all, but simply make use of the information contained inside them. In other words, these are 'read-only' middleware. For example, imagine an analytics middleware that stores, in a database, the details of all requests entering the system, such as the associated user, the URL, timestamp, etc. This data would later be analysed to identify useful information or trends. Such a middleware would simply read the request content and create/update some related records in the database, then allow it to transparently pass through. Another example would be a usage monitoring middleware that tracks how much of their usage quota a user has exhausted.

# Middleware in Django
The structure of a basic middleware in Django looks like this:

```python
class ExampleMiddleware:

    def _init_(self, get_response):
        self.get_response = get_response

    def _call_(self, request):

        # Code that is executed in each request before the view is called

        response = self.get_response(request)

        # Code that is executed in each request after the view is called
        return response

    def process_view(self, request, view_func, view_args, view_kwargs):
        # This code is executed just before the view is called

    def process_exception(self, request, exception):
        # This code is executed if an exception is raised

    def process_template_response(self, request, response):
        # This code is executed if the response contains a render() method
        return response
```

The first method, `__init__`, is the constructor for our Python class. It is called only once, at the time the server starts up. Here is where we perform any initializations or other one-time tasks we may want to do.

As you can see from the code, our constructor has one parameter, the `get_response` function. This function is passed to our middleware by the Django framework, and its purpose is to pass the request object over to the next middleware, and the get the value of the response.

The second method, `__call__` allows an object of our class to be called like a function! That is, it turns it into a [callable](https://dev.to/superfola/call-me-maybe-an-explanations-of-python-callables-4fmc). This is where we put our actual middleware logic. This method is called by the Django framework to invoke our middleware.

The other three are special 'hook' methods that allow you to invoke your middleware under specific conditions. Note that these are optional, and you can implement them only if you require the functionality they provide. Only the first two methods, `__init__` and `__call__`, are required by the Django framework for your middleware to work properly.

Let's take a look at the hook methods in more detail.

# The Django Middleware Hook Methods

## process_view
This method is called each time Django receives a request and routes it to a view. How is it different from the `__call__` method? Well, here we have access to the view function that Django is routing the request to along with any further arguments (args and kwargs) that will be passed to it. For example, path parameters and their values are usually passed in kwargs, so in case our middleware needs access to them, it must implement the `process_view` method.

The method must either return `None`, in which case Django will continue processing the request as usual, or an HttpResponse, in which case it will immediately return with that response.

## process_exception
This method is called whenever a view raises an exception that isn't caught within the view itself. Hence, process_exception is invoked _after_ the request has reached and returned from the view.

Similar to the above, the. `process_exception` method must either return `None` or an HttpResponse.

## process_template_response
This method is also invoked after the view has finished executing. It is only called if the resultant response contains a `render()` method, which indicates a template is being rendered. You can use this method to alter the content of the template, including its context data, if required.

# Registering Middleware
Once you have written your custom middleware class, it has to be registered with your Django project in order to add it to the sequence of middleware each request is passed through. To do this, simply add it as an entry in the `MIDDLEWARE` list in your main `settings.py` file:

```python
MIDDLEWARE = [
    'django.middleware.security.SecurityMiddleware',
    'django.contrib.sessions.middleware.SessionMiddleware',
    .
    .
    .
    # Add your custom middleware to this list
]
```

The entry should contain the full path to your middleware class, in string form.

Note that the order in which middleware are present in this list is very important. This is because some middleware might depend upon another to function properly, or might even impede the proper functioning of another. Most security-related middleware tend to reside towards the beginning of the list, so they can catch and filter out potentially harmful requests early. As a rule of thumb, most custom middleware are added to the end of the list. However, this is not always true, especially if, as noted above, your custom middleware is seecurity-related. You must decide where to place your middleware entry on a case-by-case basis.

# Learning By Practice

Let's create three example middleware classes that will cover all three use-cases we saw earlier. One will keep a record of the number of requests handled and exceptions raised by the server. The second will detect and inject the [user agent](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/User-Agent) information into the request. Finally, the last one will filter out requests from certain unsupported user agents.

>💡 Note: we'll only be covering the middleware in this practical section, not the setup of the rest of the Django project. If you're unfamiliar with the basics of Django, the [official tutorial](https://docs.djangoproject.com/en/3.1/intro/tutorial01/) is a great place to start.

## CountRequestsMiddleware

Our `CountRequestsMiddleware` looks like this:

```python
class CountRequestsMiddleware:

    def __init__(self, get_response):
        self.get_response = get_response
        self.count_requests = 0
        self.count_exceptions = 0

    def __call__(self, request, *args, **kwargs):
        self.count_requests += 1
        logger.info(f"Handled {self.count_requests} requests so far")
        return self.get_response(request)

    def process_exception(self, request, exception):
        self.count_exceptions += 1
        logger.error(f"Encountered {self.count_exceptions} exceptions so far")
```

In the constructor, we are initializing two variables, `count_requests` and `count_exceptions`. In the `__call__` method, which is invoked with each request, we are incrementing the value of `count_requests` and also logging it.

We have also defined the `process_exception` method, where we similarly increment and log the value of `count_exceptions`. We are not concerned with the specific type of exception here, although if we wanted to, we could track each type individually by inspecting the value of the `exception` argument.

We are not defining the other hook methods, since we don't really require them.

## SetUserAgentMiddleware

In HTTP, the user agent information is present in the `User-Agent` header. We will make use of the [`user-agents`](https://pypi.org/project/user-agents/) Python package to parse this header string and extract meaningful information from it.

```python
class SetUserAgentMiddleware:

    def __init__(self, get_response):
        self.get_response = get_response

    def __call__(self, request, *args, **kwargs):
        request.user_agent = user_agents.parse(request.META["HTTP_USER_AGENT"])
        return self.get_response(request)
```

Here, we're grabbing the value of the user agent header (`request.META["HTTP_USER_AGENT"]`) and parsing it using the library. Then, we are setting the result as an attribute of the same `request` object, so that later middlewares and views can access it.

>💡 Note: this middleware is basically a reimplimentation of the middleware in the [`django-user-agents`](https://pypi.org/project/django-user-agents/) package. While this is fine for learning purposes, in a real-world application always make use of readily available solutions. Don't reinvent the wheel!

## BlockMobileMiddleware

Say we're writing a webapp that is only compatible with desktop browsers for some reason (perhaps it's an online game that requires mouse support). We would need some way to detect and block requests that come from mobile browsers. Since we have already written the `SetUserAgentMiddleware` middleware, we can take advantage of it and simply write another middleware that checks the value of `request.user_agent` and returns immediately if it is unsupported. This way, we won't have to complicate our view code with user agent checks, since we can guarantee that any request that reaches our it comes from a supported browser.

```python
class BlockMobileMiddleware:

    def __init__(self, get_response):
        self.get_response = get_response

    def __call__(self, request, *args, **kwargs):
        if request.user_agent.is_mobile:
            return HttpResponse("Mobile devices are not supported", status=400)
        return self.get_response(request)
```

Here, we simply check the value of the boolean `is_mobile` property and, if it is True, immediately return a 400 (Bad Request) response with an appropriate error message.

Note that this depends upon the `request.user_agent` attribute which is injected by `SetUserAgentMiddleware`. Therefore, `SetUserAgentMiddleware` must always come before `BlockMobileMiddleware` in the middleware list inside your settings file.

# Conclusion



I hope you enjoyed this post and found it informative. Please leave a comment below if you spot an error or have something to add.

The complete source code for the example is available on GitHub [here](https://github.com/BikramjeetSingh/Django-Middleware-Tutorial-Example).

If you would like to learn more about Django middleware, I cannot recommend the [official documentation](https://docs.djangoproject.com/en/3.1/topics/http/middleware/) enough!

Cover image by <a href="https://pixabay.com/users/mustangjoe-2162920/?utm_source=link-attribution&amp;utm_medium=referral&amp;utm_campaign=image&amp;utm_content=1236578">MustangJoe</a> from <a href="https://pixabay.com/?utm_source=link-attribution&amp;utm_medium=referral&amp;utm_campaign=image&amp;utm_content=1236578">Pixabay</a>