Гайды

CORS, middleware и лимиты запросов в FastAPI

CORSMiddleware, request id, TrustedHost, slowapi, gzip, security headers и чек-лист для prod.

~9 мин чтения

CORS, middleware и лимиты запросов в FastAPI

CORS настраивается middleware Starlette; лимиты — через собственный middleware или библиотеку (slowapi на базе limits). Аутентификация — JWT и OAuth2; деплой за Nginx — Docker и Nginx.


1. CORS

python
from fastapi import FastAPI
from fastapi.middleware.cors import CORSMiddleware

app = FastAPI()

app.add_middleware(
    CORSMiddleware,
    allow_origins=["https://app.example.com"],  # не "*" с credentials
    allow_credentials=True,
    allow_methods=["GET", "POST", "PUT", "PATCH", "DELETE", "OPTIONS"],
    allow_headers=["*"],
    expose_headers=["X-Request-Id"],
    max_age=600,
)

allow_origins=["*"] несовместимо с allow_credentials=True по спецификации браузера.

Для публичного API без cookies иногда допустим * без credentials.


2. Свой middleware: логирование и request id

python
import uuid
from starlette.middleware.base import BaseHTTPMiddleware
from starlette.requests import Request

class RequestIdMiddleware(BaseHTTPMiddleware):
    async def dispatch(self, request: Request, call_next):
        rid = request.headers.get("X-Request-Id", str(uuid.uuid4()))
        request.state.request_id = rid
        response = await call_next(request)
        response.headers["X-Request-Id"] = rid
        return response

app.add_middleware(RequestIdMiddleware)

Порядок: последний добавленный выполняется первым на входе (onion model).


3. TrustedHost

python
from fastapi.middleware.trustedhost import TrustedHostMiddleware

app.add_middleware(TrustedHostMiddleware, allowed_hosts=["api.example.com", "*.example.com"])

За балансировщиком учитывайте proxy_headers в uvicorn — см. деплой.


4. Лимиты запросов (slowapi)

bash
pip install slowapi
python
from starlette.requests import Request
from slowapi import Limiter, _rate_limit_exceeded_handler
from slowapi.util import get_remote_address
from slowapi.errors import RateLimitExceeded

limiter = Limiter(key_func=get_remote_address)
app.state.limiter = limiter
app.add_exception_handler(RateLimitExceeded, _rate_limit_exceeded_handler)

@app.get("/search")
@limiter.limit("30/minute")
def search(request: Request, q: str):
    return {"q": q}

Для пользовательских лимитов key_func может брать user_id из JWT (осторожно с кэшированием Depends).

За reverse proxy get_remote_address увидит IP балансировщика. В uvicorn включайте --proxy-headers и --forwarded-allow-ips для доверенных сетей; в key_func разбирайте X-Forwarded-For (первый клиентский IP слева с учётом доверенных hop'ов), иначе лимит «на весь мир» схлопнется в один IP.

Для распределённого подсчёта в нескольких воркерах uvicorn храните счётчики в Redis (limits + storage) — in-memory slowapi не синхронизируется между процессами.


5. Таймауты тела

Ограничение размера тела задаётся на уровне ASGI-сервера (uvicorn) или reverse proxy (Nginx client_max_body_size).


6. GZip

python
from starlette.middleware.gzip import GZipMiddleware
app.add_middleware(GZipMiddleware, minimum_size=1000)

7. Security headers (частично в Nginx)

X-Content-Type-Options, X-Frame-Options, CSP — часто на границе CDN/Nginx, не дублируйте конфликтующе.


8. Чек-лист

  • Явный список allow_origins для prod.
  • Rate limit на /token и публичные поиски.
  • Request id в логах и ответе.
  • TrustedHost за известным прокси.

Дальше: Быстрый старт · тег FastAPI