7.2 KiB
Пользовательские классы 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] *}