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

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

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

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

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

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

В параметре конфигурации MIDDLEWARE_CLASSES каждый промежуточный слой представлен строкой: полный путь для импорта класса промежуточного слоя. Например, вот значение настройки по умолчанию для проекта, который создается с помощью django-admin startproject:

MIDDLEWARE_CLASSES = [
    'django.middleware.security.SecurityMiddleware',
    'django.contrib.sessions.middleware.SessionMiddleware',
    'django.middleware.common.CommonMiddleware',
    'django.middleware.csrf.CsrfViewMiddleware',
    'django.contrib.auth.middleware.AuthenticationMiddleware',
    'django.contrib.auth.middleware.SessionAuthenticationMiddleware',
    'django.contrib.messages.middleware.MessageMiddleware',
    'django.middleware.clickjacking.XFrameOptionsMiddleware',
]

Django не требует никаких промежуточных слоёв для своей работы — MIDDLEWARE_CLASSES может быть пустым — но мы настоятельно рекомендуем использовать хотя бы CommonMiddleware.

Порядок MIDDLEWARE_CLASSES важен, т.к. один промежуточный слой может зависеть от другого. Например, AuthenticationMiddleware сохраняет текущего пользователя в сессии, поэтому должен срабатывать после SessionMiddleware. Смотрите советы, как лучше упорядочить промежуточные слои, в Порядок промежуточных слоёв.

“Хуки” и порядок обработки

Во время обработки запроса, перед вызовом представления, Django применяет промежуточные слои в порядке указанном в MIDDLEWARE_CLASSES, сверху вниз. Доступны два “хука”:

На этапе обработки ответа, после вызова представления, промежуточные слои применяются в обратном порядке, снизу вверх. Доступны три “хука”:

middleware application order

Можете смотреть на это, как на луковицу: каждый промежуточный слой оборачивает представление.

Поведение каждого “хука” описано ниже.

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

Создать свой промежуточный слой очень просто. Это просто класс Python, который предоставляет один или несколько методов:

process_request

process_request(request)

request – это объект HttpRequest.

process_request() вызывается для каждого запроса перед тем, как Django решит какое представление вызывать.

Метод должен вернуть None или объект an HttpResponse. Если вернул None, Django продолжит обработку запроса, вызывая process_request() других промежуточных слоёв, затем метод process_view() промежуточного слоя, и в конце представление. Если вернул объект HttpResponse, Django остановит обработку запроса, применит промежуточные слои для объекта HttpResponse, и вернет результат.

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() вызывается непосредственно перед вызовом представления.

Метод должен вернуть None или объект HttpResponse. Если вернул None, Django продолжит обработку запроса, вызывая process_view() других промежуточных слоёв, а затем представление. Если вернул объект HttpResponse, Django остановит обработку запроса, применит промежуточные слои для объекта HttpResponse, и вернет результат.

Примечание

Обращение к request.POST в промежуточном слое в методе process_request или process_view не позволит в представлении изменять обработчики загрузки файлов, в большинстве случаев лучше так не делать.

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

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().

process_response

process_response(request, response)

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

process_response() вызывается для каждого ответа перед тем, как он будет оптравлен браузеру.

Этот метод должен вернуть объект HttpResponse или StreamingHttpResponse. Он может изменить переданный response, или же создать и вернуть новый экземпляр HttpResponse или StreamingHttpResponse.

В отличии от методов process_request() и process_view(), метод process_response() вызывается всегда, даже если методы process_request() и process_view() этого промежуточного слоя были пропущены (т.к. предыдущий промежуточный слой вернул объект HttpResponse). Это означает, что метод process_response() не может полагаться на действия, выполняемые в process_request().

И не забывайте, что при обработке ответа промежуточные слои выполняются в обратном порядке – снизу вверх. Это значит, что классы, указанные в конце MIDDLEWARE_CLASSES, будет выполнены в первую очередь.

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

В отличии от 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)

process_exception

process_exception(request, exception)

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

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

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

__init__

Большинство классов промежуточного слоя не требуют инициализации т.к. по сути являют набором process_* методов. Если вам необходимо какое-то глобальное состояние, вы можете использовать метод __init__ для его инициализации. Однако помните о некоторых моментах:

  • Django инициализирует промежуточный слой без аргументов, поэтому метод __init__ не должен определять обязательные аргументы.

  • В отличии от методов process_*, которые вызываются при каждом запросе, __init__ вызывается только один раз, когда Web-сервер принимает первый запрос.

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

Иногда полезно в процессе работы приложения указать, что промежуточный слой не должен использоваться. В таком случае в методе __init__ можно вызывать исключение django.core.exceptions.MiddlewareNotUsed. Django исключит этот промежуточный слой из процесса обработки и запишет отладочное сообщение в логгер django.request, если параметр конфигурации DEBUG был установлен в True.

Изменено в Django 1.8:

Ранее, исключение, MiddlewareNotUsed не попадало в журнал.

Советы

  • Класс промежуточного слоя не должен наследоваться от другого класса.

  • Класс может находиться где угодно в путях Python. Главное указать путь к нему в MIDDLEWARE_CLASSES.

  • Вы можете найти примеры кода в промежуточных слоях Django.

  • Если вы создали полезный промежуточный слой, поделитесь ним с сообществом! Напишите нам, и возможно мы добавим его в Django.