提出问题
在 Python Web框架-异常处理 例子中,发现抛错时的返回格式,与成功运行时的返回格式不一致:
成功运行返回格式:
{
"code": 200,
"data": {
"name": "李雷",
"age": 21
}
}
报错返回格式:
{
"detail": "没有此学号的学生"
}
分析
应该保持一致:
{
"code": 200, // 都有状态码
"msg": "", // 报错时显示错误信息
"data": { // 查询数据时,返回数据。报错无此项
"name": "李雷",
"age": 21
}
}
解决
使用全局异常处理
介绍
语法:@app.exception_handler(捕获的异常类),函数参数接收 request, exc。
1. 捕获 HTTPException,统一返回格式
例子
from fastapi import FastAPI, HTTPException
from starlette import status
from starlette.requests import Request
from starlette.responses import JSONResponse
app = FastAPI()
# 重写HTTPException返回格式
@app.exception_handler(HTTPException)
async def http_exception_handler(request: Request, exc: HTTPException):
return JSONResponse(
status_code=exc.status_code,
content={
"code": exc.status_code,
"msg": exc.detail,
# "data": None # 抛异常时,一般没有data
}
)
@app.get('/student/{id}')
async def get_student(id: int):
if id == 1:
ret = {
"code":200,
"data":{
'name': '李雷',
'age': 21,
}
}
else:
raise HTTPException(
status_code=status.HTTP_404_NOT_FOUND,
detail='没有此学号的学生'
)
return ret
测试报错
访问 http://127.0.0.1:8000/student/2 ,浏览器显示如下:
{
"code": 404,
"msg": "没有此学号的学生"
}
与返回成功数据的格式是一致的
2. 捕获 Python 原生异常
捕获程序所有未处理异常,例如 ZeroDivisionError、KeyError,避免直接返回500原始堆栈给前端,全局兜底
from fastapi import FastAPI
from starlette.requests import Request
from starlette.responses import JSONResponse
app = FastAPI()
# 重写HTTPException返回格式
@app.exception_handler(Exception)
async def http_exception_handler(request: Request, exc: Exception):
return JSONResponse(
status_code=500,
content={
"code": 500,
"msg": "".join(exc.args),
# "data": None # 抛异常时,一般没有data
}
)
@app.get('/div/{a}/{b}')
async def div(a: int, b: int):
res = a/b
return {
"code":200,
"msg":f"结果:{res}",
}
测试报错
浏览器发请求:http://127.0.0.1:8000/div/2/0 ,浏览器显示如下:
{
"code": 500,
"msg": "division by zero"
}
与返回成功数据的格式是一致的
注意
Exception 处理器一定要放在最后,它会捕获所有未被前面处理器接住的异常。
3. 无法被 exception_handler 捕获的场景
1 BackgroundTasks 后台任务抛出异常
后台任务不在请求路由生命周期,exception_handler 完全捕获不到,会直接打印堆栈到控制台,接口已经返回给客户端。
解决:后台任务内部必须手动 try‑except。
from fastapi import BackgroundTasks
def task():
1/0 # 这里报错,全局异常处理器捕获不到!
@app.get("/bg")
def bg(bt:BackgroundTasks):
bt.add_task(task)
return {"ok":True}
2 普通asyncio.create_task() 创建任务
同理,脱离request上下文,异常处理器无效,必须任务内部捕获。
3 中间件内部抛出异常
- 如果是
@app.middleware("http")中间件中抛出异常:可以被exception_handler捕获。 - 注意:中间件中如果已经
await call_next(request)拿到response之后,再抛异常,不会被处理。