committed by
GitHub
1 changed files with 261 additions and 0 deletions
@ -0,0 +1,261 @@ |
|||
# Обработка ошибок |
|||
|
|||
Существует множество ситуаций, когда необходимо сообщить об ошибке клиенту, использующему ваш 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://pydantic-docs.helpmanual.io/usage/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!} |
|||
``` |
|||
|
|||
В этом примере вы просто `выводите в терминал` ошибку с очень выразительным сообщением, но идея вам понятна. Вы можете использовать исключение, а затем просто повторно использовать стандартные обработчики исключений. |
Loading…
Reference in new issue