FastAPI中exception_handler和middleware 的执行顺序

682 阅读5分钟

在 FastAPI 中,exception_handlermiddleware 的执行顺序是一个常见的问题,理解它们的执行顺序对于正确处理请求和异常非常重要。

源码分析

查看starlette.applications.Starlette类的构造方法,可以看到异常处理器和中间件的类型:

image.png

查看源码中添加中间件、异常处理器、路由的逻辑

image.png

查看源码中构建应用的中间件栈的方法:

image.png

这个函数 build_middleware_stack 是一个用于构建 ASGI(Asynchronous Server Gateway Interface)应用的中间件栈的方法。ASGI 是一种异步 Web 服务器与异步 Web 应用或框架之间的接口标准。中间件是位于应用和服务器之间的组件,可以处理请求和响应,通常用于日志记录、身份验证、错误处理等。

build_middleware_stack函数逐步解释

  1. 定义函数和初始化变量
def build_middleware_stack(self) -> ASGIApp:
    debug = self.debug
    error_handler = None
    exception_handlers: dict[typing.Any, typing.Callable[[Request, Exception], Response]] = {}
  • self.debug 是一个布尔值,表示是否开启调试模式。
  • error_handler 用于存储处理 500 错误或通用异常的处理器。
  • exception_handlers 是一个字典,用于存储特定异常类型的处理器。

2. 遍历异常处理器

for key, value in self.exception_handlers.items():
    if key in (500, Exception):
        error_handler = value
    else:
        exception_handlers[key] = value
  • 遍历 self.exception_handlers 字典中的每个键值对。
  • 如果键是 500Exception,则将对应的值赋给 error_handler
  • 否则,将键值对添加到 exception_handlers 字典中。

3. 构建中间件列表

middleware = (
    [Middleware(ServerErrorMiddleware, handler=error_handler, debug=debug)]
    + self.user_middleware
    + [Middleware(ExceptionMiddleware, handlers=exception_handlers, debug=debug)]
)
  • 创建一个中间件列表 middleware
  • 首先添加一个 ServerErrorMiddleware 实例,用于处理服务器错误,传递 error_handlerdebug 参数。
  • 然后添加用户自定义的中间件 self.user_middleware
  • 最后添加一个 ExceptionMiddleware 实例,用于处理其他异常,传递 exception_handlersdebug 参数。

4. 构建中间件栈

app = self.router
for cls, args, kwargs in reversed(middleware):
    app = cls(app=app, *args, **kwargs)
  • 初始化 appself.router,这是应用的路由处理程序。
  • 反向遍历 middleware 列表,确保中间件按照正确的顺序被应用。
  • 对于每个中间件类 cls,使用 app 作为参数创建一个新的中间件实例,并将结果赋值回 app

5. 返回最终的应用

return app
  • 返回最终构建好的 ASGI 应用 app

总结

这个函数的主要作用是构建一个中间件栈,其中包含处理服务器错误和特定异常的中间件,以及用户自定义的中间件。通过反向遍历中间件列表,确保中间件按照正确的顺序被应用到应用的路由处理程序上。最终返回一个完整的 ASGI 应用。

举例分析

示例

假设你有以下 FastAPI 应用:

import time

import uvicorn
from fastapi import FastAPI, Request
from fastapi.responses import JSONResponse
from fastapi.middleware import Middleware

app = FastAPI()


# Middleware 1
@app.middleware("http")
async def add_process_time_header(request: Request, call_next):
    print("Middleware 1 add_process_time_header: before")
    start_time = time.time()
    response = await call_next(request)
    process_time = time.time() - start_time
    response.headers["X-Process-Time"] = str(process_time)
    print("Middleware 1 add_process_time_header: after")
    return response


# Middleware 2
@app.middleware("http")
async def add_custom_header(request: Request, call_next):
    print("Middleware 2 add_custom_header: before")
    response = await call_next(request)
    response.headers["X-Custom-Header"] = "CustomValue"
    print("Middleware 2 add_custom_header: after")
    return response


# Exception Handler
@app.exception_handler(ValueError)
async def value_error_exception_handler(request: Request, exc: ValueError):
    print("Exception Handler value_error_exception_handler: before")
    return JSONResponse(
        status_code=400,
        content={"message": str(exc)},
    )


# Route
@app.get("/")
async def read_root():
    raise ValueError("This is a value error")
    return {"Hello": "World"}


if __name__ == '__main__':
    uvicorn.run(app, host="0.0.0.0", port=8000)

通过debug分析如下图:

image.png

从前面的源码分析中可以看到,在添加 middleware 时会将后添加的middleware放在列表的开头,所以在middleware列表中可以看到,后添加的 add_custom_header在先添加 add_process_time_header前面,但是在遍历执行 middleware 是会先反转列表

输出出的结果如下:

image.png

执行顺序分析

  1. 请求阶段:
    • 请求首先经过 add_custom_header Middleware。
    • 然后经过 add_process_time_header Middleware。
    • 最后到达路由处理函数 read_root
  2. 异常处理:
    • read_root 中抛出 ValueError
    • FastAPI 查找并调用 value_error_exception_handler,生成一个 400 错误响应。
  3. 响应阶段:
    • 响应首先经过 add_process_time_header Middleware。
    • 然后经过 add_custom_header Middleware。
    • 最后返回给客户端。

执行顺序

  1. Middleware:
    • Middleware 是在请求到达路由处理函数之前和响应返回客户端之后执行的。
    • Middleware 按照它们在应用中注册的顺序从上到下依次执行。
    • 在请求阶段,Middleware 会按逆序执行(即最后一个注册的 Middleware 最先执行)。
    • 在响应阶段,Middleware 会按顺序依次执行。
  2. Exception Handler:
    • Exception Handler 是在处理请求过程中抛出异常时执行的。
    • 如果在 Middleware 或路由处理函数中抛出了异常,并且该异常没有被其他代码捕获,那么 FastAPI 会查找注册的异常处理器来处理这个异常。
    • Exception Handler 的执行优先级高于 Middleware 的响应处理部分。

具体流程

为了更好地理解 FastAPI 中多个 exception_handlermiddleware 的执行顺序,可以使用以下流程图来表示:

+---------------------+
| 请求进入             |
+---------------------+
          |
          v
+---------------------+
| Middleware N (入)   | <--- 最后注册的 Middleware
+---------------------+
          |
          v
+---------------------+
| Middleware N-1 (入) |
+---------------------+
          |
          v
+---------------------+
| ...                 |
+---------------------+
          |
          v
+---------------------+
| Middleware 1 (入)   | <--- 第一个注册的 Middleware
+---------------------+
          |
          v
+---------------------+
| 路由处理函数        |
+---------------------+
          |
          v
+---------------------+
| 异常处理            | <--- 如果有异常发生
| Exception Handler   |
+---------------------+
          |
          v
+---------------------+
| 响应生成            |
+---------------------+
          |
          v
+---------------------+
| Middleware 1 (出)   | <--- 第一个注册的 Middleware
+---------------------+
          |
          v
+---------------------+
| Middleware 2 (出)   |
+---------------------+
          |
          v
+---------------------+
| ...                 |
+---------------------+
          |
          v
+---------------------+
| Middleware N (出)   | <--- 最后注册的 Middleware
+---------------------+
          |
          v
+---------------------+
| 响应发送            |
+---------------------+

解释

  1. 请求阶段

    • 请求首先到达 FastAPI 应用。
    • 请求依次经过所有已注册的 Middleware,按照逆序执行(即最后一个注册的 Middleware 最先执行)。
    • 请求最终到达路由处理函数。
  2. 异常处理

    • 如果在 Middleware 或路由处理函数中抛出了异常,FastAPI 会查找并调用相应的 Exception Handler 来处理异常。
  3. 响应阶段

    • 路由处理函数生成响应。
    • 响应经过 Exception Handler(如果有异常处理的话)。
    • 响应再经过所有已注册的 Middleware 的响应处理部分,按照正序执行(即第一个注册的 Middleware 最先执行)。
    • 最终,响应被发送给客户端。

通过这个流程图,可以清晰地看到 FastAPI 中请求和响应的处理顺序,以及 Middleware 和 Exception Handler 的执行顺序。

通过以上分析,你可以看到 Middleware 和 Exception Handler 的执行顺序及其在请求和响应处理中的角色。希望这能帮助你更好地理解和使用 FastAPI。