Жизненный цикл запроса в Django: как это устроено внутри
От WSGI-вызова до готового HttpResponse — что происходит внутри Django с каждым запросом, в каком порядке отрабатывают middleware и где чаще всего теряется контроль над ответом.

Когда браузер стучится на ваш Django-сайт, между приходом байтов на сокет и отдачей HTML происходит десяток шагов, каждый из которых можно перехватить, сломать или ускорить. Знать их порядок нужно в тот момент, когда middleware начинает отрабатывать не в той последовательности, ответ подменяется где-то на полпути, а исключение исчезает в никуда вместо честного 500. Разберём путь запроса в Django 5.x по слоям: WSGI/ASGI, стек middleware, разрешение URL, вью и сборка ответа.
Где начинается запрос: WSGI и ASGI
Django-приложение — это вызываемый объект, который сервер приложений (Gunicorn, uWSGI, Uvicorn) дергает на каждый запрос. Для синхронного стека это WSGI-приложение из django.core.wsgi.get_wsgi_application(), для асинхронного — ASGI из django.core.asgi.get_asgi_application(). Именно этот объект сервер держит в памяти между запросами.
Внутри WSGI-обработчик (WSGIHandler) делает три вещи по порядку: строит объект HttpRequest из окружения WSGI (метод, путь, заголовки, тело), прогоняет его через цепочку middleware и вью, а затем превращает возвращённый HttpResponse обратно в кортеж «статус, заголовки, тело» для сервера.
Django-приложение не создаётся заново на каждый запрос. Настройки, реестр приложений и скомпилированная цепочка middleware живут ровно столько, сколько живёт воркер. Всё, что вы кладёте в глобальную область на импорте, разделяется между всеми запросами воркера — и это источник половины «плавающих» багов с состоянием.
Сборка цепочки middleware — один раз на старте
Метод BaseHandler.load_middleware() вызывается при инициализации обработчика, а не на каждый запрос. Он читает список MIDDLEWARE из настроек и оборачивает вью в матрёшку вызовов: внешний middleware вызывает следующий, тот — следующий, и так до самого вью. Результат сборки хранится в self._middleware_chain. Поэтому менять MIDDLEWARE в рантайме бессмысленно — цепочка уже собрана.
Порядок middleware: сверху вниз и снизу вверх
Список MIDDLEWARE в settings.py читается как стек. На входе запрос идёт по списку сверху вниз, на выходе ответ поднимается снизу вверх. Это ключевая деталь, из-за которой люди путаются в том, кто кого «оборачивает».
Возьмём типичный порядок и проследим путь:
# settings.py, Django 5.x
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",
]
Запрос сначала попадает в SecurityMiddleware, потом в SessionMiddleware и так вниз до вью. Ответ идёт в обратном порядке: сначала его увидит middleware, стоящий ниже, а SecurityMiddleware — последним перед отдачей серверу. Отсюда правило: то, что должно видеть уже полностью готовый ответ (например, добавление security-заголовков), ставят выше в списке.
Пять хуков, которые может иметь middleware
Современный middleware в Django — это класс с методом __call__, но у него есть и специальные хуки, которые обработчик вызывает в строго определённые моменты:
__call__(request)— основной код до и послеget_response. Всё, что до вызоваself.get_response(request), отрабатывает на входе; всё, что после — на выходе.process_view(request, view_func, view_args, view_kwargs)— вызывается после разрешения URL, но до вызова вью. Уже известно, какая вью отработает.process_exception(request, exception)— вызывается, если вью бросила исключение. Идёт снизу вверх по стеку.process_template_response(request, response)— вызывается, только если ответ имеет методrender()(то есть этоTemplateResponse, а не готовыйHttpResponse).
Хуки — необязательные. Если у класса нет process_view, Django просто его пропустит. Порядок вызова process_view — сверху вниз, process_exception и process_template_response — снизу вверх.
Разрешение URL: как путь превращается в вью
После того как запрос прошёл входную часть middleware, обработчик берёт request.path_info и передаёт его в резолвер URL. Резолвер (объект URLResolver) идёт по urlpatterns из вашего корневого ROOT_URLCONF и сопоставляет путь с паттернами по очереди — первое совпадение выигрывает. Это часто удивляет: если два паттерна совпадают, сработает тот, что выше.
Результат резолва — кортеж из функции вью, позиционных и именованных аргументов. Аргументы Django извлекает из именованных групп в паттерне (для path() — из конвертеров вроде <int:pk>). Если совпадения нет вообще, поднимается Http404, которую дальше ловит обработчик и превращает в страницу 404.
Что происходит внутри вью
Вью получает request и распакованные из URL аргументы и обязана вернуть объект HttpResponse (или его подкласс). Class-based views под капотом всё равно превращаются в функцию через as_view(), которая создаёт экземпляр класса и вызывает его dispatch() — тот по HTTP-методу выбирает get(), post() и так далее.
Если вью вернула не HttpResponse, а, скажем, обычную строку или None, Django поднимет ValueError ещё до отдачи ответа. Это частая ошибка новичков: забыли return.
Сборка и отдача ответа
Когда вью вернула объект-ответ, обработчик проверяет, не является ли он «ленивым» шаблонным ответом. TemplateResponse в момент возврата из вью ещё не отрендерен — у него есть только шаблон и контекст. Рендеринг происходит позже, после прохода process_template_response, что и позволяет middleware подменить шаблон или контекст до финальной сборки HTML.
Обычный HttpResponse уже содержит готовое тело — по нему process_template_response не вызывается. Это объясняет, почему middleware, рассчитанное на подмену контекста, «молчит» при возврате обычного ответа.
Полный путь одного запроса в порядке вызовов:
- Сервер вызывает WSGI/ASGI-приложение, обработчик строит
HttpRequest. - Входная часть middleware — сверху вниз по
MIDDLEWARE. - Резолв URL: путь сопоставляется с
urlpatterns, первое совпадение. - Хуки
process_view— сверху вниз. - Вызов вью, возврат объекта-ответа.
- При
TemplateResponse—process_template_responseснизу вверх, затемrender(). - Выходная часть middleware — снизу вверх.
- Обработчик превращает ответ в формат сервера, сервер отдаёт байты клиенту.
Где это ломается на практике
Граничные случаи, из-за которых порядок перестаёт работать так, как ожидаешь:
- Исключение внутри middleware, а не во вью. Хук
process_exceptionловит исключения вью, но не исключения, брошенные другим middleware ниже по стеку. Такое исключение всплывёт как необработанное и даст 500 в обход вашей логики. - Ответ вернули из
process_view. Если хук вернулHttpResponse, вью не вызовется вообще, а ответ пойдёт сразу в выходную часть middleware. Легко случайно закоротить обработку. - Стриминговый ответ. У
StreamingHttpResponseнет атрибутаcontent— тело генерируется лениво по мере отдачи. Middleware, которое читает или переписываетresponse.content, на нём сломается или исчерпает генератор. - Async и sync вперемешку. На ASGI Django оборачивает синхронные middleware адаптерами и наоборот. Каждая граница sync/async стоит переключения контекста через пул потоков — при плотном чередовании это заметная накладка.
Точный список методов и их сигнатуры для вашей версии стоит сверять в разделе документации Django про middleware — API хуков между мажорными версиями в целом стабилен, но детали поведения на ASGI менялись.
Как это увидеть у себя
Чтобы не гадать, поставьте django-debug-toolbar (для локальной разработки) — панель Middleware покажет реальный порядок и время каждого слоя. Для отладки резолва URL полезна команда python manage.py show_urls из пакета django-extensions либо интерактивная проверка через django.urls.resolve() в shell.
# проверить, какая вью отработает для пути (Django 5.x, в manage.py shell)
from django.urls import resolve
match = resolve("/orders/42/")
print(match.func) # функция вью или .view_class у CBV
print(match.kwargs) # {'pk': 42} — что придёт во вью
print(match.url_name) # имя маршрута из urlpatterns
Что смотреть дальше: замерьте суммарное время middleware в проде через APM или логи, посмотрите, не читает ли какое-то ваше middleware response.content у стриминговых ответов, и проверьте порядок в MIDDLEWARE — security-заголовки должны стоять выше тех, кто меняет тело ответа. Инвалидация кеша и работа кеширующего middleware — отдельная тема, здесь мы разбирали только порядок прохождения запроса.
Аналитик звонков: Разбор записи звонка по этапам сделки: где сорвалось, что упущено, три исправляемые рекомендации с цитатами.
Спросить за 90 ₽Источники: Django documentation — Middleware, Django documentation — Request and response objects
Частые вопросы
В каком порядке выполняются middleware на входе и на выходе?
У меня другая версия Django, что-то из описанного не совпадает?
Почему моё process_template_response не срабатывает?
Что будет, если два URL-паттерна совпадают с одним путём?
Почему исключение из вью не поймалось моим process_exception?
Как это ведёт себя под нагрузкой в проде?
Материал носит информационный характер и подготовлен редакцией «Агентуры». Он не является офертой, рекламой или индивидуальной консультацией. Упомянутые продукты, компании и торговые знаки принадлежат их правообладателям. Перед принятием решений, влекущих юридические или финансовые последствия, обратитесь к профильному специалисту.