DjangoSeniorSystem design

Объясните жизненный цикл запроса/ответа в 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 без изменения публичного контракта.

Sources

Related topics