fastapi/docs/ru/docs/tutorial/handling-errors.md

274 lines
14 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# Обработка ошибок
Существует множество ситуаций, когда необходимо сообщить об ошибке клиенту, использующему ваш API.
Таким клиентом может быть браузер с фронтендом, чужой код, IoT-устройство и т.д.
Возможно, вам придется сообщить клиенту о следующем:
* Клиент не имеет достаточных привилегий для выполнения данной операции.
* Клиент не имеет доступа к данному ресурсу.
* Элемент, к которому клиент пытался получить доступ, не существует.
* и т.д.
В таких случаях обычно возвращается **HTTP-код статуса ответа** в диапазоне **400** (от 400 до 499).
Они похожи на двухсотые HTTP статус-коды (от 200 до 299), которые означают, что запрос обработан успешно.
Четырёхсотые статус-коды означают, что ошибка произошла по вине клиента.
Помните ли ошибки **"404 Not Found "** (и шутки) ?
## Использование `HTTPException`
Для возврата клиенту HTTP-ответов с ошибками используется `HTTPException`.
### Импортируйте `HTTPException`
```Python hl_lines="1"
{!../../docs_src/handling_errors/tutorial001.py!}
```
### Вызовите `HTTPException` в своем коде
`HTTPException` - это обычное исключение Python с дополнительными данными, актуальными для API.
Поскольку это исключение Python, то его не `возвращают`, а `вызывают`.
Это также означает, что если вы находитесь внутри функции, которая вызывается внутри вашей *функции операции пути*, и вы поднимаете `HTTPException` внутри этой функции, то она не будет выполнять остальной код в *функции операции пути*, а сразу завершит запрос и отправит HTTP-ошибку из `HTTPException` клиенту.
О том, насколько выгоднее `вызывать` исключение, чем `возвращать` значение, будет рассказано в разделе, посвященном зависимостям и безопасности.
В данном примере, когда клиент запрашивает элемент по несуществующему ID, возникает исключение со статус-кодом `404`:
```Python hl_lines="11"
{!../../docs_src/handling_errors/tutorial001.py!}
```
### Возвращаемый ответ
Если клиент запросит `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.
///
## Добавление пользовательских заголовков
В некоторых ситуациях полезно иметь возможность добавлять пользовательские заголовки к ошибке HTTP. Например, для некоторых типов безопасности.
Скорее всего, вам не потребуется использовать его непосредственно в коде.
Но в случае, если это необходимо для продвинутого сценария, можно добавить пользовательские заголовки:
```Python hl_lines="14"
{!../../docs_src/handling_errors/tutorial002.py!}
```
## Установка пользовательских обработчиков исключений
Вы можете добавить пользовательские обработчики исключений с помощью <a href="https://www.starlette.io/exceptions/" class="external-link" target="_blank">то же самое исключение - утилиты от Starlette</a>.
Допустим, у вас есть пользовательское исключение `UnicornException`, которое вы (или используемая вами библиотека) можете `вызвать`.
И вы хотите обрабатывать это исключение глобально с помощью FastAPI.
Можно добавить собственный обработчик исключений с помощью `@app.exception_handler()`:
```Python hl_lines="5-7 13-18 24"
{!../../docs_src/handling_errors/tutorial003.py!}
```
Здесь, если запросить `/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`.
///
## Переопределение стандартных обработчиков исключений
**FastAPI** имеет некоторые обработчики исключений по умолчанию.
Эти обработчики отвечают за возврат стандартных JSON-ответов при `вызове` `HTTPException` и при наличии в запросе недопустимых данных.
Вы можете переопределить эти обработчики исключений на свои собственные.
### Переопределение исключений проверки запроса
Когда запрос содержит недопустимые данные, **FastAPI** внутренне вызывает ошибку `RequestValidationError`.
А также включает в себя обработчик исключений по умолчанию.
Чтобы переопределить его, импортируйте `RequestValidationError` и используйте его с `@app.exception_handler(RequestValidationError)` для создания обработчика исключений.
Обработчик исключения получит объект `Request` и исключение.
```Python hl_lines="2 14-16"
{!../../docs_src/handling_errors/tutorial004.py!}
```
Теперь, если перейти к `/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`
/// 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`
Аналогичным образом можно переопределить обработчик `HTTPException`.
Например, для этих ошибок можно вернуть обычный текстовый ответ вместо JSON:
```Python hl_lines="3-4 9-11 22"
{!../../docs_src/handling_errors/tutorial004.py!}
```
/// note | "Технические детали"
Можно также использовать `from starlette.responses import PlainTextResponse`.
**FastAPI** предоставляет тот же `starlette.responses`, что и `fastapi.responses`, просто для удобства разработчика. Однако большинство доступных ответов поступает непосредственно из Starlette.
///
### Используйте тело `RequestValidationError`
Ошибка `RequestValidationError` содержит полученное `тело` с недопустимыми данными.
Вы можете использовать его при разработке приложения для регистрации тела и его отладки, возврата пользователю и т.д.
```Python hl_lines="14"
{!../../docs_src/handling_errors/tutorial005.py!}
```
Теперь попробуйте отправить недействительный элемент, например:
```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
**FastAPI** имеет собственный `HTTPException`.
Класс ошибок **FastAPI** `HTTPException` наследует от класса ошибок Starlette `HTTPException`.
Единственное отличие заключается в том, что `HTTPException` от **FastAPI** позволяет добавлять заголовки, которые будут включены в ответ.
Он необходим/используется внутри системы для OAuth 2.0 и некоторых утилит безопасности.
Таким образом, вы можете продолжать вызывать `HTTPException` от **FastAPI** как обычно в своем коде.
Но когда вы регистрируете обработчик исключений, вы должны зарегистрировать его для `HTTPException` от Starlette.
Таким образом, если какая-либо часть внутреннего кода Starlette, расширение или плагин Starlette вызовет исключение Starlette `HTTPException`, ваш обработчик сможет перехватить и обработать его.
В данном примере, чтобы иметь возможность использовать оба `HTTPException` в одном коде, исключения Starlette переименованы в `StarletteHTTPException`:
```Python
from starlette.exceptions import HTTPException as StarletteHTTPException
```
### Переиспользование обработчиков исключений **FastAPI**
Если вы хотите использовать исключение вместе с теми же обработчиками исключений по умолчанию из **FastAPI**, вы можете импортировать и повторно использовать обработчики исключений по умолчанию из `fastapi.exception_handlers`:
```Python hl_lines="2-5 15 21"
{!../../docs_src/handling_errors/tutorial006.py!}
```
В этом примере вы просто `выводите в терминал` ошибку с очень выразительным сообщением, но идея вам понятна. Вы можете использовать исключение, а затем просто повторно использовать стандартные обработчики исключений.