Заголовки FastAPI: как принять X-Request-ID и вернуть его в ответе

Клиент отправляет X-Request-ID, чтобы найти свой запрос в журнале. Обработчик получает None: разработчик объявил обычный параметр функции и ожидал, что FastAPI догадается об источнике. Для HTTP-заголовка нужен Header; явный alias дополнительно отделяет Python-имя аргумента от сетевого имени.

Заголовок и query-параметр — разные источники

Скалярный аргумент без специального объявления часто трактуется как query-параметр. Header прямо указывает источник. По умолчанию FastAPI преобразует подчёркивания в дефисы: user_agent соответствует User-Agent. Явный alias="X-Request-ID" полезен, когда Python-имя request_id не должно повторять технический префикс.

Имена HTTP-заголовков регистронезависимы. Значение при этом не становится доверенным идентификатором только потому, что пришло в заголовке. В примере ограничим длину и допустимые символы. Это не позволяет клиенту передавать переносы строк или произвольный большой текст для будущего журнала.

Проверим извлечение и обратную передачу

Нужны FastAPI и HTTPX. Если идентификатор не пришёл, сервер создаёт uuid. Для переданного значения проверяем точное совпадение тела и заголовка ответа, а для ошибочного — отказ до выполнения обработчика.

Сохраните весь блок в файл и запустите Python 3.11+ в отдельном окружении с пакетами fastapi, httpx. Для моделей используется Pydantic v2; отдельный сервер для TestClient не нужен.

from typing import Annotated
from uuid import uuid4
from fastapi import FastAPI, Header, Response
from fastapi.testclient import TestClient

app = FastAPI()
@app.get("/status")
def status(response: Response,
           request_id: Annotated[str | None, Header(alias="X-Request-ID", min_length=1,
               max_length=64, pattern=r"^[A-Za-z0-9_-]+$")] = None):
    value = request_id or uuid4().hex
    response.headers["X-Request-ID"] = value
    return {"request_id": value}

with TestClient(app) as client:
    r = client.get("/status", headers={"x-request-id": "check_17"})
    assert r.json() == {"request_id": "check_17"}
    assert r.headers["x-request-id"] == "check_17"
    generated = client.get("/status")
    assert len(generated.json()["request_id"]) == 32
    assert client.get("/status", headers={"X-Request-ID": "has spaces"}).status_code == 422
print("provided and generated request ids checked")

Как использовать идентификатор для диагностики

Одинаковый идентификатор в ответе и журнале помогает сопоставить сообщение пользователя с обработанным запросом. Если запрос идёт через несколько сервисов, договоритесь, кто создаёт значение и кто передаёт его дальше. Не путайте его с id пользователя или секретным токеном: это техническая метка обращения.

Для всего приложения удобнее middleware, которое устанавливает идентификатор до маршрута и добавляет его к ошибочным ответам. Параметр Header в одном обработчике полезен как первый явный шаг и для специальных заголовков конкретной операции. Если валидация здесь вернёт 422, код маршрута не выполнится и не добавит собственный заголовок.

Проверьте браузерный доступ к ответу

Когда frontend работает на другом origin, браузеру может потребоваться expose_headers=["X-Request-ID"] в CORS-настройке для чтения этого заголовка JavaScript-кодом. Наличие заголовка в панели Network ещё не означает, что response.headers.get доступен скрипту. Проверяйте именно клиентский путь, если ID показывается пользователю.

Не используйте возвращённый клиентский ID как доказательство авторства запроса. Пользователь сам способен прислать знакомое значение. Для трассировки это допустимо при нормализации и правильном контексте журнала; для авторизации нужен отдельный проверенный credential.

Следующая практика

Закрепите получение общих данных запроса в открытом уроке Depends. Затем вынесите обработку идентификатора обращения в отдельную зависимость, сохранив явный Header и тест источника значения. Откройте урок о зависимостях и продолжите практику.

В бесплатной программе FastAPI вы соедините обработчики с авторизацией и тестами. Сохраните идентификатор обращения рядом с этими сценариями, чтобы диагностировать уже полноценный API. Откройте программу курса.

Продолжить на практике