You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

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] *}