mirror of https://github.com/tiangolo/fastapi.git
256 lines
15 KiB
Markdown
256 lines
15 KiB
Markdown
# Обработка ошибок { #handling-errors }
|
||
|
||
Существует множество ситуаций, когда необходимо сообщить об ошибке клиенту, использующему ваш API.
|
||
|
||
Таким клиентом может быть браузер с фронтендом, чужой код, IoT-устройство и т.д.
|
||
|
||
Возможно, вам придется сообщить клиенту о следующем:
|
||
|
||
* Клиент не имеет достаточных привилегий для выполнения данной операции.
|
||
* Клиент не имеет доступа к данному ресурсу.
|
||
* Элемент, к которому клиент пытался получить доступ, не существует.
|
||
* и т.д.
|
||
|
||
В таких случаях обычно возвращается **HTTP-код статуса ответа** в диапазоне **400** (от 400 до 499).
|
||
|
||
Они похожи на двухсотые HTTP статус-коды (от 200 до 299), которые означают, что запрос обработан успешно.
|
||
|
||
Четырёхсотые статус-коды означают, что ошибка произошла по вине клиента.
|
||
|
||
Помните ли ошибки **"404 Not Found "** (и шутки) ?
|
||
|
||
## Использование `HTTPException` { #use-httpexception }
|
||
|
||
Для возврата клиенту HTTP-ответов с ошибками используется `HTTPException`.
|
||
|
||
### Импортируйте `HTTPException` { #import-httpexception }
|
||
|
||
{* ../../docs_src/handling_errors/tutorial001.py hl[1] *}
|
||
|
||
### Вызовите `HTTPException` в своем коде { #raise-an-httpexception-in-your-code }
|
||
|
||
`HTTPException` - это обычное исключение Python с дополнительными данными, актуальными для API.
|
||
|
||
Поскольку это исключение Python, то его не `возвращают`, а `вызывают`.
|
||
|
||
Это также означает, что если вы находитесь внутри функции, которая вызывается внутри вашей *функции операции пути*, и вы поднимаете `HTTPException` внутри этой функции, то она не будет выполнять остальной код в *функции операции пути*, а сразу завершит запрос и отправит HTTP-ошибку из `HTTPException` клиенту.
|
||
|
||
О том, насколько выгоднее `вызывать` исключение, чем `возвращать` значение, будет рассказано в разделе, посвященном зависимостям и безопасности.
|
||
|
||
В данном примере, когда клиент запрашивает элемент по несуществующему ID, возникает исключение со статус-кодом `404`:
|
||
|
||
{* ../../docs_src/handling_errors/tutorial001.py hl[11] *}
|
||
|
||
### Возвращаемый ответ { #the-resulting-response }
|
||
|
||
Если клиент запросит `http://example.com/items/foo` (`item_id` `"foo"`), то он получит статус-код 200 и ответ в формате JSON:
|
||
|
||
```JSON
|
||
{
|
||
"item": "The Foo Wrestlers"
|
||
}
|
||
```
|
||
|
||
Но если клиент запросит `http://example.com/items/bar` (несуществующий `item_id` `"bar"`), то он получит статус-код 404 (ошибка "не найдено") и JSON-ответ в виде:
|
||
|
||
```JSON
|
||
{
|
||
"detail": "Item not found"
|
||
}
|
||
```
|
||
|
||
/// tip | Подсказка
|
||
|
||
При вызове `HTTPException` в качестве параметра `detail` можно передавать любое значение, которое может быть преобразовано в JSON, а не только `str`.
|
||
|
||
Вы можете передать `dict`, `list` и т.д.
|
||
|
||
Они автоматически обрабатываются **FastAPI** и преобразуются в JSON.
|
||
|
||
///
|
||
|
||
## Добавление пользовательских заголовков { #add-custom-headers }
|
||
|
||
В некоторых ситуациях полезно иметь возможность добавлять пользовательские заголовки к ошибке HTTP. Например, для некоторых типов безопасности.
|
||
|
||
Скорее всего, вам не потребуется использовать его непосредственно в коде.
|
||
|
||
Но в случае, если это необходимо для продвинутого сценария, можно добавить пользовательские заголовки:
|
||
|
||
{* ../../docs_src/handling_errors/tutorial002.py hl[14] *}
|
||
|
||
## Установка пользовательских обработчиков исключений { #install-custom-exception-handlers }
|
||
|
||
Вы можете добавить пользовательские обработчики исключений с помощью <a href="https://www.starlette.dev/exceptions/" class="external-link" target="_blank">то же самое исключение - утилиты от Starlette</a>.
|
||
|
||
Допустим, у вас есть пользовательское исключение `UnicornException`, которое вы (или используемая вами библиотека) можете `вызвать`.
|
||
|
||
И вы хотите обрабатывать это исключение глобально с помощью FastAPI.
|
||
|
||
Можно добавить собственный обработчик исключений с помощью `@app.exception_handler()`:
|
||
|
||
{* ../../docs_src/handling_errors/tutorial003.py hl[5:7,13:18,24] *}
|
||
|
||
Здесь, если запросить `/unicorns/yolo`, то *операция пути* вызовет `UnicornException`.
|
||
|
||
Но оно будет обработано `unicorn_exception_handler`.
|
||
|
||
Таким образом, вы получите чистую ошибку с кодом состояния HTTP `418` и содержимым JSON:
|
||
|
||
```JSON
|
||
{"message": "Oops! yolo did something. There goes a rainbow..."}
|
||
```
|
||
|
||
/// note | Технические детали
|
||
|
||
Также можно использовать `from starlette.requests import Request` и `from starlette.responses import JSONResponse`.
|
||
|
||
**FastAPI** предоставляет тот же `starlette.responses`, что и `fastapi.responses`, просто для удобства разработчика. Однако большинство доступных ответов поступает непосредственно из Starlette. То же самое касается и `Request`.
|
||
|
||
///
|
||
|
||
## Переопределение стандартных обработчиков исключений { #override-the-default-exception-handlers }
|
||
|
||
**FastAPI** имеет некоторые обработчики исключений по умолчанию.
|
||
|
||
Эти обработчики отвечают за возврат стандартных JSON-ответов при `вызове` `HTTPException` и при наличии в запросе недопустимых данных.
|
||
|
||
Вы можете переопределить эти обработчики исключений на свои собственные.
|
||
|
||
### Переопределение исключений проверки запроса { #override-request-validation-exceptions }
|
||
|
||
Когда запрос содержит недопустимые данные, **FastAPI** внутренне вызывает ошибку `RequestValidationError`.
|
||
|
||
А также включает в себя обработчик исключений по умолчанию.
|
||
|
||
Чтобы переопределить его, импортируйте `RequestValidationError` и используйте его с `@app.exception_handler(RequestValidationError)` для создания обработчика исключений.
|
||
|
||
Обработчик исключения получит объект `Request` и исключение.
|
||
|
||
{* ../../docs_src/handling_errors/tutorial004.py hl[2,14:16] *}
|
||
|
||
Теперь, если перейти к `/items/foo`, то вместо стандартной JSON-ошибки с:
|
||
|
||
```JSON
|
||
{
|
||
"detail": [
|
||
{
|
||
"loc": [
|
||
"path",
|
||
"item_id"
|
||
],
|
||
"msg": "value is not a valid integer",
|
||
"type": "type_error.integer"
|
||
}
|
||
]
|
||
}
|
||
```
|
||
|
||
вы получите текстовую версию:
|
||
|
||
```
|
||
1 validation error
|
||
path -> item_id
|
||
value is not a valid integer (type=type_error.integer)
|
||
```
|
||
|
||
#### `RequestValidationError` или `ValidationError` { #requestvalidationerror-vs-validationerror }
|
||
|
||
/// warning | Внимание
|
||
|
||
Это технические детали, которые можно пропустить, если они не важны для вас сейчас.
|
||
|
||
///
|
||
|
||
`RequestValidationError` является подклассом Pydantic <a href="https://docs.pydantic.dev/latest/concepts/models/#error-handling" class="external-link" target="_blank">`ValidationError`</a>.
|
||
|
||
**FastAPI** использует его для того, чтобы, если вы используете Pydantic-модель в `response_model`, и ваши данные содержат ошибку, вы увидели ошибку в журнале.
|
||
|
||
Но клиент/пользователь этого не увидит. Вместо этого клиент получит сообщение "Internal Server Error" с кодом состояния HTTP `500`.
|
||
|
||
Так и должно быть, потому что если в вашем *ответе* или где-либо в вашем коде (не в *запросе* клиента) возникает Pydantic `ValidationError`, то это действительно ошибка в вашем коде.
|
||
|
||
И пока вы не устраните ошибку, ваши клиенты/пользователи не должны иметь доступа к внутренней информации о ней, так как это может привести к уязвимости в системе безопасности.
|
||
|
||
### Переопределите обработчик ошибок `HTTPException` { #override-the-httpexception-error-handler }
|
||
|
||
Аналогичным образом можно переопределить обработчик `HTTPException`.
|
||
|
||
Например, для этих ошибок можно вернуть обычный текстовый ответ вместо JSON:
|
||
|
||
{* ../../docs_src/handling_errors/tutorial004.py hl[3:4,9:11,22] *}
|
||
|
||
/// note | Технические детали
|
||
|
||
Можно также использовать `from starlette.responses import PlainTextResponse`.
|
||
|
||
**FastAPI** предоставляет тот же `starlette.responses`, что и `fastapi.responses`, просто для удобства разработчика. Однако большинство доступных ответов поступает непосредственно из Starlette.
|
||
|
||
///
|
||
|
||
### Используйте тело `RequestValidationError` { #use-the-requestvalidationerror-body }
|
||
|
||
Ошибка `RequestValidationError` содержит полученное `тело` с недопустимыми данными.
|
||
|
||
Вы можете использовать его при разработке приложения для регистрации тела и его отладки, возврата пользователю и т.д.
|
||
|
||
{* ../../docs_src/handling_errors/tutorial005.py hl[14] *}
|
||
|
||
Теперь попробуйте отправить недействительный элемент, например:
|
||
|
||
```JSON
|
||
{
|
||
"title": "towel",
|
||
"size": "XL"
|
||
}
|
||
```
|
||
|
||
Вы получите ответ о том, что данные недействительны, содержащий следующее тело:
|
||
|
||
```JSON hl_lines="12-15"
|
||
{
|
||
"detail": [
|
||
{
|
||
"loc": [
|
||
"body",
|
||
"size"
|
||
],
|
||
"msg": "value is not a valid integer",
|
||
"type": "type_error.integer"
|
||
}
|
||
],
|
||
"body": {
|
||
"title": "towel",
|
||
"size": "XL"
|
||
}
|
||
}
|
||
```
|
||
|
||
#### `HTTPException` в FastAPI или в Starlette { #fastapis-httpexception-vs-starlettes-httpexception }
|
||
|
||
**FastAPI** имеет собственный `HTTPException`.
|
||
|
||
Класс ошибок **FastAPI** `HTTPException` наследует от класса ошибок Starlette `HTTPException`.
|
||
|
||
Единственное отличие состоит в том, что `HTTPException` в **FastAPI** принимает любые данные, пригодные для преобразования в JSON, в поле `detail`, тогда как `HTTPException` в Starlette принимает для него только строки.
|
||
|
||
Таким образом, вы можете продолжать вызывать `HTTPException` от **FastAPI** как обычно в своем коде.
|
||
|
||
Но когда вы регистрируете обработчик исключений, вы должны зарегистрировать его для `HTTPException` от Starlette.
|
||
|
||
Таким образом, если какая-либо часть внутреннего кодa Starlette, расширение или плагин Starlette вызовет исключение Starlette `HTTPException`, ваш обработчик сможет перехватить и обработать его.
|
||
|
||
В данном примере, чтобы иметь возможность использовать оба `HTTPException` в одном коде, исключения Starlette переименованы в `StarletteHTTPException`:
|
||
|
||
```Python
|
||
from starlette.exceptions import HTTPException as StarletteHTTPException
|
||
```
|
||
|
||
### Переиспользование обработчиков исключений **FastAPI** { #reuse-fastapis-exception-handlers }
|
||
|
||
Если вы хотите использовать исключение вместе с теми же обработчиками исключений по умолчанию из **FastAPI**, вы можете импортировать и повторно использовать обработчики исключений по умолчанию из `fastapi.exception_handlers`:
|
||
|
||
{* ../../docs_src/handling_errors/tutorial006.py hl[2:5,15,21] *}
|
||
|
||
В этом примере вы просто `выводите в терминал` ошибку с очень выразительным сообщением, но идея вам понятна. Вы можете использовать исключение, а затем просто повторно использовать стандартные обработчики исключений.
|