Перейти к основному контенту
Tech Path Finder
КурсыИнтервьюКод-ревьюБлог
Tech Path Finder

Персонализированный путеводитель в IT. Квизы, мок-интервью, код ревью и аналитика прогресса.

@potapov_me

Платформа

  • Курсы
  • Прогресс
  • Мок-интервью
  • Код ревью
  • Живое ревью с ИИ
  • Тренажёр переговоров
  • Закладки

Контент

  • Блог
  • Главная
  • Обратная связь

Компания

  • О проекте
  • Тарифы
  • Условия использования
  • Конфиденциальность
  • Согласие на обработку данных
  • Cookie
  • Реквизиты

Аккаунт

  • Войти
  • Зарегистрироваться
  • Профиль

© 2026 Tech Path Finder. Все права защищены.

·ИП Потапов К.С.·Политика конфиденциальности·
Сделано с ❤️ в России
  1. Middleware
middleware

Middleware

Создание middleware, порядок выполнения, request/response hooks

Middleware в Django

Middleware — слой вокруг маршрутизации и view для поведения, которое действительно относится ко многим запросам: security headers, session/auth plumbing, correlation id, locale. Правило публикации поста или расчёт тарифа к middleware не относятся.

Материал актуален для Django 5.2 LTS и 6.0. Встроенная CSP отдельно помечена как новинка Django 6.0.

#«Луковица» и короткий ответ

При списке [A, B, C] запрос входит как A → B → C → view, а ответ выходит view → C → B → A. Каждый слой решает, вызывать ли следующий:

request → A → B → C → view response ← A ← B ← C ←────┘

Если B вернул response до get_response(), C и view не выполнятся, а ответ пройдёт наружу через A. Поэтому middleware ниже по списку не гарантированно увидит каждый запрос.

Порядок — часть корректности. SessionMiddleware должен идти до AuthenticationMiddleware, а MessageMiddleware зависит от sessions. Response-фаза идёт в обратном направлении, что важно для cache, compression и заголовков.

#Современный callable

from time import monotonic class TimingMiddleware: def __init__(self, get_response): self.get_response = get_response def __call__(self, request): started_at = monotonic() response = self.get_response(request) response.headers["Server-Timing"] = ( f"app;dur={(monotonic() - started_at) * 1000:.1f}" ) return response

Экземпляр middleware создаётся один раз при старте процесса и обслуживает много запросов. Не храните текущий request, user или timer в self: при параллельных запросах значения смешаются. Локальная переменная и атрибут самого request принадлежат запросу.

Server-Timing может раскрывать внутренние детали; публикуйте только согласованные метрики или включайте заголовок в диагностической среде.

#Дополнительные hooks

Factory/callable можно дополнить методами:

  • process_view(request, view_func, view_args, view_kwargs) — перед вызовом найденной view;
  • process_exception(request, exception) — если view выбросила исключение;
  • process_template_response(request, response) — для ответа с методом render().

Hook возвращает None, чтобы продолжить, либо HttpResponse, чтобы подменить дальнейшую обработку. Exception hooks вызываются в обратном порядке middleware; если один вернул response, остальные получают уже response-фазу.

MiddlewareMixin адаптирует старые process_request/process_response классы и не удалён. Он полезен при сопровождении legacy, но новый простой слой легче написать callable-стилем.

Не пытайтесь ловить все исключения обычным try/except вокруг self.get_response(): Django преобразует исключения в responses на границах цепочки. Для site-wide 400/403/404/500 используйте URL handlers и error templates, для DRF — его exception handler, а process_exception оставляйте для действительно общего поведения.

#Базовый порядок встроенных 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 ordering документации каждого компонента. Например, compression должен оборачивать responses в правильном месте относительно conditional GET/cache.

#Что делает каждый компонент

  • SecurityMiddleware управляет HTTPS redirect, HSTS, X-Content-Type-Options, Referrer Policy и Cross-Origin Opener Policy согласно settings.
  • SessionMiddleware добавляет request.session и сохраняет изменённую сессию в response.
  • CommonMiddleware реализует APPEND_SLASH, PREPEND_WWW и некоторые общие проверки.
  • CsrfViewMiddleware проверяет unsafe cookie-authenticated requests.
  • AuthenticationMiddleware добавляет лениво вычисляемый request.user и зависит от sessions.
  • MessageMiddleware связывает messages с request/response.
  • XFrameOptionsMiddleware добавляет защиту от framing по X_FRAME_OPTIONS.

CommonMiddleware не сжимает response и не является текущим механизмом ETag. Для сжатия есть GZipMiddleware/reverse proxy, для conditional responses — ConditionalGetMiddleware и HTTP cache semantics.

#SecurityMiddleware: rollout важнее копирования settings

SECURE_SSL_REDIRECT = True SECURE_CONTENT_TYPE_NOSNIFF = True SECURE_REFERRER_POLICY = "strict-origin-when-cross-origin" SECURE_CROSS_ORIGIN_OPENER_POLICY = "same-origin" # HSTS сначала вводят с малым значением и только на полностью HTTPS-домене. SECURE_HSTS_SECONDS = 300 SECURE_HSTS_INCLUDE_SUBDOMAINS = False SECURE_HSTS_PRELOAD = False

HSTS браузер помнит вне приложения. Год, includeSubDomains и preload нельзя включать, пока каждый поддомен не готов к HTTPS и нет плана отката.

За TLS-terminating proxy настройте SECURE_PROXY_SSL_HEADER только если proxy удаляет входящий header клиента и сам выставляет проверенное значение. Иначе злоумышленник может подделать request.is_secure(), что влияет на redirect и CSRF.

SECURE_BROWSER_XSS_FILTER удалён в Django 4.0: устаревший browser XSS auditor не заменял CSP. USE_ETAGS также удалён в Django 4.0. Не переносите эти settings из старого чеклиста.

#CSRF: origin trust не отключает token

Для server-rendered формы:

<form method="post"> {% csrf_token %} {{ form.as_div }} <button type="submit">Сохранить</button> </form>

Middleware защищает unsafe методы (POST, PUT, PATCH, DELETE), когда используется cookie/session authentication. CSRF_TRUSTED_ORIGINS разрешает проверенные origins на origin/referer-этапе, но не отменяет CSRF token. CORS и CSRF — разные механизмы.

csrf_exempt применяют только после анализа модели аутентификации. Endpoint с bearer token, недоступным браузеру как ambient cookie, может не нуждаться в CSRF; session-auth API нуждается. Глобальное исключение /api/ по prefix опасно.

#Correlation id и структурный лог

import logging from time import monotonic from uuid import uuid4 logger = logging.getLogger(__name__) class RequestLogMiddleware: def __init__(self, get_response): self.get_response = get_response def __call__(self, request): request.request_id = uuid4().hex started_at = monotonic() response = self.get_response(request) duration_ms = (monotonic() - started_at) * 1000 logger.info( "http_request_finished", extra={ "request_id": request.request_id, "method": request.method, "path": request.path, "status_code": response.status_code, "duration_ms": round(duration_ms, 1), }, ) response.headers["X-Request-ID"] = request.request_id return response

Не включайте query string, cookies, Authorization header, password и body по умолчанию. Если инфраструктура уже выдаёт trace/request id, доверяйте входящему значению только от контролируемого proxy и валидируйте формат/длину. Для distributed tracing лучше стандарт W3C Trace Context через observability SDK.

Если нужно логировать 500 при исключении, используйте настроенный Django error logger/APM: показанный response-only слой может увидеть статус, но не само уже преобразованное исключение.

#Locale без утечки состояния

Translation state контекстный, и его надо восстановить даже при исключении:

from django.utils import translation class DomainLocaleMiddleware: languages = { "example.com": "en", "example.ru": "ru", } def __init__(self, get_response): self.get_response = get_response def __call__(self, request): host = request.get_host().partition(":")[0] language = self.languages.get(host, "en") request.LANGUAGE_CODE = language with translation.override(language): return self.get_response(request)

request.get_host() проверяет ALLOWED_HOSTS; чтение сырого Host из META обходит эту защиту. Для стандартного выбора языка сначала рассмотрите LocaleMiddleware.

#Rate limiting не помещается в cache.get() + set()

Наивный middleware обычно ошибается сразу в нескольких местах:

  • read-modify-write счётчика не атомарен;
  • locmem cache отдельный у каждого процесса;
  • повторный set(..., timeout=60) двигает окно;
  • X-Forwarded-For подделывается без доверенной proxy-chain;
  • общий IP блокирует офис/NAT и не защищает распределённую атаку;
  • все paths получают один лимит, хотя login, upload и чтение различаются.

Для внешнего трафика предпочтителен rate limit на CDN/API gateway/reverse proxy. Прикладной limiter строят на общей atomic storage (например, Redis script), ключе actor/API key плюс проверенный network signal, именованной политике endpoint и ответе 429 с Retry-After. Fail-open/fail-closed выбирают по риску операции.

User-Agent нельзя считать identity или security signal: клиент меняет его одной строкой. Блокировка известных crawler может уменьшить шум, но не является защитой.

#Maintenance mode

Надёжнее переключать maintenance на load balancer/CDN, оставляя health endpoint для оркестратора. Django middleware требует осторожного порядка: если он обращается к request.user, то должен стоять после AuthenticationMiddleware; исключения по /admin/, /static/, /media/ легко раскрывают лишние пути.

Если режим реализован в приложении, используйте именованную политику/список разрешённых routes, возвращайте 503 и Retry-After, не кэшируйте персональный ответ и тестируйте staff bypass. Флаг должен быть общим для всех workers, не модульной переменной.

#Новое в Django 6.0: встроенный CSP middleware

from django.utils.csp import CSP MIDDLEWARE = [ "django.middleware.security.SecurityMiddleware", "django.middleware.csp.ContentSecurityPolicyMiddleware", # остальные middleware ] SECURE_CSP_REPORT_ONLY = { "default-src": [CSP.SELF], "script-src": [CSP.SELF, CSP.NONCE], "style-src": [CSP.SELF], "img-src": [CSP.SELF, "data:", "https:"], "frame-ancestors": [CSP.NONE], "report-uri": ["/csp-reports/"], }

Начните с report-only, устраните inline scripts/styles или переведите нужные scripts на nonce, затем включайте SECURE_CSP. Для nonce нужен django.template.context_processors.csp и применение {{ csp_nonce }} согласно CSP how-to.

Django строит header, но не проверяет смысл и валидность всех directives. Report endpoint сам принимает недоверенный JSON, требует ограничения размера/rate и не должен отражать данные в HTML.

В Django 5.2 встроенного CSP middleware нет: policy добавляет reverse proxy или поддерживаемая сторонняя библиотека. Не копируйте unsafe-inline ради того, чтобы «CSP не ломал сайт»: это существенно ослабляет script policy.

COEP/CORP/COOP не включают комплектом без теста. Cross-Origin-Embedder-Policy: require-corp, например, блокирует ресурсы, которые не дают подходящий CORS/CORP response.

#Async deployment и adaptation

Middleware может быть sync-only, async-only или поддерживать оба режима. При несовпадении Django адаптирует вызов, но переходы sync↔async уменьшают выгоду ASGI. Долгая синхронная цепочка может удерживать thread на запрос.

При написании dual-capable middleware следуйте официальному шаблону с sync_capable, async_capable, iscoroutinefunction и markcoroutinefunction; простого async def __call__ недостаточно для универсальной совместимости. Отдельно тестируйте WSGI и ASGI конфигурации.

#Когда middleware не нужен

  • одна view: decorator или mixin;
  • одна модельная операция: service/use case;
  • формат ошибок API: DRF exception handler;
  • ingress rate limit/TLS/compression: часто proxy;
  • фоновая работа: task queue;
  • бизнес-аудит: транзакционный use case/outbox, а не лог всех HTTP-запросов.

Проверка request.path.startswith() связывает middleware с текущим prefix и ломается при namespace/versioning. Если поведение не глобально, сузьте его ближе к endpoint.

#Legacy и удалённые настройки

MiddlewareMixin и hooks не удалены и помогают поддерживать старые пакеты. Удалены SECURE_BROWSER_XSS_FILTER и USE_ETAGS в Django 4.0. Утверждение «CommonMiddleware делает gzip и ETag» для актуального Django неверно.

В Django 6.0 появился встроенный CSP. При одновременном старом custom CSP middleware убедитесь, что response не получает конфликтующие headers, затем мигрируйте поэтапно.

#Документация

  • Middleware
  • Built-in middleware
  • Middleware ordering
  • Django CSP

Далее: Signals