Объясните жизненный цикл запроса/ответа в Django — от разрешения URL до HTTP-ответа.
Запрос Django проходит: WSGIHandler → middleware (process_request) → URL resolver → view → middleware (process_response) → HttpResponse. Исключения перехватываются через process_exception в middleware.
Жизненный цикл запроса/ответа в Django
Обзор этапов
Каждый HTTP-запрос проходит строго определённую цепочку: WSGI-сервер → Django handler → middleware stack → URL resolver → view → middleware stack (обратно) → ответ клиенту.
1. WSGI/ASGI entry point
Gunicorn/Uvicorn получает HTTP-запрос и вызывает get_wsgi_application() или get_asgi_application(), которые возвращают WSGIHandler/ASGIHandler. Handler создаёт объект HttpRequest из environ-словаря.
2. Middleware: process_request
Middleware из MIDDLEWARE settings применяются сверху вниз на входящий запрос. Каждый middleware может:
- Изменить request (добавить атрибут, декодировать тело)
- Вернуть HttpResponse досрочно (короткое замыкание, остальные middleware и view не вызываются)
# Порядок важен — пример из settings.py
MIDDEWARE = [
"django.middleware.security.SecurityMiddleware", # HTTPS редиректы
"django.contrib.sessions.middleware.SessionMiddleware", # request.session
"django.middleware.common.CommonMiddleware", # trailing slash
"django.middleware.csrf.CsrfViewMiddleware", # CSRF проверка
"django.contrib.auth.middleware.AuthenticationMiddleware", # request.user
"django.contrib.messages.middleware.MessageMiddleware",
"django.middleware.clickjacking.XFrameOptionsMiddleware",
]
3. URL Resolver
URLResolver сопоставляет request.path_info с паттернами из urls.py, используя regex или path-конвертеры. Если совпадение найдено — извлекаются kwargs для view. Если нет — Resolver404.
# urls.py
from django.urls import path, re_path
from . import views
urlpatterns = [
path("users/<int:pk>/", views.UserDetailView.as_view(), name="user-detail"),
re_path(r"^legacy/(?P<slug>[\w-]+)/$", views.LegacyView.as_view()),
]
4. View вызов
Django вызывает найденный view с (request, **kwargs). View должен вернуть HttpResponse (или подкласс). Функциональные view вызываются напрямую, CBV — через as_view(), который вызывает dispatch() → get()/post()/put() в зависимости от HTTP-метода.
from django.views import View
from django.http import JsonResponse
class UserDetailView(View):
def dispatch(self, request, *args, **kwargs):
# Вызывается до get/post — место для pre-обработки
return super().dispatch(request, *args, **kwargs)
def get(self, request, pk):
from .models import User
try:
user = User.objects.get(pk=pk)
return JsonResponse({"id": user.pk, "email": user.email})
except User.DoesNotExist:
from django.http import Http404
raise Http404
5. Middleware: process_response
После получения ответа от view middleware применяются снизу вверх (в обратном порядке). Каждый middleware может модифицировать response (добавить заголовки, сжать тело) или заменить его полностью.
class TimingMiddleware:
def __init__(self, get_response):
self.get_response = get_response
def __call__(self, request):
import time
start = time.monotonic()
response = self.get_response(request) # вызов следующего middleware/view
duration = time.monotonic() - start
response["X-Request-Duration"] = f"{duration:.3f}s"
return response
def process_exception(self, request, exception):
# Вызывается при необработанном исключении из view
# Вернуть HttpResponse — перехватить, вернуть None — передать дальше
import logging
logging.error("Unhandled exception: %s", exception, exc_info=True)
return None
6. Обработка исключений
Необработанные исключения из view перехватываются BaseHandler.handle_uncaught_exception. Http404 → 404 response (через handler404), PermissionDenied → 403, прочие → 500. При DEBUG=True Django рендерит debug-страницу с traceback.
Полная схема
# Псевдокод жизненного цикла
def wsgi_handler(environ, start_response):
request = WSGIRequest(environ)
# Middleware chain: сверху вниз
for middleware in MIDDLEWARE:
response = middleware.process_request(request)
if response: # short-circuit
break
else:
# URL resolving
callback, callback_args, callback_kwargs = resolver.resolve(request.path)
# View
try:
response = callback(request, *callback_args, **callback_kwargs)
except Exception as e:
for middleware in reversed(MIDDLEWARE):
response = middleware.process_exception(request, e)
if response:
break
else:
response = handle_500(request)
# Middleware chain: снизу вверх
for middleware in reversed(MIDDLEWARE):
response = middleware.process_response(request, response)
return response
Подводные камни
- Порядок middleware имеет значение —
AuthenticationMiddlewareдолжен идти послеSessionMiddleware, иначеrequest.userне будет доступен. - process_view vs __call__ — старый стиль
process_viewвызывается перед view, но после URL-резолвинга; новый стиль middleware__call__не имеетprocess_viewавтоматически — нужно переопределять явно. - Middleware возвращает response досрочно — middleware после него в цепочке не вызываются; если они добавляют критичные заголовки (CORS, HSTS), они будут пропущены.
- CBV не вызывает process_exception middleware — исключения внутри
dispatch()могут быть поглощеныView.dispatchдо того как middleware их увидит, если метод обёрнут в try-except. - Http404 в middleware — если middleware поднимает Http404, Django обработает его корректно только если это происходит в
__call__, не вprocess_requestстарого стиля. - request.body и request.POST взаимоисключают — чтение
request.bodyв middleware делаетrequest.POSTпустым в view, т.к. поток уже прочитан.
Common mistakes
- Описывать request response lifecycle только как термин и не показывать механизм на минимальном примере.
- Игнорировать ошибки, пустые данные, конкурентный доступ или границы транзакции.
- Не связывать поведение с официальным контрактом Django и реальной эксплуатацией.
What the interviewer is testing
- Объясняет request response lifecycle через последовательность действий, а не через набор ключевых слов.
- Приводит короткий кодовый пример или production-сценарий с ожидаемым поведением.
- Называет хотя бы один риск: производительность, безопасность, транзакции, память или сопровождение.
- Умеет обсудить отказ, наблюдаемость и rollback без изменения публичного контракта.