Среднесвязующий уровень
Среднесвязующий уровень — это фреймворк хуков для обработки запросов/ответов Django. Это легкая, низкоуровневая система «плагинов» для глобального изменения входных или выходных данных Django.
Каждый компонент среднесвязующего уровня отвечает за выполнение определенной функции. Например, Django включает компонент среднесвязующего уровня, AuthenticationMiddleware, который связывает пользователей с запросами с использованием сессий.
В этом документе объясняется, как работает среднесвязующий уровень, как активировать его и как написать свой собственный. Django поставляется с некоторыми встроенными среднесвязующими уровнями, которые можно использовать прямо из коробки. Они описаны в справочнике по встроенным среднесвязующим уровням.
Написание собственного среднесвязующего уровня
Фабрика среднесвязующего уровня — это вызываемый объект, который принимает вызываемый объект 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 для последнего среднесвязующего уровня в цепочке не будет фактическим представлением, а скорее методом-обёрткой от обработчика, который позаботится о применении представлений среднесвязующего уровня, вызове представления с соответствующими аргументами URL и применении среднесвязующего уровня ответа шаблона и среднесвязующего уровня исключений.
Среднесвязующий уровень может поддерживать только синхронный Python (по умолчанию), только асинхронный Python или оба. См. Поддержка асинхронности для получения подробностей о том, как объявлять вашу поддержку и определять вид получаемого запроса.
Среднесвязующие уровни могут находиться в любой части вашего пути Python.
__init__(get_response)
Фабрики среднесвязующих уровней должны принимать аргумент get_response. Вы также можете инициализировать некоторое глобальное состояние для среднесвязующего уровня. Имейте в виду несколько ограничений:
- Django инициализирует ваш среднесвязующий уровень только с аргументом
get_response, поэтому вы не можете определить__init__()как требующий каких-либо других аргументов. - В отличие от метода
__call__(), который вызывается один раз на запрос,__init__()вызывается только один раз при запуске веб-сервера.
Отмечение среднесвязующего уровня как неиспользуемого
Иногда полезно определить во время запуска, нужно ли использовать компонент среднесвязующего уровня. В этих случаях метод __init__() вашего среднесвязующего уровня может вызвать MiddlewareNotUsed. Тогда Django удалит этот среднесвязующий уровень из процесса среднесвязующих уровней и выведет сообщение отладки в логгер django.request, когда DEBUG имеет значение True.
Активация среднесвязующего уровня
Чтобы активировать компонент среднесвязующего уровня, добавьте его в список MIDDLEWARE в ваших настройках Django.
В MIDDLEWARE каждый компонент среднесвязующего уровня представляется строкой: полным путём к классу или имени функции фабрики среднесвязующего уровня в Python. Например, вот значение по умолчанию, созданное 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.
Порядок и слои среднесвязующих уровней
Во время фазы запроса, перед вызовом представления, 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 продолжит обработку этого запроса, выполнив все остальные среднесвязующие уровни и затем соответствующее представление. Если он возвращает объект HttpResponse, Django не будет беспокоиться о вызове соответствующего представления; он применит среднесвязующий уровень ответа к этому объекту HttpResponse и вернёт результат.
Примечание
Доступ к request.POST внутри среднесвязующего уровня до запуска представления или в process_view() предотвратит любое представление, выполняющееся после среднесвязующего уровня, от возможности изменить обработчики загрузки для запроса, и это обычно следует избегать.
Класс CsrfViewMiddleware может рассматриваться как исключение, так как он предоставляет декораторы csrf_exempt() и csrf_protect(), которые позволяют представлениям явно контролировать момент проверки CSRF.
process_exception()
-
process_exception(request, exception)
request — это объект HttpRequest. exception — это объект Exception, поднятый функцией представления.
Django вызывает process_exception() , когда представление вызывает исключение. process_exception() должен вернуть либо None, либо объект HttpResponse. Если он возвращает объект HttpResponse, шаблон ответа и среднесвязующий уровень ответа будут применены, а полученный ответ будет возвращён браузеру. В противном случае, включается обработка исключений по умолчанию.
Опять же, среднесвязующие уровни выполняются в обратном порядке во время фазы ответа, что включает 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)
StreamingHttpResponse допускает использование как синхронных, так и асинхронных итераторов. Функция обёртывания должна соответствовать. Проверьте StreamingHttpResponse.is_async, если вашему промежуточному ПО нужно поддерживать оба типа итераторов.
Обработка исключений
Django автоматически преобразует исключения, поднятые представлением или промежуточным ПО, в соответствующий HTTP-ответ с кодом состояния ошибки. Определённые исключения преобразуются в коды состояния 4xx, а неизвестное исключение преобразуется в код состояния 500.
Это преобразование происходит до и после каждого промежуточного ПО (можно представить это как тонкую плёнку между слоями лука), так что каждое промежуточное ПО всегда может полагаться на получение какого-то HTTP-ответа от вызова своей get_response вызываемой функции. Промежуточное ПО не нужно беспокоиться об обёртке своего вызова get_response в try/except и обработке исключения, которое могло быть поднято последующим промежуточным ПО или представлением. Даже если следующее промежуточное ПО в цепочке поднимет исключение Http404, например, ваше промежуточное ПО не увидит это исключение; вместо этого оно получит объект HttpResponse с кодом состояния status_code 404.
Вы можете установить DEBUG_PROPAGATE_EXCEPTIONS в True, чтобы пропустить это преобразование и передать исключения вверх.
Поддержка асинхронных функций
Промежуточное ПО может поддерживать любую комбинацию синхронных и асинхронных запросов. Django адаптирует запросы к требованиям промежуточного ПО, если оно не может поддерживать оба, но с потерей производительности.
По умолчанию Django предполагает, что ваше промежуточное ПО способно обрабатывать только синхронные запросы. Чтобы изменить эти предположения, установите следующие атрибуты в вашей фабричной функции или классе промежуточного ПО:
-
sync_capable— это логическое значение, указывающее, может ли промежуточное ПО обрабатывать синхронные запросы. По умолчаниюTrue. -
async_capable— это логическое значение, указывающее, может ли промежуточное ПО обрабатывать асинхронные запросы. По умолчаниюFalse.
Если ваше промежуточное ПО имеет и sync_capable = True, и async_capable = True, тогда Django передаст запрос без его преобразования. В этом случае вы можете определить, получит ли ваше промежуточное ПО асинхронные запросы, проверив, является ли объект get_response, который вам передаётся, функцией-генератором, используя asgiref.sync.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, если они предоставлены, также должны быть адаптированы для соответствия режиму синхронности/асинхронности. Однако Django будет индивидуально адаптировать их при необходимости, если вы этого не сделаете, с дополнительной потерей производительности.
Вот пример того, как создать функцию промежуточного ПО, поддерживающую оба режима:
from asgiref.sync import iscoroutinefunction
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 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 оптимизирует стек вызовов промежуточного ПО, чтобы минимизировать переходы синхрон/асинхрон.
Таким образом, даже если вы обёртываете асинхронное представление, вы можете быть вызваны в синхронном режиме, если между вами и представлением есть другое синхронное промежуточное ПО.
При использовании асинхронного промежуточного ПО на основе класса, вы должны убедиться, что экземпляры правильно помечены как функции-генераторы:
from asgiref.sync import iscoroutinefunction, markcoroutinefunction
class AsyncMiddleware:
async_capable = True
sync_capable = False
def __init__(self, get_response):
self.get_response = get_response
if iscoroutinefunction(self.get_response):
markcoroutinefunction(self)
async def __call__(self, request):
response = await self.get_response(request)
# Some logic ...
return response
Обновление промежуточного ПО старого стиля Django 1.10
-
class django.utils.deprecation.MiddlewareMixin
Django предоставляет django.utils.deprecation.MiddlewareMixin для облегчения создания классов промежуточного ПО, совместимых как с MIDDLEWARE, так и со старым стилем MIDDLEWARE_CLASSES, и поддерживающих синхронные и асинхронные запросы. Все классы промежуточного ПО, включённые в Django, совместимы с обоими наборами настроек.
Миксин предоставляет метод __init__(), который требует аргумент get_response и сохраняет его в self.get_response.
Метод __call__():
- Вызывает
self.process_request(request)(если определён). - Вызывает
self.get_response(request)для получения ответа от последующего промежуточного ПО и представления. - Вызывает
self.process_response(request, response)(если определён). - Возвращает ответ.
Если используется с MIDDLEWARE_CLASSES, метод __call__() никогда не будет использован; Django вызывает process_request() и process_response() напрямую.
В большинстве случаев наследование от этого миксина будет достаточно, чтобы сделать промежуточное ПО старого стиля совместимым с новой системой с достаточной обратной совместимостью. Новые семантики обхода будут безвредными или даже полезными для существующего промежуточного ПО. В некоторых случаях классу промежуточного ПО могут потребоваться некоторые изменения для адаптации к новым семантикам.
Вот различия в поведении при использовании MIDDLEWARE и MIDDLEWARE_CLASSES:
- В
MIDDLEWARE_CLASSES, каждый компонент промежуточного ПО будет всегда вызывать свой методprocess_response, даже если более ранний компонент промежуточного ПО сократит цепочку вызовов, вернув ответ из своего методаprocess_request. ВMIDDLEWARE, компоненты промежуточного ПО ведут себя более похожим на слои лука: слои, через которые проходит ответ при выходе, — те же слои, которые видели запрос при входе. Если промежуточное ПО сокращает цепочку вызовов, только это промежуточное ПО и компоненты промежуточного ПО, которые находятся перед ним вMIDDLEWARE, увидят ответ. - В
MIDDLEWARE_CLASSES,process_exceptionприменяется к исключениям, поднятым из метода промежуточного ПОprocess_request. ВMIDDLEWARE,process_exceptionприменяется только к исключениям, поднятым из представления (или из методаrenderобъектаTemplateResponse). Исключения, поднятые из промежуточного ПО, преобразуются в соответствующий HTTP-ответ, а затем передаются следующему промежуточному ПО. - В
MIDDLEWARE_CLASSES, если методprocess_responseподнимет исключение, методыprocess_responseвсех более ранних компонентов промежуточного ПО пропустиваются, и всегда возвращается HTTP-ответ500 Internal Server Error(даже если поднятое исключение было, например,Http404). ВMIDDLEWARE, исключение, поднятое из промежуточного ПО, немедленно преобразуется в соответствующий HTTP-ответ, и затем следующее промежуточное ПО в очереди увидит этот ответ. Промежуточное ПО никогда не пропускается из-за поднятия исключения промежуточным ПО.
© Django Software Foundation and individual contributors
Licensed under the BSD License.
https://docs.djangoproject.com/en/5.1/topics/http/middleware/