Python Web框架-全局异常处理

提出问题

在 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之后,再抛异常,不会被处理。

原文出处:/show_1GW3ro61nCXj.html