6.8 KiB
Классы кастомных запросов и APIRoute
В некоторых случаях вы можете захотеть переопределить логику, используемую классами Request
и APIRoute
.
В частности, это может быть хорошей альтернативой логике в middleware.
Например, если вы хотите прочитать или изменить тело запроса до его обработки вашим приложением.
/// danger | Опасность
Это "продвинутая" функция.
Если вы только начинаете с FastAPI, возможно, вам стоит пропустить этот раздел.
///
Примеры использования
Некоторые примеры использования включают:
- Конвертацию тел запросов, которые не являются JSON, в JSON (например,
msgpack
). - Распаковку тел запросов, сжатых с помощью gzip.
- Автоматическое логирование всех тел запросов.
Обработка кастомных кодировок тел запросов
Давайте рассмотрим, как использовать подкласс Request
для распаковки запросов в формате gzip.
И подкласс APIRoute
для использования этого кастомного класса запроса.
Создание кастомного класса GzipRequest
/// tip | Подсказка
Это учебный пример, демонстрирующий, как это работает; если вам нужна поддержка Gzip, вы можете использовать предоставленный GzipMiddleware
{.internal-link target=_blank}.
///
Сначала мы создаем класс GzipRequest
, который будет переопределять метод Request.body()
для распаковки тела при наличии соответствующего заголовка.
Если в заголовке нет gzip
, он не будет пытаться распаковать тело.
Таким образом, один и тот же класс маршрута может обрабатывать как сжатые, так и несжатые запросы в формате gzip.
{* ../../docs_src/custom_request_and_route/tutorial001.py hl[8:15] *}
Создание кастомного класса GzipRoute
Далее, мы создаем кастомный подкласс fastapi.routing.APIRoute
, который будет использовать GzipRequest
.
На этот раз он переопределит метод APIRoute.get_route_handler()
.
Этот метод возвращает функцию. И именно эта функция принимает запрос и возвращает ответ.
Здесь мы используем его для создания GzipRequest
из оригинального запроса.
{* ../../docs_src/custom_request_and_route/tutorial001.py hl[18:26] *}
/// note | Технические детали
Request
имеет атрибут request.scope
, это просто Python dict
, содержащий метаданные, связанные с запросом.
Request
также имеет request.receive
, это функция для "получения" тела запроса.
scope
dict
и функция receive
обе являются частью спецификации ASGI.
И эти две вещи, scope
и receive
, — то, что нужно для создания нового экземпляра Request
.
Чтобы узнать больше о Request
, ознакомьтесь с документацией Starlette о запросах.
///
Единственное, что функция, возвращаемая GzipRequest.get_route_handler
, делает иначе — это преобразует Request
в GzipRequest
.
Делая это, наш GzipRequest
позаботится о распаковке данных (если это необходимо) до передачи их нашим операциям на пути.
После этого вся логика обработки остается той же.
Но из-за наших изменений в GzipRequest.body
, тело запроса будет автоматически распаковано, когда оно понадобится FastAPI.
Доступ к телу запроса в обработчике исключений
/// tip | Подсказка
Для решения этой же проблемы, вероятно, гораздо проще использовать body
в кастомном обработчике для RequestValidationError
(Обработка ошибок{.internal-link target=_blank}).
Но этот пример все же действителен и показывает, как взаимодействовать с внутренними компонентами.
///
Мы также можем использовать этот же подход для доступа к телу запроса в обработчике исключений.
Все, что нам нужно сделать, это обработать запрос внутри блока try
/except
:
{* ../../docs_src/custom_request_and_route/tutorial002.py hl[13,15] *}
Если произойдет исключение, экземпляр Request
все равно будет в доступной области, так что мы сможем прочитать и использовать тело запроса при обработке ошибки:
{* ../../docs_src/custom_request_and_route/tutorial002.py hl[16:18] *}
Кастомный класс APIRoute
в маршрутизаторе
Вы также можете установить параметр route_class
для APIRouter
:
{* ../../docs_src/custom_request_and_route/tutorial003.py hl[26] *}
В этом примере операции пути под router
будут использовать кастомный класс TimedRoute
и добавят в ответ заголовок X-Response-Time
с временем, затраченным на генерацию ответа:
{* ../../docs_src/custom_request_and_route/tutorial003.py hl[13:20] *}