Промежуточный слой (Middleware)

Промежуточный слой – это механизм «хуков» для обработки запросов и ответов в Django. Это простая низкоуровневая система «плагинов», которая глобально влияет на ввод и вывод в Django.

Каждый компонент промежуточного слоя отвечает за определенный функционал. Например, Django предоставляет компонент AuthenticationMiddleware, который ассоциирует пользователей с запросами с помощью сессий.

Этот раздел описывает как работают промежуточные слои, как активировать их, и как создать собственные. Django предоставляет набор встроенных промежуточных слоев, которые можно использовать в проекте. Они описаны в разделе о встроенных промежуточных слоях.

Создание собственного промежуточного слоя

A middleware factory is a callable that takes a get_response callable and returns a middleware. A middleware is a callable that takes a request and returns a response, just like a view.

A middleware can be written as a function that looks like this:

def simple_middleware(get_response):
    # One-time configuration and initialization.

    def middleware(request):
        # Code to be executed for each request before
        # the view (and later middleware) are called.

        response = get_response(request)

        # Code to be executed for each request/response after
        # the view is called.

        return response

    return middleware

Or it can be written as a class whose instances are callable, like this:

class SimpleMiddleware:
    def __init__(self, get_response):
        self.get_response = get_response
        # One-time configuration and initialization.

    def __call__(self, request):
        # Code to be executed for each request before
        # the view (and later middleware) are called.

        response = self.get_response(request)

        # Code to be executed for each request/response after
        # the view is called.

        return response

The get_response callable provided by Django might be the actual view (if this is the last listed middleware) or it might be the next middleware in the chain. The current middleware doesn’t need to know or care what exactly it is, just that it represents whatever comes next.

The above is a slight simplification – the get_response callable for the last middleware in the chain won’t be the actual view but rather a wrapper method from the handler which takes care of applying view middleware, calling the view with appropriate URL arguments, and applying template-response and exception middleware.

Middleware can live anywhere on your Python path.

__init__(get_response)

Middleware factories must accept a get_response argument. You can also initialize some global state for the middleware. Keep in mind a couple of caveats:

  • Django initializes your middleware with only the get_response argument, so you can’t define __init__() as requiring any other arguments.
  • Unlike the __call__() method which is called once per request, __init__() is called only once, when the Web server starts.

Помечаем промежуточный слой как неиспользуемый

It’s sometimes useful to determine at startup time whether a piece of middleware should be used. In these cases, your middleware’s __init__() method may raise MiddlewareNotUsed. Django will then remove that middleware from the middleware process and log a debug message to the django.request logger when DEBUG is True.

Подключение промежуточных слоёв

To activate a middleware component, add it to the MIDDLEWARE list in your Django settings.

In MIDDLEWARE, each middleware component is represented by a string: the full Python path to the middleware factory’s class or function name. For example, here’s the default value created by django-admin startproject:

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',
]

A Django installation doesn’t require any middleware — MIDDLEWARE can be empty, if you’d like — but it’s strongly suggested that you at least use CommonMiddleware.

The order in MIDDLEWARE matters because a middleware can depend on other middleware. For instance, AuthenticationMiddleware stores the authenticated user in the session; therefore, it must run after SessionMiddleware. See Порядок промежуточных слоёв for some common hints about ordering of Django middleware classes.

Middleware order and layering

During the request phase, before calling the view, Django applies middleware in the order it’s defined in MIDDLEWARE, top-down.

You can think of it like an onion: each middleware class is a «layer» that wraps the view, which is in the core of the onion. If the request passes through all the layers of the onion (each one calls get_response to pass the request in to the next layer), all the way to the view at the core, the response will then pass through every layer (in reverse order) on the way back out.

If one of the layers decides to short-circuit and return a response without ever calling its get_response, none of the layers of the onion inside that layer (including the view) will see the request or the response. The response will only return through the same layers that the request passed in through.

Other middleware hooks

Besides the basic request/response middleware pattern described earlier, you can add three other special methods to class-based middleware:

process_view()

process_view(request, view_func, view_args, view_kwargs)

request – объект HttpRequest. view_func– функция представления, которую Django собирается вызвать для обработки запроса. (Это объект функции, а не название.) view_args – список позиционных аргументов, которые будут переданы в функцию представления, view_kwargs – словарь именованных аргументов. Ни view_args, ни view_kwargs не включают первый аргумент представления (request).

process_view() вызывается непосредственно перед вызовом представления.

It should return either None or an HttpResponse object. If it returns None, Django will continue processing this request, executing any other process_view() middleware and, then, the appropriate view. If it returns an HttpResponse object, Django won’t bother calling the appropriate view; it’ll apply response middleware to that HttpResponse and return the result.

Примечание

Accessing request.POST inside middleware before the view runs or in process_view() will prevent any view running after the middleware from being able to modify the upload handlers for the request, and should normally be avoided.

Класс CsrfViewMiddleware можно считать исключением, т.к. он предоставляет декораторы csrf_exempt() и csrf_protect(), которые позволяют представлениям явно контролировать на каком этапе выполнять CSRF проверку.

process_exception()

process_exception(request, exception)

request – объект HttpRequest. exceptionException, вызванное функцией представления.

Django calls process_exception() when a view raises an exception. process_exception() should return either None or an HttpResponse object. If it returns an HttpResponse object, the template response and response middleware will be applied and the resulting response returned to the browser. Otherwise, default exception handling kicks in.

Again, middleware are run in reverse order during the response phase, which includes process_exception. If an exception middleware returns a response, the process_exception methods of the middleware classes above that middleware won’t be called at all.

process_template_response()

process_template_response(request, response)

request – объект HttpRequest. response – объект TemplateResponse (или аналогичный), который вернуло представление или промежуточный слой.

process_template_response() вызывается после выполнение представления, если объект ответа содержит метод render(), что указывает на TemplateResponse или аналог.

Этот метод должен вернуть объект ответа, который содержит метод render. Он может изменить переданный объект response, изменив response.template_name или response.context_data, или же создать новый экземпляр класса TemplateResponse или аналогичного.

Нет необходимости явно рендерить объекты ответов – они будут автоматически отрендерены после выполнения всех промежуточных слоёв.

Промежуточные слои выполняются в обратном порядке при обработке ответа, это относится и к методу process_template_response().

Работа с потоковыми ответами

В отличии от HttpResponse StreamingHttpResponse не содержит атрибут content. Поэтому промежуточные слои больше не могут полагаться на то, что все объекта ответа будут содержать атрибут content. Поэтому необходимо проверять тип ответа:

if response.streaming:
    response.streaming_content = wrap_streaming_content(response.streaming_content)
else:
    response.content = alter_content(response.content)

Примечание

Подразумевается, что streaming_content слишком большой, чтобы храниться в памяти. Промежуточный слой может обернуть его в новый генератор, но не загружать его полностью. Генератор-обертка обычно выглядит следующим образом:

def wrap_streaming_content(content):
    for chunk in content:
        yield alter_content(chunk)

Exception handling

Django automatically converts exceptions raised by the view or by middleware into an appropriate HTTP response with an error status code. Certain exceptions are converted to 4xx status codes, while an unknown exception is converted to a 500 status code.

This conversion takes place before and after each middleware (you can think of it as the thin film in between each layer of the onion), so that every middleware can always rely on getting some kind of HTTP response back from calling its get_response callable. Middleware don’t need to worry about wrapping their call to get_response in a try/except and handling an exception that might have been raised by a later middleware or the view. Even if the very next middleware in the chain raises an Http404 exception, for example, your middleware won’t see that exception; instead it will get an HttpResponse object with a status_code of 404.

Upgrading pre-Django 1.10-style middleware

class django.utils.deprecation.MiddlewareMixin

Django provides django.utils.deprecation.MiddlewareMixin to ease creating middleware classes that are compatible with both MIDDLEWARE and the old MIDDLEWARE_CLASSES. All middleware classes included with Django are compatible with both settings.

The mixin provides an __init__() method that accepts an optional get_response argument and stores it in self.get_response.

The __call__() method:

  1. Calls self.process_request(request) (if defined).
  2. Calls self.get_response(request) to get the response from later middleware and the view.
  3. Calls self.process_response(request, response) (if defined).
  4. Returns the response.

If used with MIDDLEWARE_CLASSES, the __call__() method will never be used; Django calls process_request() and process_response() directly.

In most cases, inheriting from this mixin will be sufficient to make an old-style middleware compatible with the new system with sufficient backwards-compatibility. The new short-circuiting semantics will be harmless or even beneficial to the existing middleware. In a few cases, a middleware class may need some changes to adjust to the new semantics.

These are the behavioral differences between using MIDDLEWARE and MIDDLEWARE_CLASSES:

  1. Under MIDDLEWARE_CLASSES, every middleware will always have its process_response method called, even if an earlier middleware short-circuited by returning a response from its process_request method. Under MIDDLEWARE, middleware behaves more like an onion: the layers that a response goes through on the way out are the same layers that saw the request on the way in. If a middleware short-circuits, only that middleware and the ones before it in MIDDLEWARE will see the response.
  2. Under MIDDLEWARE_CLASSES, process_exception is applied to exceptions raised from a middleware process_request method. Under MIDDLEWARE, process_exception applies only to exceptions raised from the view (or from the render method of a TemplateResponse). Exceptions raised from a middleware are converted to the appropriate HTTP response and then passed to the next middleware.
  3. Under MIDDLEWARE_CLASSES, if a process_response method raises an exception, the process_response methods of all earlier middleware are skipped and a 500 Internal Server Error HTTP response is always returned (even if the exception raised was e.g. an Http404). Under MIDDLEWARE, an exception raised from a middleware will immediately be converted to the appropriate HTTP response, and then the next middleware in line will see that response. Middleware are never skipped due to a middleware raising an exception.