某些情况下,有必要向客户端(包括前端浏览器、其他应用程序、物联网设备等)返回错误提示,以便客户端能够了解错误的类型,从而做出应对。
目录
1 默认处理
1.1 错误介绍
1.2 使用 HTTPException
2 自定义处理
2.1 自定义响应头
2.2 自定义异常处理器
2.3 覆盖默认异常处理器
① 覆盖请求验证异常
② 覆盖 HTTPException 错误处理器
③ 使用 RequestValidationError 的请求体
📌源码地址:
https://gitee.com/yinyuu/fast-api_study_yinyu
1 默认处理
1.1 错误介绍
以下场景需要向客户端返回错误提示:
- 客户端没有执行操作的权限
- 客户端没有访问资源的权限
- 客户端要访问的项目不存在
- 等等 ...
遇到这些情况时,通常要返回 4XX(400 至 499)HTTP 状态码。 4XX 状态码与表示请求成功的 2XX(200 至 299) HTTP 状态码类似。
4XX 状态码表示客户端发生的错误,大家都知道「404 Not Found」错误吧~
1.2 使用 HTTPException
向客户端返回 HTTP 错误响应,可以使用 HTTPException。
📌代码
from fastapi import FastAPI, HTTPException #导入 HTTPException
app = FastAPI()
items = {"yinyu": "The Foo Wrestlers"}
@app.get("/items/{item_id}")
async def read_item(item_id: str):
if item_id not in items:
raise HTTPException(status_code=404, detail="Item not found")
return {"item": items[item_id]}
HTTPException 是额外包含了和 API 有关的常规 Python 异常,因为是 Python 异常,所以不能 return,只能 raise。
📌响应结果
本例中,客户端用 ID 请求的 item 不存在时,触发状态码为 404 的异常:
比如,此时请求为 127.0.0.1:8000/items/yinyu(item_id 为 「yinyu」)时,客户端会接收到 HTTP 状态码 - 200 及如下 JSON 响应结果:
{
"item": "The Foo Wrestlers"
}
但如果客户端请求 127.0.0.1:8000/items/bar(item_id 「bar」 不存在时),则会接收到 HTTP 状态码 - 404(「未找到」错误)及如下 JSON 响应结果:
{
"detail": "Item not found"
}
触发 HTTPException 时,可以用参数 detail 传递任何能转换为 JSON 的值,不仅限于 str。 还支持传递 dict、list 等数据结构。FastAPI 能自动处理这些数据,并将之转换为 JSON。
2 自定义处理
2.1 自定义响应头
一般情况下不会需要在代码中直接使用响应头,但是有时出于某些方面的安全需要,可能需要添加自定义响应头:
...
@app.get("/items-header/{item_id}")
async def read_item_header(item_id: str):
if item_id not in items:
raise HTTPException(
status_code=404,
detail="Item not found",
headers={"X-Error": "There goes my error"} #添加自定义响应头
)
return {"item": items[item_id]}
也就是说,触发 HTTPException 时,响应头也会自定义返回~
2.2 自定义异常处理器
假设要触发的自定义异常叫作 SelfDefinedException, 且需要 FastAPI 实现全局处理该异常。
此时,可以用 @app.exception_handler() 添加自定义异常控制器:
from fastapi import FastAPI, Request
from fastapi.responses import JSONResponse
app = FastAPI()
class SelfDefinedException(Exception):
def __init__(self, name: str):
self.name = name
@app.exception_handler(SelfDefinedException)
async def defined_exception_handler(request: Request, exc: SelfDefinedException):
return JSONResponse(
status_code=418,
content={"message": f"Oops! {exc.name} did something. There goes a rainbow..."},
)
@app.get("/self_defined/{name}")
async def read_defined(name: str):
if name == "yolo":
raise SelfDefinedException(name=name)
return {"unicorn_name": name}
请求 /self_defined/yolo 时,路径操作会触发 SelfDefinedException,且该异常将会被 defined_exception_handler处理。
接收到的错误信息清晰明了,HTTP 状态码为 418,JSON 内容如下:
{"message": "Oops! yolo did something. There goes a rainbow..."}
2.3 覆盖默认异常处理器
FastAPI 自带了一些默认异常处理器,触发 HTTPException 或请求无效数据时,这些处理器返回默认的 JSON 响应结果, 当然你也可以使用自定义处理器覆盖默认异常处理器。
① 覆盖请求验证异常
请求过程中,若请求包含无效数据则 FastAPI 内部会触发 RequestValidationError。 该异常内置了默认异常处理器,覆盖默认异常处理器时需要导入 RequestValidationError,并用 @app.excption_handler(RequestValidationError) 装饰异常处理器。
这样,异常处理器就可以接收 Request 与异常。
from fastapi import FastAPI, HTTPException
from fastapi.exceptions import RequestValidationError
from fastapi.responses import PlainTextResponse
app = FastAPI()
@app.exception_handler(RequestValidationError)
async def validation_exception_handler(request, exc):
return PlainTextResponse(str(exc), status_code=400)
@app.get("/items2/{item_id}")
async def read_item(item_id: int):
if item_id == 3:
raise HTTPException(status_code=418, detail="Nope! I don't like 3.")
return {"item_id": item_id}
访问 http://127.0.0.1:8000/items2/yinyu,可以看到以下文本格式的错误信息:
并且替换了以下默认 JSON 错误信息:
{
"detail": [
{
"loc": [
"path",
"item_id"
],
"msg": "value is not a valid integer",
"type": "type_error.integer"
}
]
}
② 覆盖 HTTPException 错误处理器
同理,也可以覆盖 HTTPException 处理器。
例如,只为错误返回纯文本响应,而不是返回 JSON 格式的内容:
from fastapi import FastAPI, HTTPException
from fastapi.responses import PlainTextResponse
from starlette.exceptions import HTTPException as StarletteHTTPException
app = FastAPI()
@app.exception_handler(StarletteHTTPException)
async def http_exception_handler(request, exc):
return PlainTextResponse(str(exc.detail), status_code=exc.status_code)
@app.get("/items3/{item_id}")
async def read_item(item_id: int):
if item_id == 3:
raise HTTPException(status_code=418, detail="Nope! I don't like 3.")
return {"item_id": item_id}
③ 使用 RequestValidationError 的请求体
RequestValidationError 包含其接收到的无效数据请求的 body 。
开发时,可以用这个请求体生成日志、调试错误,并返回给用户!!
from fastapi import FastAPI, Request, status
from fastapi.encoders import jsonable_encoder
from fastapi.exceptions import RequestValidationError
from fastapi.responses import JSONResponse
from pydantic import BaseModel
app = FastAPI()
@app.exception_handler(RequestValidationError)
async def validation_exception_handler(request: Request, exc: RequestValidationError):
return JSONResponse(
status_code=status.HTTP_422_UNPROCESSABLE_ENTITY,
content=jsonable_encoder({"detail": exc.errors(), "body": exc.body}),
)
class Item(BaseModel):
title: str
size: int
@app.post("/items4/")
async def create_item(item: Item):
return item
现在试着发送一个无效的 item,例如:
{
"title": "towel",
"size": "XL"
}
收到的响应包含 body 信息,并说明数据是无效的:
{
"detail": [
{
"loc": [
"body",
"size"
],
"msg": "value is not a valid integer",
"type": "type_error.integer"
}
],
"body": {
"title": "towel",
"size": "XL"
}
}