Django Middleware Explained

What is Django middleware

Middleware is a framework-level hook for handling requests (Requests) and responses (Responses) in Django, a lightweight, low-level plugin system for changing Django’s input and output globally.

When a user performs an action in a website, the process is when the user sends an HTTP request to the website (Request); and the website returns relevant web content based on the user’s action, a process called response processing (Response). In the request-to-response process, when Django receives a user request, it first processes the request information through middleware, performs the relevant processing, and then returns the result to the user.

As you can clearly see from the above diagram, the role of middleware is to process user request information and return response content. Developers can customize the middleware according to their development needs, just add the custom middleware to the configuration property MIDDLEWARE to activate it, in general, the default middleware configuration of Django can meet most of the development needs.

The process of defining middleware

The middleware is configured in the configuration property MIDDLEWARE in settings.py. In creating the project, Django has configured seven middleware by default, and the description of each middleware is as follows.

  • SecurityMiddleware: built-in security mechanism to protect the user’s communication with the site security
  • SessionMiddleware: session session function
  • CommonMiddleware: processing request information and normalizing request content
  • CsrfViewMiddleware: enable CSRF protection
  • AuthenticationMiddleware:Enables built-in user authentication system
  • MessageMiddleware:Enables the built-in message alert function
  • XFameOptionsMiddleware: prevent malicious programs from computer and hijacking

In order to get a deeper understanding of the middleware definition process, we open and view the source code file of a middleware in Pycharm to analyze the middleware definition process, taking the middleware SessionMiddleware as an example, the source code is as follows.

class SessionMiddleware(MiddlewareMixin):
    def __init__(self, get_response=None):
        self.get_response = get_response
        engine = import_module(settings.SESSION_ENGINE)
        self.SessionStore = engine.SessionStore

MiddlewareMiddleware inherits from its parent class MiddlewareMixin, which only defines the functions init and call, while MiddlewareMiddleware defines the hook functions process_request and process_response in addition to overriding init of its parent class.

A complete middleware has 5 hook functions. Django divides the process from user request to website response into phases, and each phase corresponds to the execution of a certain hook function, and each hook function runs as described below.

  • process_request(): The request object is created, but the URL accessed by the user has not yet been matched with the site’s routing address.
  • process_view(): The user’s URL is matched with the routing address, but the view function has not yet been executed.
  • process_exception(): An exception occurs during the execution of the view function, such as a code exception, an active 404 exception, etc.
  • process_response(): the execution of the view function is completed, but the response is not yet returned to the browser
  • process_template_response(): not executed by default, called after the view function completes its operation, unless the response returned by the view function has a render method (which is almost never used and can be ignored)

Practical Cases

First, let’s do the preparation work, create a new project middleware_demo, create a sub-application middleware_app and register it in settings.py, first configure the routing address, the code is as follows.

# middleware_demo.urls.py
urlpatterns = [
    path('middleware/', include('middleware_app.urls')),
]

# middleware_app.urls.py
urlpatterns = [
    path('', views.index, name="index")
]

After the routing configuration is complete, the view function index is created with the following code.

def index(request):
    print("Middleware home page")
    return HttpResponse('Middleware Home')

Once the preparation is done, we create the middlewares.py file in the middleware_app application and fill in the following code.

class FirstMiddleware(MiddlewareMixin):
    def process_request(self, request):
        """
        Generate request object, before route matching
        :param request:
        :return:
            If return response: call the current middleware's process_response processing
            if return None: call the next middleware's process_request processing
        """
        print("firstMiddleware request")

    def process_view(self, request, view_func, view_args, view_kwargs):
        """
        Before the route match is complete and the view function is called
        :param request:
        :param view_func: The view function that the url route matches to
        :param view_args: variable arguments for the view function
        :param view_kwargs: Variable keyword arguments for the view function
        :return:
            If return response: call process_response of the last middleware to start processing
            if return None: call process_view of the next middleware to process
        """
        """"""
        print("firstMiddleware process view")

    def process_exception(self, request, exception):
        """
        When the view function throws an exception
        :param request:
        :param exception: the exception object thrown during the process
        :return:
            If return response: all subsequent process_exceptions are not triggered, but the process_response of the last middleware is called directly
            if return None: the process_exception of the previous middleware is called
        """
        print("firstMiddleware process exception")

    def process_response(self, request, response):
        """
        After the view function is executed, the response content is returned to the browser before
        :param request:
        :param response:
        :return:
            response: call process_response processing of the previous middleware
        """
        print("firstMiddleware process response")
        return response

Then insert the custom middleware into the middleware list in settings.py as follows.

MIDDLEWARE = [
    'django.middleware.security.SecurityMiddleware',
    'django.contrib.sessions.middleware.SessionMiddleware',
    'django.middleware.common.CommonMiddleware',
    'django.middleware.csrf.CsrfViewMiddleware',
    'django.contrib.auth.middleware.AuthenticationMiddleware',
    'django.contrib.messages.middleware.MessageMiddleware',
    'django.middleware.clickjacking.XFrameOptionsMiddleware',
    'middleware_app.middlewares.FirstMiddleware',
]

Because the middleware is sequential, we usually put the custom ones at the end of the list.

Next we visit the route 127.0.0.1/middleware/ to see the log output from the Pycharm console.

firstMiddleware request
firstMiddleware process view
Middleware home page
firstMiddleware process response

We can clearly see the order of request execution, let’s summarize it below.

  1. The user sends the request
  2. Execute process_request
  3. urlconf route matching, find the corresponding view function
  4. Execute the view preprocessing method process_view
  5. View function
  6. process_template_response (if the view function returns a response, there is a render method, if not, this step will not be executed)
  7. execute process_response
  8. return the response to the user

Among them, in the view function and process_template_response processing, if an exception occurs, then the middleware process_exception will be executed in reverse order.

Common custom middleware features

In short, you can use middleware if you have a need to operate on global requests or responses, for example.

  1. IP filtering: return specific response to some specific IP address.
  2. URL filtering: if the user is accessing the login view, it passes; if accessing other views, it needs to detect if there is a session already there and it passes, if not, it returns to the login page. This eliminates the need to write decorators login_required on multiple view functions.
  3. Content compression: response content to achieve gzip compression, return the compressed content to the front-end
  4. CDN: content distribution network to achieve caching, if there is data in the cache to return directly, did not find the cache and then request the view.
  5. URL filtering: a sub-application upgrade suspension, a specific path path request, return a specific page.

Leave a Reply