Как определяется порядок выполнения middleware в Python-фреймворках?

«Как определяется порядок выполнения middleware в Python-фреймворках?» — вопрос из категории Django, который задают на 10% собеседований Python Разработчик. Ниже — развёрнутый ответ с разбором ключевых моментов.

Ответ

Порядок выполнения middleware в Python-фреймворках (таких как Django или FastAPI) определяется последовательностью их объявления или добавления.

Middleware обычно работают по принципу «луковицы» (onion model): запрос проходит через них от внешнего к внутреннему слою, а ответ — в обратном порядке.

Django

В Django middleware обрабатывается в порядке, указанном в списке MIDDLEWARE в settings.py:

  • При обработке запроса: Middleware вызываются сверху вниз (от первого к последнему в списке).
  • При обработке ответа: Middleware вызываются снизу вверх (от последнего к первому в списке).
# settings.py
MIDDLEWARE = [
    'django.middleware.security.SecurityMiddleware',  # 1-й на запросе, последний на ответе
    'django.contrib.sessions.middleware.SessionMiddleware',  # 2-й на запросе, предпоследний на ответе
    'django.middleware.common.CommonMiddleware',
    # ... другие middleware
]

Почему это важно: Порядок критичен для корректной работы. Например, SecurityMiddleware должен быть одним из первых, чтобы применить меры безопасности до того, как запрос достигнет других компонентов. SessionMiddleware должен быть до middleware, которые используют сессии.

FastAPI

В FastAPI middleware вызываются в порядке их добавления к приложению с помощью декоратора @app.middleware("http") или метода app.add_middleware().

from fastapi import FastAPI, Request, Response

app = FastAPI()

@app.middleware("http")
async def add_process_time_header(request: Request, call_next):
    # Это middleware выполнится первым на запросе и последним на ответе
    print("Middleware 1: Начало")
    response = await call_next(request)
    print("Middleware 1: Конец")
    return response

@app.middleware("http")
async def log_requests(request: Request, call_next):
    # Это middleware выполнится вторым на запросе и предпоследним на ответе
    print("Middleware 2: Начало")
    response = await call_next(request)
    print("Middleware 2: Конец")
    return response

@app.get("/items/")
async def read_items():
    return {"message": "Hello from API"}

Почему это важно: Аналогично Django, порядок определяет последовательность модификации запроса перед его обработкой роутом и модификации ответа перед отправкой клиенту. Например, middleware для логирования или аутентификации обычно ставятся раньше.