Среднее программное обеспечение
Среднее программное обеспечение — это фреймворк хуков в обработке запросов/ответов 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 применяет среднее программное обеспечение в порядке его определения в MIDDLEWARE, сверху вниз.
Можно представить это как лук: каждый класс среднего программного обеспечения — это «слой», который оборачивает представление, которое находится в ядре лука. Если запрос проходит через все слои лука (каждый вызывает get_response, чтобы передать запрос в следующий слой), до представления в ядре, ответ затем пройдет через каждый слой (в обратном порядке) по пути наружу.
Если один из слоёв решит прервать процесс и вернуть ответ, не вызвав свой get_response, ни один из слоев лука внутри этого слоя (включая представление) не увидит запрос или ответ. Ответ вернётся только через те же слои, через которые прошёл запрос.
Другие хуки middleware
Помимо базового шаблона middleware запроса/ответа, описанного ранее, вы можете добавить три других специальных метода в middleware на основе класса:
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() middleware и затем соответствующее представление. Если он возвращает объект HttpResponse, Django не будет беспокоиться о вызове соответствующего представления; он применит middleware для ответа к этому объекту HttpResponse и вернёт результат.
Примечание
Доступ к request.POST внутри middleware до запуска представления или в process_view() предотвратит возможность любого представления, выполняемого после middleware, изменить обработчики загрузки для запроса, и обычно следует этого избегать.
Класс CsrfViewMiddleware можно рассматривать как исключение, так как он предоставляет декораторы csrf_exempt() и csrf_protect(), которые позволяют представлениям явно контролировать, в какой момент должна произойти проверка CSRF.
process_exception()
-
process_exception(request, exception)
request — это объект HttpRequest. exception — это объект Exception, вызванный функцией представления.
Django вызывает process_exception(), когда представление вызывает исключение. process_exception() должен вернуть либо None, либо объект HttpResponse. Если он возвращает объект HttpResponse, шаблон ответа и middleware ответа будут применены, и полученный ответ будет возвращён браузеру. В противном случае включается обработка исключений по умолчанию.
Опять же, middleware запускаются в обратном порядке во время фазы ответа, которая включает process_exception. Если middleware для обработки исключений возвращает ответ, методы process_exception классов middleware выше этого middleware вообще не будут вызваны.
process_template_response()
-
process_template_response(request, response)
request — это объект HttpRequest. response — это объект TemplateResponse (или эквивалент), возвращённый представлением Django или middleware.
process_template_response() вызывается сразу после завершения выполнения представления, если у объекта ответа есть метод render(), указывающий на то, что это TemplateResponse или эквивалент.
Он должен вернуть объект ответа, реализующий метод render. Он может изменить предоставленный объект response, изменив response.template_name и response.context_data, или он может создать и вернуть совершенно новый объект TemplateResponse или эквивалент.
Вам не нужно явно рендерить ответы — ответы будут автоматически рендериться после вызова всех middleware для ответа.
Middleware запускаются в обратном порядке во время фазы ответа, которая включает process_template_response().
Обработка потоковых ответов
В отличие от HttpResponse, StreamingHttpResponse не имеет атрибута content. В результате middleware больше не могут предполагать, что все ответы будут иметь атрибут content. Если им нужен доступ к содержимому, они должны проверить наличие потокового ответа и соответствующим образом скорректировать своё поведение:
if response.streaming:
response.streaming_content = wrap_streaming_content(response.streaming_content)
else:
response.content = alter_content(response.content)
Примечание
Следует предполагать, что streaming_content слишком велик, чтобы поместиться в памяти. Middleware ответа может обернуть его в новый генератор, но не должен его потреблять. Обычно обёртывание реализуется следующим образом:
def wrap_streaming_content(content):
for chunk in content:
yield alter_content(chunk)
StreamingHttpResponse поддерживает как синхронные, так и асинхронные итераторы. Функция обертки должна соответствовать. Проверьте StreamingHttpResponse.is_async, если вашему middleware нужно поддерживать оба типа итераторов.
Обработка исключений
Django автоматически преобразует исключения, вызванные представлением или middleware, в соответствующий HTTP-ответ с кодом состояния ошибки. Определённые исключения преобразуются в коды состояния 4xx, а неизвестное исключение — в код состояния 500.
Это преобразование происходит до и после каждого middleware (можно представить себе как тонкую плёнку между каждым слоем лука), так что каждый middleware всегда может положиться на получение какого-либо HTTP-ответа после вызова своего вызываемого объекта get_response. Middleware не нужно беспокоиться об обертывании своего вызова get_response в try/except и обработке исключения, которое может быть вызвано последующим middleware или представлением. Даже если следующий middleware в цепочке вызывает исключение Http404, например, ваш middleware не увидит это исключение; вместо этого он получит объект 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, который вам передан, функцией-генератором (coroutine function), используя 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.2/topics/http/middleware/