Заголовки, cookies и формы
Кроме пути, query и тела-JSON, FastAPI умеет извлекать данные из заголовков (Header), cookies (Cookie) и форм (Form) — каждым источником управляет свой маркер.
Источник данных вы выбираете явно:
Annotated[str, Header()]читает заголовок,Cookie()— куку,Form()— поле формы. Без маркера простой тип читается как query.
HTTP-запрос несёт больше, чем путь и тело. Заголовки сообщают язык, тип контента, токен авторизации. Cookies хранят сессию. Формы (application/x-www-form-urlencoded или multipart/form-data) приходят со страниц с классическими HTML-формами и при загрузке файлов. FastAPI даёт по маркеру на каждый источник.
from fastapi import FastAPI, Header, Cookie, Form
from typing import Annotated
app = FastAPI()
@app.get("/info")
async def info(
user_agent: Annotated[str | None, Header()] = None,
session_id: Annotated[str | None, Cookie()] = None,
):
return {"ua": user_agent, "session": session_id}
@app.post("/login")
async def login(
username: Annotated[str, Form()],
password: Annotated[str, Form()],
):
return {"user": username}
Тонкость с заголовками: HTTP-заголовки нечувствительны к регистру и пишутся через дефис (User-Agent), а имена параметров Python — через подчёркивание. FastAPI автоматически сопоставляет user_agent с заголовком user-agent. Формы требуют, чтобы был установлен python-multipart, и не могут соседствовать с JSON-телом в одном запросе: запрос либо form-data, либо JSON.
Как работает под капотом
Заголовки — это просто пары «имя: значение», где сравнение имени регистронезависимо. Формы в кодировке x-www-form-urlencoded — это та же query-строка, только в теле. Смоделируем оба разбора на stdlib:
from urllib.parse import parse_qs
# заголовки приходят как сырой текст; имена регистронезависимы
raw_headers = "User-Agent: Mozilla/5.0
Accept-Language: ru-RU
Cookie: session_id=abc123"
headers = {}
cookies = {}
for line in raw_headers.split("
"):
name, _, value = line.partition(":")
name = name.strip().lower() # нормализуем регистр
value = value.strip()
if name == "cookie":
for pair in value.split(";"):
k, _, v = pair.strip().partition("=")
cookies[k] = v
else:
headers[name] = value
print("user-agent ->", headers.get("user-agent"))
print("session_id (cookie) ->", cookies.get("session_id"))
# form-data в кодировке x-www-form-urlencoded — это query в теле
form_body = "username=ernest&password=secret"
form = {k: v[0] for k, v in parse_qs(form_body).items()}
print("форма ->", form)
Попробуй сам ▶ Видно три источника сразу: нормализация заголовка по регистру, разбор cookie и разбор формы.
Частые ошибки
Первая — забыть установить python-multipart и получить непонятную ошибку при использовании Form. Вторая — пытаться одновременно принять Form и Pydantic-тело-JSON в одном обработчике; это разные форматы запроса. Третья — вручную писать имена заголовков с дефисами в имени Python-параметра (так нельзя) вместо подчёркиваний. Четвёртая — хранить чувствительные данные в обычных, не HttpOnly куках.
Best practices
- Явно маркируйте источник:
Header(),Cookie(),Form()— это и читаемость, и документация. - Для form-data ставьте
python-multipart; не смешивайте формы с JSON-телом. - Чувствительные cookies помечайте
HttpOnly,Secure,SameSite. - Стандартные заголовки авторизации лучше обрабатывать через security-зависимости, а не вручную через
Header.
Загрузка файлов
Частный, но важный случай форм — загрузка файлов. Для этого есть тип UploadFile и маркер File(). UploadFile устроен умно: он не загружает весь файл в память целиком, а использует временный буфер с порогом, после которого данные уходят на диск. Это позволяет принимать большие файлы, не рискуя исчерпать оперативную память. Внутри обработчика у вас есть асинхронные методы read, write, seek, close, а также имя файла и тип содержимого. Как и обычные формы, загрузка файлов требует установленного python-multipart и формата multipart/form-data, поэтому в одном запросе нельзя смешать файл и JSON-тело. Если файлов несколько, объявляют list[UploadFile]. Это стандартный, безопасный по памяти способ принимать аватары, документы и вложения.
Итог: у каждого источника данных свой маркер. Заголовки сопоставляются регистронезависимо с заменой _ на -, формы — это URL-кодированное тело, и они несовместимы с JSON в одном запросе.