Middleware, CORS и фоновые задачи

Middleware оборачивает каждый запрос сквозной логикой (логирование, заголовки), CORS разрешает браузерным фронтендам обращаться к API, а BackgroundTasks выполняет работу после отправки ответа.

Три инструмента «по краям» запроса: middleware работает до и после обработчика для всех запросов, CORS — частный, но критичный middleware для браузеров, а фоновые задачи откладывают неважное на «после ответа».

Некоторая логика не привязана к конкретному эндпоинту: засечь время обработки, добавить заголовок, залогировать запрос. Это работа middleware — слоя, через который проходит каждый запрос по пути к обработчику и каждый ответ на пути обратно. Middleware образуют «луковицу»: запрос проходит слои внутрь до обработчика, ответ — наружу в обратном порядке.

from fastapi import FastAPI, Request, BackgroundTasks
from fastapi.middleware.cors import CORSMiddleware
import time

app = FastAPI()

app.add_middleware(
    CORSMiddleware,
    allow_origins=["https://my-frontend.app"],
    allow_methods=["*"],
    allow_headers=["*"],
)

@app.middleware("http")
async def add_timing(request: Request, call_next):
    start = time.perf_counter()
    response = await call_next(request)          # вызываем следующий слой/обработчик
    response.headers["X-Process-Time"] = str(time.perf_counter() - start)
    return response

@app.post("/signup")
async def signup(email: str, tasks: BackgroundTasks):
    tasks.add_task(send_welcome_email, email)    # выполнится ПОСЛЕ ответа
    return {"status": "ok"}

CORS — особый случай. Браузер по умолчанию запрещает странице с одного домена обращаться к API на другом; чтобы разрешить, сервер должен прислать нужные заголовки. CORSMiddleware делает это, и без него фронтенд получит загадочную ошибку запроса. BackgroundTasks же решает другую проблему: отправку письма не стоит заставлять ждать пользователя — ответ уходит сразу, а письмо шлётся фоном.

Как работает под капотом

Middleware — это вложенные обёртки: каждый слой получает запрос, может что-то сделать до, вызвать следующий слой и что-то сделать после. Смоделируем «луковицу» на stdlib:

def handler(request):
    return f"ответ на {request}"

def timing_mw(next_layer):
    def wrapper(request):
        print("  [timing] до обработки")
        result = next_layer(request)
        print("  [timing] после обработки")
        return result
    return wrapper

def logging_mw(next_layer):
    def wrapper(request):
        print("[log] входящий запрос:", request)
        result = next_layer(request)
        print("[log] исходящий ответ:", result)
        return result
    return wrapper

# собираем «луковицу»: log -> timing -> handler
app = logging_mw(timing_mw(handler))
print("РЕЗУЛЬТАТ:", app("GET /items"))

Попробуй сам ▶ Порядок логов показывает «луковицу»: внешний слой (log) входит первым и выходит последним, ровно как стек middleware в FastAPI.

Частые ошибки

Первая — забыть CORS и долго искать, почему фронтенд не может достучаться до API (ошибка видна только в браузере). Вторая — ставить allow_origins=["*"] вместе с allow_credentials=True, что небезопасно и не работает по спецификации. Третья — класть в BackgroundTasks критичную работу: если процесс упадёт, задача потеряется (для важного нужна очередь вроде Celery). Четвёртая — тяжёлая блокирующая логика в middleware, тормозящая все запросы.

Best practices

  • Настраивайте CORS явно: конкретные allow_origins, а не * с credentials.
  • В middleware — лёгкая сквозная логика (тайминги, заголовки, корреляция логов).
  • BackgroundTasks — для некритичных пост-действий (письма, уведомления); для важного — внешняя очередь.
  • Помните про порядок: middleware образуют стек, внешний срабатывает первым на вход.

Middleware против зависимостей: что выбрать

И middleware, и зависимости умеют выполнять код «вокруг» обработки, и новички путаются, что применять. Разница в области действия и доступе к данным. Middleware работает на уровне сырого запроса и ответа, видит их целиком, применяется ко всем маршрутам без разбора и идеален для сквозных задач: логирование, тайминги, добавление заголовков, корреляционные идентификаторы. Но он не знает про разобранные параметры и модели — для него тело это просто байты. Зависимость, наоборот, встроена в конкретный обработчик, видит типизированные параметры, участвует в документации и легко подменяется в тестах — она для логики, специфичной для эндпоинта: авторизация, выдача сессии, проверка прав. Правило: общее и низкоуровневое для всех запросов — middleware; типизированное и точечное для конкретных маршрутов — зависимость. Понимание этой границы избавляет от попыток затолкать бизнес-проверки в middleware, где у вас нет ни типов, ни удобной тестируемости.

Итог: middleware даёт сквозную логику «луковицей» вокруг каждого запроса, CORS разрешает доступ браузерным фронтендам, а BackgroundTasks переносит некритичную работу на время после ответа. Для гарантированных фоновых задач используйте настоящую очередь.

Проверьте себя
1. Зачем нужен CORSMiddleware?
AЧтобы ускорить запросы
BЧтобы разрешить браузерному фронтенду на другом домене обращаться к API, прислав нужные CORS-заголовки
CЧтобы шифровать тело запроса
DЧтобы кэшировать ответы
2. Что важно помнить про BackgroundTasks?
AОни выполняются до отправки ответа
BОни гарантированно переживут падение процесса
CОни выполняются после отправки ответа и не гарантированы при падении процесса — для критичного нужна внешняя очередь
DОни блокируют ответ до завершения задачи