fastapi/docs/ru/docs/how-to/custom-request-and-route.md

7.2 KiB
Raw Blame History

Пользовательские классы Request и APIRoute

В некоторых случаях может понадобиться переопределить логику, используемую классами Request и APIRoute.

В частности, это может быть хорошей альтернативой логике в middleware.

Например, если вы хотите прочитать или изменить тело запроса до того, как оно будет обработано вашим приложением.

/// danger | Опасность

Это «продвинутая» возможность.

Если вы только начинаете работать с FastAPI, возможно, стоит пропустить этот раздел.

///

Сценарии использования

Некоторые сценарии:

  • Преобразование тел запросов, не в формате JSON, в JSON (например, msgpack).
  • Распаковка тел запросов, сжатых с помощью gzip.
  • Автоматическое логирование всех тел запросов.

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

Посмотрим как использовать пользовательский подкласс Request для распаковки gzip-запросов.

И подкласс APIRoute, чтобы использовать этот пользовательский класс запроса.

Создать пользовательский класс GzipRequest

/// tip | Совет

Это учебный пример, демонстрирующий принцип работы. Если вам нужна поддержка Gzip, вы можете использовать готовый GzipMiddleware{.internal-link target=_blank}.

///

Сначала создадим класс GzipRequest, который переопределит метод Request.body() и распакует тело запроса при наличии соответствующего HTTP-заголовка.

Если в заголовке нет gzip, он не будет пытаться распаковывать тело.

Таким образом, один и тот же класс маршрута сможет обрабатывать как gzip-сжатые, так и несжатые запросы.

{* ../../docs_src/custom_request_and_route/tutorial001.py hl[8:15] *}

Создать пользовательский класс GzipRoute

Далее создадим пользовательский подкласс fastapi.routing.APIRoute, который будет использовать GzipRequest.

На этот раз он переопределит метод APIRoute.get_route_handler().

Этот метод возвращает функцию. Именно эта функция получает HTTP-запрос и возвращает HTTP-ответ.

Здесь мы используем её, чтобы создать GzipRequest из исходного HTTP-запроса.

{* ../../docs_src/custom_request_and_route/tutorial001.py hl[18:26] *}

/// note | Технические детали

У Request есть атрибут request.scope — это просто Python-dict, содержащий метаданные, связанные с HTTP-запросом.

У Request также есть request.receive — функция для «получения» тела запроса.

И dict scope, и функция 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 и получат дополнительный HTTP-заголовок X-Response-Time в ответе с временем, затраченным на формирование ответа:

{* ../../docs_src/custom_request_and_route/tutorial003.py hl[13:20] *}