Django Middleware от А до Я: пишем свой кастомный middleware
Каждый раз, когда пользователь открывает страницу вашего Django-приложения, его запрос проходит через невидимый конвейер, прежде чем попадет в вашу функцию (view). И этот же конвейер обрабатывает ответ (response) перед тем, как отдать его обратно браузеру.
Этот конвейер называется Middleware.
В этой статье мы разберем, как работает этот механизм под капотом, и напишем полезный кастомный middleware для профилирования медленных запросов в продакшне.
Как работает Middleware?
Middleware в Django реализует классический паттерн проектирования “Цепочка обязанностей” (Chain of Responsibility), или, если точнее, “Паттерн декоратора” на уровне HTTP-запросов.
Когда приходит запрос, он проходит через каждый middleware сверху вниз (как они указаны в MIDDLEWARE в settings.py). Когда генерируется ответ — он проходит через них снизу вверх (в обратном порядке).
# settings.py
MIDDLEWARE = [
'django.middleware.security.SecurityMiddleware', # 1. Запрос входит тут -> (Ответ выходит тут)
'django.contrib.sessions.middleware.SessionMiddleware', # 2. ->
'django.middleware.common.CommonMiddleware', # 3. ->
'django.middleware.csrf.CsrfViewMiddleware', # 4. ->
'django.contrib.auth.middleware.AuthenticationMiddleware', # 5. ->
# ...
]
Каждый слой может:
- Изменить объект
request. - Изменить объект
response. - Отклонить запрос и сразу вернуть свой
response(например, если сработала защита CSRF или пользователь не авторизован), не пуская запрос дальше к вашей view.
Пишем свой первый Middleware
Начиная с Django 1.10, middleware пишется в виде вызываемых объектов (обычно классов или функций). Мы будем использовать класс, так как это удобнее для хранения состояния.
Давайте напишем SlowRequestLoggerMiddleware. Его задача: замерять время выполнения запроса, и если оно превышает определенный порог (например, 1 секунда), записывать в лог предупреждение.
Создайте файл middleware.py в одном из ваших приложений (например, core/middleware.py):
import time
import logging
logger = logging.getLogger(__name__)
class SlowRequestLoggerMiddleware:
def __init__(self, get_response):
# Эта функция вызывается один раз при старте сервера
self.get_response = get_response
# Устанавливаем порог в 1 секунду
self.slow_threshold = 1.0
def __call__(self, request):
# Код, который выполняется ДО вызова view (и следующих middleware)
start_time = time.time()
# Передаем эстафету дальше. get_response вызовет следующую middleware
# или саму view, если это последняя middleware.
response = self.get_response(request)
# Код, который выполняется ПОСЛЕ вызова view
duration = time.time() - start_time
# Если запрос был медленным, логируем его
if duration > self.slow_threshold:
logger.warning(
f"Slow request detected: {request.method} {request.path} "
f"took {duration:.2f} seconds."
)
return response
Как это подключить?
Просто добавьте путь к вашему классу в конец списка MIDDLEWARE в settings.py:
MIDDLEWARE = [
# ... стандартные middleware ...
'core.middleware.SlowRequestLoggerMiddleware',
]
Продвинутые хуки (Hooks)
Метод __call__ — это основа, но Django предоставляет ещё несколько специальных методов, которые можно переопределить в классе middleware для более тонкой настройки:
1. process_view(request, view_func, view_args, view_kwargs)
Вызывается прямо перед тем, как Django вызовет вашу view. Здесь вы уже знаете, какая функция будет обрабатывать запрос, и какие аргументы она получит. Если этот метод возвращает HttpResponse, Django не будет вызывать саму view.
Пример использования: Ограничение доступа к определенным view (Rate Limiting) или автоматическая проверка прав.
2. process_exception(request, exception)
Вызывается, если view (или другая middleware) выбросила исключение (ошибку).
Пример использования: Автоматическая отправка уведомлений об ошибках в Telegram или Slack, или возвращение красивой JSON-ошибки вместо стандартной HTML-страницы при работе с API.
class ErrorNotifierMiddleware:
def __init__(self, get_response):
self.get_response = get_response
def __call__(self, request):
return self.get_response(request)
def process_exception(self, request, exception):
# Логика отправки ошибки в Telegram
send_to_telegram(f"🔥 Упал запрос {request.path}: {str(exception)}")
# Возвращаем None, чтобы Django продолжил стандартную обработку ошибки
return None
3. process_template_response(request, response)
Вызывается сразу после view, если view вернула объект TemplateResponse (или другой ответ, поддерживающий метод render()). Позволяет изменить контекст шаблона или сам шаблон до того, как он будет отрендерен.
Когда НЕ нужно использовать Middleware?
Middleware — мощный инструмент, но он выполняется для каждого отдельного запроса к вашему серверу (включая запросы за статикой, если её раздает Django).
Не используйте middleware для:
- Тяжелых вычислений. Если ваш middleware делает 5 запросов к БД, ваш сервер умрёт под нагрузкой.
- Логики, специфичной только для одного URL. Если вам нужно что-то проверить только для страницы
/profile/, используйте декораторы view (например,@login_required), а не middleware.
Заключение
Middleware — это идеальное место для сквозной (cross-cutting) логики, которая касается всего приложения: аутентификация, безопасность, CORS-заголовки, профилирование производительности и глобальная обработка ошибок. Понимание этого конвейера отличает уверенного мидла от джуниора.
forum Обсуждение
Комментарии скоро будут доступны. Следите за обновлениями!