Middleware

Middleware - это система хуков для обработки запросов/ответов в Django. Это легкая, низкоуровневая система «плагинов» для глобального изменения входных или выходных данных Django.

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

Этот документ объясняет, как работает промежуточное ПО, как активировать промежуточное ПО и как написать свое собственное промежуточное ПО. Django поставляется с некоторым встроенным промежуточным ПО, которое вы можете использовать прямо из коробки. Они документированы в разделе built-in middleware reference.

Написание собственного промежуточного программного обеспечения

Фабрика промежуточного ПО - это вызываемая переменная, которая принимает вызываемую переменную get_response и возвращает промежуточное ПО. Промежуточное ПО - это вызываемый объект, который принимает запрос и возвращает ответ, как и представление.

Промежуточное ПО можно записать в виде функции, которая выглядит следующим образом:

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

Или его можно записать как класс, экземпляры которого можно вызывать, например, так:

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

Вызываемый элемент get_response, предоставляемый Django, может быть фактическим представлением (если это последний из перечисленных промежуточных элементов), или это может быть следующий промежуточный элемент в цепочке. Текущий промежуточный модуль не должен знать или заботиться о том, что именно он собой представляет, просто он представляет то, что будет следующим.

Вышеприведенное является небольшим упрощением - вызываемый модуль get_response для последнего промежуточного модуля в цепочке не будет собственно представлением, а скорее методом-оберткой из обработчика, который позаботится о применении view middleware, вызове представления с соответствующими аргументами URL и применении template-response и exception промежуточных модулей.

Middleware может поддерживать либо только синхронный Python (по умолчанию), либо только асинхронный Python, либо оба варианта. Смотрите Поддержка асинхронного режима для подробностей о том, как рекламировать то, что вы поддерживаете, и знать, какой тип запроса вы получаете.

Middleware может находиться в любом месте вашего пути Python.

__init__(get_response)

Фабрики промежуточного ПО должны принимать аргумент get_response. Вы также можете инициализировать некоторое глобальное состояние для промежуточного ПО. Помните о нескольких предостережениях:

  • Django инициализирует ваш промежуточный модуль только аргументом get_response, поэтому вы не можете определить __init__() как требующий каких-либо других аргументов.
  • В отличие от метода __call__(), который вызывается один раз на каждый запрос, __init__() вызывается только один раз, когда запускается веб-сервер.

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

Иногда полезно определить во время запуска, следует ли использовать часть промежуточного программного обеспечения. В таких случаях метод __init__() вашего промежуточного ПО может поднять MiddlewareNotUsed. Тогда Django удалит этот middleware из процесса middleware и запишет отладочное сообщение в логгер django.request, когда DEBUG будет True.

Активация промежуточного программного обеспечения

Чтобы активировать компонент промежуточного ПО, добавьте его в список MIDDLEWARE в настройках Django.

В MIDDLEWARE каждый компонент middleware представлен строкой: полный путь Python к классу или имени функции фабрики middleware. Например, вот значение по умолчанию, созданное 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',
]

Установка Django не требует никакого промежуточного программного обеспечения - MIDDLEWARE может быть пустым, если вы хотите - но настоятельно рекомендуется, чтобы вы по крайней мере использовали CommonMiddleware.

Порядок в MIDDLEWARE имеет значение, потому что промежуточная программа может зависеть от других промежуточных программ. Например, AuthenticationMiddleware хранит аутентифицированного пользователя в сессии; поэтому он должен выполняться после SessionMiddleware. Смотрите Заказ промежуточного программного обеспечения для некоторых общих подсказок о порядке следования классов промежуточного ПО Django.

Порядок и слоистость промежуточного программного обеспечения

На этапе запроса, перед вызовом представления, Django применяет промежуточное ПО в порядке, определенном в MIDDLEWARE, сверху вниз.

Можно представить это как лук: каждый класс промежуточного ПО - это «слой», который оборачивает представление, находящееся в сердцевине лука. Если запрос проходит через все слои лука (каждый из которых вызывает get_response для передачи запроса в следующий слой), до самого представления в сердцевине, то ответ будет проходить через каждый слой (в обратном порядке) на обратном пути.

Если один из слоев решит замкнуться и вернуть ответ без вызова своего get_response, ни один из слоев луковицы внутри этого слоя (включая представление) не увидит ни запроса, ни ответа. Ответ вернется только через те же слои, через которые прошел запрос.

Другие крючки промежуточного ПО

Помимо базового шаблона промежуточного ПО «запрос/ответ», описанного ранее, вы можете добавить еще три специальных метода к промежуточному ПО на основе классов:

process_view()

process_view(request, view_func, view_args, view_kwargs)

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

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

Он должен вернуть либо None, либо объект HttpResponse. Если он возвращает None, Django продолжит обработку этого запроса, выполняя любое другое промежуточное ПО process_view() и, затем, соответствующее представление. Если он возвращает объект HttpResponse, Django не будет беспокоиться о вызове соответствующего представления; он применит промежуточное ПО ответа к этому HttpResponse и вернет результат.

Примечание

Доступ к request.POST внутри промежуточного программного обеспечения до запуска представления или в process_view() не позволит любому представлению, запущенному после промежуточного программного обеспечения, получить доступ к modify the upload handlers for the request, и обычно его следует избегать.

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

process_exception()

process_exception(request, exception)

request - это объект HttpRequest. exception - это объект Exception, поднятый функцией view.

Django вызывает process_exception(), когда представление вызывает исключение. process_exception() должен вернуть либо None, либо объект HttpResponse. Если он возвращает объект HttpResponse, то будет применен шаблонный ответ и промежуточное ПО ответа, и полученный ответ будет возвращен браузеру. В противном случае включается default exception handling.

Опять же, промежуточные программы запускаются в обратном порядке во время фазы ответа, которая включает process_exception. Если промежуточное ПО исключения возвращает ответ, то методы process_exception классов промежуточного ПО, расположенных выше этого промежуточного ПО, вообще не будут вызываться.

process_template_response()

process_template_response(request, response)

request - это объект HttpRequest. response - это объект TemplateResponse (или эквивалент), возвращаемый представлением Django или промежуточным ПО.

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)

Обработка исключений

Django автоматически преобразует исключения, вызванные представлением или промежуточным ПО, в соответствующий HTTP-ответ с кодом статуса ошибки. Certain exceptions преобразуются в код состояния 4xx, в то время как неизвестное исключение преобразуется в код состояния 500.

Это преобразование происходит до и после каждого промежуточного ПО (можно считать, что это тонкая пленка между слоями лука), так что каждое промежуточное ПО всегда может рассчитывать на получение какого-либо HTTP-ответа после вызова его вызываемой переменной get_response. Middleware не нужно беспокоиться о том, чтобы обернуть свой вызов get_response в try/except и обработать исключение, которое могло быть вызвано более поздним middleware или представлением. Даже если следующее промежуточное ПО в цепочке вызовет, например, исключение Http404, ваше промежуточное ПО не увидит этого исключения; вместо этого оно получит объект HttpResponse с status_code из 404.

Вы можете установить DEBUG_PROPAGATE_EXCEPTIONS в True, чтобы пропустить это преобразование и распространить исключения вверх.

Поддержка асинхронного режима

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

По умолчанию Django предполагает, что ваше промежуточное ПО способно обрабатывать только синхронные запросы. Чтобы изменить эти предположения, установите следующие атрибуты для вашей фабричной функции или класса промежуточного ПО:

  • sync_capable - булево значение, указывающее, может ли промежуточное ПО обрабатывать синхронные запросы. По умолчанию имеет значение True.
  • async_capable - булево значение, указывающее, может ли промежуточное ПО обрабатывать асинхронные запросы. По умолчанию имеет значение False.

If your middleware has both sync_capable = True and async_capable = True, then Django will pass it the request without converting it. In this case, you can work out if your middleware will receive async requests by checking if the get_response object you are passed is a coroutine function, using asyncio.iscoroutinefunction.

Модуль django.utils.decorators содержит декораторы sync_only_middleware(), async_only_middleware() и sync_and_async_middleware(), которые позволяют применять эти флаги к фабричным функциям промежуточного ПО.

Возвращаемая вызываемая функция должна соответствовать синхронному или асинхронному характеру метода get_response. Если у вас асинхронный get_response, вы должны вернуть функцию корутины (async def).

Методы process_view, process_template_response и process_exception, если они предусмотрены, также должны быть адаптированы к режиму sync/async. Однако, Django будет адаптировать их индивидуально, если вы этого не сделаете, с дополнительным снижением производительности.

Вот пример того, как создать промежуточную функцию, поддерживающую оба варианта:

import asyncio
from django.utils.decorators import sync_and_async_middleware

@sync_and_async_middleware
def simple_middleware(get_response):
    # One-time configuration and initialization goes here.
    if asyncio.iscoroutinefunction(get_response):
        async def middleware(request):
            # Do something here!
            response = await get_response(request)
            return response

    else:
        def middleware(request):
            # Do something here!
            response = get_response(request)
            return response

    return middleware

Примечание

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

Таким образом, даже если вы обертываете асинхронное представление, вы можете быть вызваны в режиме синхронизации, если между вами и представлением находится другое, синхронное промежуточное ПО.

Обновление промежуточного программного обеспечения, существовавшего до версии Django 1.10

class django.utils.deprecation.MiddlewareMixin

Django предоставляет django.utils.deprecation.MiddlewareMixin для облегчения создания классов промежуточного ПО, которые совместимы как с MIDDLEWARE, так и со старым MIDDLEWARE_CLASSES, и поддерживают синхронные и асинхронные запросы. Все классы промежуточного ПО, включенные в Django, совместимы с обеими настройками.

Миксин предоставляет метод __init__(), который требует аргумент get_response и сохраняет его в self.get_response.

Метод __call__():

  1. Вызывает self.process_request(request) (если определено).
  2. Вызывает self.get_response(request) для получения ответа от последующего промежуточного ПО и представления.
  3. Вызывает self.process_response(request, response) (если определено).
  4. Возвращает ответ.

Если используется MIDDLEWARE_CLASSES, метод __call__() никогда не будет использоваться; Django вызывает process_request() и process_response() напрямую.

В большинстве случаев наследования от этого миксина будет достаточно, чтобы сделать промежуточное ПО старого типа совместимым с новой системой с достаточной обратной совместимостью. Новая семантика замыкания будет безвредна или даже полезна для существующего промежуточного ПО. В некоторых случаях класс промежуточного ПО может потребовать некоторых изменений для адаптации к новой семантике.

Это поведенческие различия между использованием MIDDLEWARE и MIDDLEWARE_CLASSES:

  1. При MIDDLEWARE_CLASSES у каждого промежуточного ПО всегда будет вызван его метод process_response, даже если предыдущее промежуточное ПО замыкается, возвращая ответ из своего метода process_request. При MIDDLEWARE промежуточное ПО ведет себя более похоже на луковицу: слои, через которые проходит ответ на выходе, - это те же слои, которые видели запрос на входе. Если промежуточное ПО замыкается, то ответ увидит только это промежуточное ПО и те, что были до него в MIDDLEWARE.
  2. При MIDDLEWARE_CLASSES, process_exception применяется к исключениям, вызванным из метода промежуточного программного обеспечения process_request. При MIDDLEWARE, process_exception применяется только к исключениям, вызванным из представления (или из render метода TemplateResponse). Исключения, вызванные из промежуточного ПО, преобразуются в соответствующий HTTP-ответ и затем передаются следующему промежуточному ПО.
  3. При MIDDLEWARE_CLASSES, если метод process_response вызывает исключение, методы process_response всех предыдущих промежуточных программ пропускаются, и всегда возвращается HTTP-ответ 500 Internal Server Error (даже если вызванное исключение было, например, Http404). При MIDDLEWARE исключение, поднятое промежуточным ПО, будет немедленно преобразовано в соответствующий HTTP-ответ, а затем следующее по порядку промежуточное ПО увидит этот ответ. Промежуточное ПО никогда не пропускается из-за того, что промежуточное ПО подняло исключение.
Вернуться на верх