Middleware Django: пользовательское ПО промежуточного слоя
Проектируя высокомасштабируемые веб-приложения очень важно знать о каждом нюансе в том, как работает связующее, межплатформенное, промежуточное программное обеспечение: Python Django middleware.
Из руководства вы узнаете всё о связующем программном обеспечении в Django, зачем его использовать, как оно работает и как настроить пользовательский Django middleware.
Ознакомьтесь с оглавлением:
1. Что такое Middleware?
С точки зрения пользователя 👨💼 — Middleware (программное обеспечение промежуточного слоя, связующее, межплатформенное ПО) — это словно мост, связывающий между собой две части программы или системы.
В технических терминах 👨💻 — Django Middleware — это промежуточный слой между запросом и ответом, это легкая, низкоуровневая система “плагинов” для глобального изменения входных или выходных данных. Каждый компонент промежуточного слоя отвечает за выполнение определенной функции.
2. Как работает Django Middleware?
Когда пользователь инициирует запрос из вашего приложения, создается обработчик WSGI, выполняющий следующие действия по порядку:
4. Пользовательский Django Middleware
Django предоставляет два типа связующего программного обеспечения:
Стандартное связующее ПО предоставляется по умолчанию, когда вы создаете свой новый Django-проект; проверить список Middleware по умолчанию можно в модуле settings.py из корневой директории проекта:
Встроенное промежуточное ПО Django по умолчанию
Пользовательское связующее ПО — это ваши собственные Middleware для использования во всем проекте. Давайте рассмотрим, как их писать:
Пользовательский Django Middleware на основе функции:
Пользовательский Django Middleware на основе класса:
Напоследок перейдите в корневую директорию проекта и добавьте ваше новое пользовательское промежуточное ПО в список MIDDLEWARE из модуля settings.py :
Добавление пользовательского Middleware в файл settings.py
Всё готово! ✔
Воспользуйтесь на практике новым пользовательским связующим ПО.
Документация Django 1.7
Промежуточный слой – это механизм “хуков” для обработки запросов и ответов в Django. Это простая низкоуровневая система “плагинов”, которая глобально влияет на ввод и вывод в Django.
Этот раздел описывает как работают промежуточные слои, как активировать их, и как создать собственные. Django предоставляет набор встроенных промежуточных слоев, которые можно использовать в проекте. Они описаны в разделе о встроенных промежуточных слоях.
Подключение промежуточных слоёв¶
“Хуки” и порядок обработки¶
На этапе обработки ответа, после вызова представления, промежуточные слои применяются в обратном порядке, снизу вверх. Доступны три “хука”:
Можете смотреть на это, как на луковицу: каждый промежуточный слой оборачивает представление.
Поведение каждого “хука” описано ниже.
Создание собственного промежуточного слоя¶
Создать свой промежуточный слой очень просто. Это просто класс Python, который предоставляет один или несколько методов:
process_request ¶
process_request() вызывается для каждого запроса перед тем, как Django решит какое представление вызывать.
process_view ¶
process_view() вызывается непосредственно перед вызовом представления.
process_template_response ¶
Нет необходимости явно рендерить объекты ответов – они будут автоматически отрендерены после выполнения всех промежуточных слоёв.
process_response ¶
process_response() вызывается для каждого ответа перед тем, как он будет оптравлен браузеру.
Работа с потоковыми ответами¶
Подразумевается, что streaming_content слишком большой, чтобы храниться в памяти. Промежуточный слой может обернуть его в новый генератор, но не загружать его полностью. Генератор-обертка обычно выглядит следующим образом:
process_exception ¶
__init__ ¶
Большинство классов промежуточного слоя не требуют инициализации т.к. по сути являют набором process_* методов. Если вам необходимо какое-то глобальное состояние, вы можете использовать метод __init__ для его инициализации. Однако помните о некоторых моментах:
Django инициализирует промежуточный слой без аргументов, по этому метод __init__ не должен определять обязательные аргументы.
Помечаем промежуточный слой как неиспользуемый¶
Советы¶
Класс промежуточного слоя не должен наследоваться от другого класса.
Если вы создали полезный промежуточный слой, поделитесь ним с сообществом! Напишите нам, и возможно мы добавим его в Django.
Middleware¶
Написание собственного промежуточного программного обеспечения¶
Промежуточное ПО можно записать в виде функции, которая выглядит следующим образом:
Или его можно записать как класс, экземпляры которого можно вызывать, например, так:
Middleware может поддерживать либо только синхронный Python (по умолчанию), либо только асинхронный Python, либо оба варианта. Смотрите Поддержка асинхронного режима для подробностей о том, как рекламировать то, что вы поддерживаете, и знать, какой тип запроса вы получаете.
Middleware может находиться в любом месте вашего пути Python.
__init__(get_response) ¶
Пометить промежуточное ПО как неиспользуемое¶
Активация промежуточного программного обеспечения¶
Чтобы активировать компонент промежуточного ПО, добавьте его в список MIDDLEWARE в настройках Django.
В MIDDLEWARE каждый компонент middleware представлен строкой: полный путь Python к классу или имени функции фабрики middleware. Например, вот значение по умолчанию, созданное django-admin startproject :
Порядок и слоистость промежуточного программного обеспечения¶
Другие крючки промежуточного ПО¶
Помимо базового шаблона промежуточного ПО «запрос/ответ», описанного ранее, вы можете добавить еще три специальных метода к промежуточному ПО на основе классов:
process_view() ¶
process_view() вызывается непосредственно перед тем, как Django вызывает представление.
process_exception() ¶
process_template_response() ¶
Работа с потоковыми ответами¶
streaming_content следует считать слишком большим для хранения в памяти. Промежуточное ПО ответа может обернуть его в новый генератор, но не должно его потреблять. Обертывание обычно реализуется следующим образом:
Обработка исключений¶
Django автоматически преобразует исключения, вызванные представлением или промежуточным ПО, в соответствующий HTTP-ответ с кодом статуса ошибки. Certain exceptions преобразуются в код состояния 4xx, в то время как неизвестное исключение преобразуется в код состояния 500.
Поддержка асинхронного режима¶
Middleware может поддерживать любую комбинацию синхронных и асинхронных запросов. Django адаптирует запросы под требования промежуточного ПО, если оно не может поддерживать оба варианта, но при этом снижается производительность.
По умолчанию Django предполагает, что ваше промежуточное ПО способно обрабатывать только синхронные запросы. Чтобы изменить эти предположения, установите следующие атрибуты для вашей фабричной функции или класса промежуточного ПО:
Вот пример того, как создать промежуточную функцию, поддерживающую оба варианта:
Если вы объявляете гибридное промежуточное ПО, которое поддерживает как синхронные, так и асинхронные вызовы, то вид получаемого вами вызова может не соответствовать базовому представлению. Django оптимизирует стек вызовов промежуточного ПО так, чтобы в нем было как можно меньше синхронных/асинхронных переходов.
Таким образом, даже если вы обертываете асинхронное представление, вы можете быть вызваны в режиме синхронизации, если между вами и представлением находится другое, синхронное промежуточное ПО.
Обновление промежуточного программного обеспечения, существовавшего до версии Django 1.10¶
В большинстве случаев наследования от этого миксина будет достаточно, чтобы сделать промежуточное ПО старого типа совместимым с новой системой с достаточной обратной совместимостью. Новая семантика замыкания будет безвредна или даже полезна для существующего промежуточного ПО. В некоторых случаях класс промежуточного ПО может потребовать некоторых изменений для адаптации к новой семантике.
Это поведенческие различия между использованием MIDDLEWARE и MIDDLEWARE_CLASSES :
В MiddlewareMixin была добавлена поддержка асинхронных запросов.
Начало работы с middleware в Django
Django поставляется с множеством полезных функций. Одним из них является механизм middleware (переводится как промежуточное программное обеспечение). В этом посте я кратко объясню, как работает middleware и как начать писать свой собственный.
Исходный код, включенный в этот пост, доступен на GitHub.
Общая концепция
Middleware позволяет обрабатывать запросы из браузера, прежде чем они достигнут представления Django, а также ответы от представлений до того, как они возвращаются в браузер. Django ведет список middleware для каждого проекта. Вы можете найти его в settings.py под названием MIDDLEWARE. Каждый новый проект Django уже имеет несколько middleware, добавленных в этот список, и в большинстве случаев вам не следует ничего удалять из этого списка. Однако, вы можете, добавить свой собственный.
Middleware применяется в том же порядке, в каком оно добавлено в список в настройках Django. Когда браузер отправляет запрос, он обрабатывается так:
Представление получает запрос, выполняет некоторые операции и возвращает ответ. На пути к браузеру ответ снова проходить через каждое middleware, но в обратном порядке:
Это очень краткое объяснение. Более подробное описание можно найти в документации Django.
Простой пример
Мы начнем с простого middleware, которое измеряет время, необходимое для обработки запроса. Все примеры в этом посте используют Django 3.0.5 и Python 3.6.9.
Настрйка проекта
Сначала создайте проект Django с одним приложением. Игнорируйте миграции, примеры из этого поста не будут использовать базу данных. Создайте файл под названием middleware.py в своем приложении: именно туда мы поместим большую часть кода.
Ваш проект должен выглядеть так:
Не забудьте зарегистрировать свое приложение в django_middleware/settings.py:
Теперь вы можете запустить проект:
Создание Django middleware
Согласно документации Django, существует 2 способа создания middleware: как функция и как класс. Мы будем использовать первый метод, но последний пример покажет вам, как создать класс.
Общая структура middleware в Django выглядит следующим образом (пример скопирован из документации Django):
Функция simple_middleware вызывается один раз, когда Django инициализирует middleware и добавляет его в список всех middleware, используемых в проекте. Функция middleware вызывается для каждого запроса к серверу. Все до строки response = get_response (request) вызывается, когда запрос переходит из браузера на сервер. Все после этой строки вызывается, когда ответ отправляется с сервера обратно в браузер.
Что делает строка respone = get_response (request)? Если кратко, она вызывает следующее middleware в списке. Если это последний middleware, вызывается представление: оно получает запрос, выполняет некоторые операции и генерирует ответ. Этот ответ затем возвращается последнему middleware в списке, который, в свою очередь, отправляет его предыдущему, пока не закончится все middleware и ответ не будет отправлен в браузер.
В нашем примере мы хотим проверить, сколько времени занимает весь процесс обработки запроса. Отредактируйте файл intro/middleware.py следующим образом:
В этом примере мы измеряем время в секундах (time.time()) до и после запроса и выводим разницу.
Следующим шагом является установка middleware, чтобы Django знал, что мы собираемся его использовать. Все, что нам нужно сделать, это добавить его в django_middleware/settings.py:
Примечание: в этом примере intro — это имя нашего приложения Django, middleware — это имя файла Python, содержащего наш код, а timing — это имя функции middleware в этом файле.
Теперь мы готовы это проверить. Откройте браузер и перейдите к localhost:8000. В браузере вы должны увидеть страницу проекта Django по умолчанию (ту, что содержит ракету). В командной строке (где вы назвали python manage.py runserver) вы должны увидеть что-то похожее на это:
Изменение запроса
Наше middleware работает достаточно хорошо, печатая информацию в командной строке. Но мы можем пойти еще дальше: как насчет добавления чего-либо в запрос, чтобы наши представления могли использовать его позже? Поскольку мы занимаемся синхронизацией времени, как насчет добавления даты и времени выполнения запроса?
Эта модификация будет довольно легкой. Отредактируйте файл intro/middleware.py следующим образом:
Мы добавили 2 строки: import datetime и request.current_time = datetime.datetime.now(). Вместе они добавят текущее время к нашему запросу. Теперь нам нужно представление, чтобы отобразить это время. Отредактируйте intro/views.py:
Для такого простого примера нам не нужен шаблон, мы можем создать объект HttpResponse непосредственно в нашем коде.
Теперь нам нужен URL для нашей view. Создайте файл intro/urls.py и отредактируйте его:
Не забудьте отредактировать и django_middleware/urls.py:
Давайте проверим это. Откройте localhost:8000 в вашем браузере. Вы должны увидеть что-то вроде этого:
Обновите страницу несколько раз, чтобы убедиться, что вы получите разные результаты (время должно обновляться для каждого запроса).
Что-то более полезное: обработка исключений
Пришло время для более интересного примера. Рассмотрим ситуацию из реальной жизни: вы пишете программу, и она не работает. Это случается с лучшими из нас, не волнуйтесь. Что вы обычно тогда делаете? Вы ищите ответы на Stack Overflow? Как насчет создания middleware, которое будет выполнять этот поиск за нас?
Django middleware может включать функцию, которая будет вызываться каждый раз, когда возникает исключение. Эта функция называется process_exception, и она принимает 2 аргумента: request, вызвавший исключение, и само исключение.
Если наше middleware определено как функция, тогда мы можем реализовать process_exception следующим образом:
Допустим в нашем случае мы хотим отправить наше исключение в Stack Overflow и получить ссылки на самые актуальные вопросы.
Краткое введение в API Stack Overflow
Если вы раньше не использовали API, не волнуйтесь. Основная идея такова: точно так же, как вы отправляете вопрос в Интернет с помощью веб-браузера, API — это еще один способ для вас отправлять вопросы, но только автоматически через код.
В Python для отправки такого запроса мы будем использовать модуль под названием requests
Stack Overflow middleware
Давайте создадим новое middleware с именем stackoverflow:
Каждый раз, когда представление вызывает исключение, будет вызываться наш метод process_exception. Мы используем модуль requests для вызова Stack Exchange API. Большинство параметров говорят сами за себя. Они такие же, как мы использовали в примере с браузером, но вместо того, чтобы помещать их все в URL вручную, мы позволяем модулю запросов сделать это за нас. Мы просто изменили теги (для поиска Python и Django) и используем наше исключение в виде строки (str (exception)) для поиска заголовка доступных вопросов. После получения ответа от Stack Overflow мы собираем HTML-код, содержащий ссылку на каждый соответствующий вопрос. Надеюсь, мы сможем найти ответ на нашу проблему там. Наконец, этот HTML возвращается в браузер.
Обратите внимание, что ответ от переполнения стека не является обычной веб-страницей, а представляет собой набор информации в формате JSON. Вот почему мы вызываем response.json(), чтобы получить наши результаты.
Конечно, нам нужно установить это новое middleware:
Единственная проблема, которую мы имеем сейчас, заключается в том, что наш код отлично работает. Нам нужно немного его сломать, если мы хотим, чтобы наше новое middleware получило некоторые исключения для обработки. Отредактируйте intro/views.py:
Имейте в виду, что метод process_exception будет вызываться только для реальных исключений. Возвращение HttpResponseServerError или любого другого кода ошибки не считается.
Пришло время проверить это. Откройте localhost:8000 в вашем браузере. Вы должны увидеть что-то вроде этого:
middleware, которое мы только что создали, немного сложнее, чем начальные примеры. По мере роста вашего кода, может быть, лучше управлять middleware как классами, а не функциями. Наше middleware Stack Overflow как класс будет выглядеть так:
Большая часть кода выглядит аналогично, но для класса нам нужно сохранить обратный вызов get_response в нашем экземпляре и использовать его для каждого вызова метода __call__. Если вы предпочитаете эту версию, не забудьте изменить настройки:
Middleware¶
Доступное промежуточное программное обеспечение¶
Промежуточное программное обеспечение для кэша¶
«Общее» промежуточное программное обеспечение¶
Добавляет несколько удобств для перфекционистов:
Если APPEND_SLASH равно True и исходный URL не заканчивается слэшем, и он не найден в URLconf, то формируется новый URL путем добавления слэша в конце. Если этот новый URL найден в URLconf, то Django перенаправляет запрос на этот новый URL. В противном случае исходный URL обрабатывается как обычно.
При необходимости отдельные представления могут быть исключены из поведения APPEND_SLASH с помощью декоратора no_append_slash() :
Устанавливает заголовок Content-Length для непотоковых ответов.
Промежуточное программное обеспечение GZip¶
django.middleware.gzip.GZipMiddleware сжимает содержимое для браузеров, которые понимают сжатие GZip (все современные браузеры).
Это промежуточное ПО должно быть размещено перед любым другим промежуточным ПО, которому необходимо прочитать или записать тело ответа, чтобы сжатие происходило после.
Он НЕ будет сжимать содержимое, если верно хотя бы одно из следующих условий:
Условное промежуточное программное обеспечение GET¶
Locale middleware¶
Промежуточное ПО для сообщений¶
Промежуточное программное обеспечение безопасности¶
django.middleware.security.SecurityMiddleware обеспечивает несколько улучшений безопасности цикла запроса/ответа. Каждый из них может быть независимо включен или отключен с помощью настройки.
Строгая транспортная безопасность HTTP¶
Для сайтов, доступ к которым должен осуществляться только через HTTPS, вы можете настроить современные браузеры на отказ от соединения с вашим доменным именем через небезопасное соединение (в течение определенного периода времени), установив параметр «Strict-Transport-Security» header. Это уменьшит вашу подверженность некоторым SSL-атакам типа «человек посередине» (MITM).
SecurityMiddleware будет устанавливать этот заголовок для вас во всех ответах HTTPS, если вы установите параметр SECURE_HSTS_SECONDS в ненулевое целочисленное значение.
При включении HSTS рекомендуется сначала использовать небольшое значение для тестирования, например, SECURE_HSTS_SECONDS = 3600 на один час. Каждый раз, когда веб-браузер видит заголовок HSTS на вашем сайте, он будет отказываться от небезопасного взаимодействия (используя HTTP) с вашим доменом в течение заданного периода времени. Как только вы убедитесь, что все ресурсы на вашем сайте обслуживаются безопасно (т.е. HSTS ничего не нарушил), целесообразно увеличить это значение, чтобы редкие посетители были защищены (обычно это 31536000 секунд, т.е. 1 год).
Политика HSTS применяется ко всему вашему домену, а не только к URL ответа, для которого вы установили заголовок. Поэтому ее следует использовать только в том случае, если весь ваш домен обслуживается только через HTTPS.
Браузеры, должным образом соблюдающие заголовок HSTS, не позволят пользователям обойти предупреждения и подключиться к сайту с просроченным, самоподписанным или иным недействительным SSL-сертификатом. Если вы используете HSTS, убедитесь, что ваши сертификаты в хорошей форме и остаются таковыми!
Политика в отношении рефералов¶
Некоторые браузеры имеют возможность принимать подсказки о том, следует ли им отправлять заголовок HTTP Referer >, когда пользователь нажимает на ссылку; эта подсказка предоставляется через the Referrer-Policy header. Этот заголовок может предложить браузеру любой из трех вариантов поведения:
Существует два типа условий, на которые этот заголовок может указать браузеру:
no-referrer Указывает браузеру не отправлять реферер для ссылок, нажатых на этом сайте. no-referrer-when-downgrade Указывает браузеру отправлять полный URL в качестве реферера, но только в том случае, если не происходит понижение протокола. origin Указывает браузеру отправлять в качестве реферера только источник, а не полный URL. origin-when-cross-origin Указывает браузеру отправлять полный URL в качестве реферера для одноименных ссылок и только источник для перекрестных ссылок. same-origin Указывает браузеру отправлять полный URL, но только для ссылок одного происхождения. Для перекрестных ссылок реферер не отправляется. strict-origin Указывает браузеру отправлять только источник, а не полный URL, и не отправлять referrer, когда происходит понижение протокола. strict-origin-when-cross-origin Указывает браузеру отправлять полный URL, если ссылка является одноименной и не происходит понижение протокола; отправлять только источник, если ссылка является перекрестной и не происходит понижение протокола; и не отправлять referrer, если происходит понижение протокола. unsafe-url Указывает браузеру всегда отправлять полный URL в качестве реферера.
Неизвестные значения политики
X-Content-Type-Options: nosniff ¶
Если ваш сайт обслуживает загружаемые пользователем файлы, злоумышленник может загрузить специально созданный файл, который будет интерпретирован браузером как HTML или JavaScript, в то время как вы ожидали, что это будет что-то безобидное.
Обратите внимание, что в большинстве ситуаций развертывания, когда Django не участвует в обслуживании загруженных пользователем файлов, эта настройка вам не поможет. Например, если ваш MEDIA_URL обслуживается непосредственно вашим внешним веб-сервером (nginx, Apache и т.д.), то вы захотите установить этот заголовок там. С другой стороны, если вы используете Django для выполнения чего-то вроде требования авторизации для загрузки файлов, и вы не можете установить заголовок с помощью вашего веб-сервера, эта настройка будет полезна.
X-XSS-Protection: 1; mode=block ¶
Некоторые браузеры имеют возможность блокировать содержимое, которое выглядит как XSS attack. Они работают путем поиска содержимого JavaScript в параметрах GET или POST страницы. Если JavaScript воспроизводится в ответе сервера, страница блокируется, и вместо нее отображается страница ошибки.
Параметр X-XSS-Protection header используется для управления работой XSS-фильтра.
XSS-фильтр браузера является полезной мерой защиты, но не следует полагаться только на него. Он не может обнаружить все XSS-атаки, и не все браузеры поддерживают этот заголовок. Убедитесь, что вы по-прежнему validating and sanitizing все вводимые данные для предотвращения XSS-атак.
Перенаправление SSL¶
Если ваш сайт предлагает как HTTP, так и HTTPS соединения, большинство пользователей по умолчанию будут использовать незащищенное соединение. Для обеспечения наилучшей безопасности следует перенаправить все HTTP-соединения на HTTPS.
Если для параметра SECURE_SSL_REDIRECT установлено значение True, SecurityMiddleware будет постоянно (HTTP 301) перенаправлять все HTTP-соединения на HTTPS.
По соображениям производительности, предпочтительнее делать эти перенаправления вне Django, на внешнем балансировщике нагрузки или обратном прокси-сервере, таком как nginx. SECURE_SSL_REDIRECT предназначен для ситуаций развертывания, когда это не представляется возможным.
Если параметр SECURE_SSL_HOST имеет значение, все перенаправления будут отправляться на этот хост вместо первоначально запрошенного хоста.







