Во многих фреймворках есть возможность создавать своеобразные прослойки для того, чтобы дополнять запросы к приложению, либо же модифицировать ответы от него. Это middleware. Они работают незаметно для основного приложения, но несут большую пользу. Сами посмотрите:
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',
]
Это стандартные middleware, которые подключаются при генерации
проекта на Django. Найти и модифицировать список используемых
"прослоек" можно в файле настроек settings.py
. Что же они дают проекту?
django.middleware.security.SecurityMiddleware
проводит ряд проверок
запроса к приложению на предмет различных атак:
- Устанавливает заголовок
Strict-Transport-Security
, который предписывает браузеру подключаться только по защищённому соединению (HTTPS). - Добавляет заголовок
X-Content-Type-Options: nosniff
, чтобы браузеры не пытались угадатьContent-Type
переданного файла. Да, это может быть полезно, если сервер настроен неверно, но также "игра в угадайку" может быть использована для атаки. - Включает
X-XSS-Protection
для того, чтобы браузер проверял,GET
иPOST
параметры на предмет JavaScript, дабы предотвратить XSS-атаку. - При наличии HTTP и HTTPS версий сайта, перенаправляет на HTTPS версию.
django.contrib.sessions.middleware.SessionMiddleware
включает поддержку
механизма сессий в Django-проекте. То есть по Session ID, который передаётся
в Cookies, находит данные сессии в бекенде для хранения сессий (например, в
базе данных).
django.middleware.common.CommonMiddleware
добавляет несколько
полезных возможностей. Таких как добавление слеша (/
) в конце URL,
www
к домену - если включены соответствующие настройки,
поддержка механизма HTTP ETag
.
django.middleware.csrf.CsrfViewMiddleware
проверяет запросы с данными от
клиента на наличие CSRF-токена, дабы предотвратить подделку запроса.
django.contrib.auth.middleware.AuthenticationMiddleware
находит
по сессии Django-пользователя, подставляя его в поле user
объекта request
.
django.contrib.messages.middleware.MessageMiddleware
позволяет хранить
в сессии пользователя небольшие информационные сообщения, которые
будут храниться между запросами.
django.middleware.clickjacking.XFrameOptionsMiddleware
добавляет защиту
от clickjacking
-а - подмены сайта посредством frame
-элементов.
Таким образом, сложно переоценить пользу от этих маленьких прослоек - middleware.

Как же работают middleware?
Возможно, вы знакомы с тем, как работают web-proxy или же Man-in-the-middle. А может быть, вам будет более близка аналогия с декораторами.
В любом случае, middleware встаёт перед приложением, предоставляя интерфейс как у приложения. Таким образом она как бы подменяет его. Благодаря этому middleware получает управление и запрос ещё до приложения, может модифицировать его, провести подготовительные действия.
После этого промежуточное программное обеспечение вызывает само приложение либо же следующую middleware, ожидая ответ.
Получив ответ, middleware может его модифицировать и передать дальше - клиенту или же предыдущей "прослойке".
Получается своеобразная матрёшка или лук - приложение обёрнуто в миддлварь, которая обёрнута в другую и т.д.
Общий вид Django middleware
Как и декораторы в Python, промежуточное программное обеспечение
также выглядит как вызываемый объект, который возвращает
вызываемый объект. Как вариант - функция, которая возвращает функцию.
Внешняя функция принимает параметром функцию для вызова приложения,
либо же следующей middleware (get_response
), а вложенная функция - запрос к серверу (request
).
from typing import Callable
from django.http import HttpRequest, HttpResponse
def name(get_response: Callable[[HttpRequest], HttpResponse]) -> Callable:
def middleware(request: HttpRequest) -> HttpResponse:
response = get_response(request)
return response
return middleware
Также нужно добавить данную функцию в список middleware в настройках приложения:
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',
'app_name.module.name',
]
После чего она будет использована и вызвана после прочих стандартных для Django мидлварей.
Пример Django middleware, которая работает с запросом
Предположим, мы хотим сделать так, чтобы пользователи, вошедшие на наш сайт автоматически разлогинивались через некоторое время. В некоторых организациях есть подобные требования безопасности.
Тогда для решения данной задачи нам идеально подойдёт middleware.
Данную middleware можно расположить в приложении для работы с пользователями (в данном случае - user
),
в модуле middleware
:
from datetime import datetime, timedelta
from typing import Callable
from django.conf import settings
from django.contrib.auth import logout
from django.http import HttpRequest, HttpResponse
def logout_on_timeout(get_response: Callable[[HttpRequest], HttpResponse]) -> Callable:
ttl = settings.LOGOUT_TIMEOUT
def middleware(request: HttpRequest) -> HttpResponse:
user = request.user
if (
not user.is_anonymous
and user.last_login < datetime.now() - timedelta(seconds=ttl)
):
logout(request)
response = get_response(request)
return response
return middleware
После получения управления и запроса мы берём из запроса пользователя, поэтому нужно будет
подключить данную "прослойку" после django.contrib.auth.middleware.AuthenticationMiddleware
,
чтобы пользователь уже был инициализирован и находился в request
.
Далее проверяем, что пользователь не анонимный (его разлогинивать незачем), а также, что не истекло время работы пользователя (отсчитываем от времени последнего входа пользователя). Если время истекло - разлогиниваем.
Время, которое отпущено пользователю до разлогина указываем в настройках:
LOGOUT_TIMEOUT = 20 * 60 # 20 минут
Не забываем в настройках добавить нашу мидлварь в список MIDDLEWARE
.
Пример Django middleware, которая работает с ответом приложения
Возможно, вы обращали внимание, что код html-страниц, сгенерированный вашим приложением, не отличается лаконичностью: куча лишних пробелов, пустых строк и т.д. Таким образом по сети отправляются лишние данные, что не может радовать.
Напишем middleware, которая будет убирать лишние пробелы из начала строк, а также лишние пустые строки.
import re
from django.http import HttpRequest, HttpResponse
RE_EMPTY_STRING = r'\n\s*?\n'
RE_STRING_SPACE_PREFIX = r'\n\s+'
def html_optimize(get_response):
def middleware(request: HttpRequest):
response: HttpResponse = get_response(request)
new_content = re.sub(RE_EMPTY_STRING, lambda *_: '\n', response.content.decode())
new_content = re.sub(RE_STRING_SPACE_PREFIX, lambda *_: '\n', new_content)
response.content = new_content.encode()
response["Content-Length"] = len(response.content)
return response
return middleware
Для начала получаем управление и отдаём его дальше, когда всё отработало, мы получаем ответ сервера.
Самое время его "почистить"! response.content
содержит байты html-страницы.
Поэтому декодируем в строку и с помощью регулярных выражений убираем лишние пробелы.
Далее - кодируем обратно в байты.
Так как мы изменили содержимое ответа, нужно также изменить заголовок, в котором указан размер содержимого ответа нашего приложения. После чего отправляем объект ответа дальше.
Эта мидлварь не использует ничего из других, поэтому её можно поставить первой в списке MIDDLEWARE
в файле настроек нашего Django-приложения.