在 FastAPI 中,exception_handler 和 middleware 的执行顺序是一个常见的问题,理解它们的执行顺序对于正确处理请求和异常非常重要。
源码分析
查看starlette.applications.Starlette类的构造方法,可以看到异常处理器和中间件的类型:
查看源码中添加中间件、异常处理器、路由的逻辑
查看源码中构建应用的中间件栈的方法:
这个函数 build_middleware_stack 是一个用于构建 ASGI(Asynchronous Server Gateway Interface)应用的中间件栈的方法。ASGI 是一种异步 Web 服务器与异步 Web 应用或框架之间的接口标准。中间件是位于应用和服务器之间的组件,可以处理请求和响应,通常用于日志记录、身份验证、错误处理等。
build_middleware_stack函数逐步解释
- 定义函数和初始化变量:
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字典中的每个键值对。 - 如果键是
500或Exception,则将对应的值赋给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_handler和debug参数。 - 然后添加用户自定义的中间件
self.user_middleware。 - 最后添加一个
ExceptionMiddleware实例,用于处理其他异常,传递exception_handlers和debug参数。
4. 构建中间件栈:
app = self.router
for cls, args, kwargs in reversed(middleware):
app = cls(app=app, *args, **kwargs)
- 初始化
app为self.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分析如下图:
从前面的源码分析中可以看到,在添加 middleware 时会将后添加的middleware放在列表的开头,所以在middleware列表中可以看到,后添加的 add_custom_header在先添加 add_process_time_header前面,但是在遍历执行 middleware 是会先反转列表。
输出出的结果如下:
执行顺序分析
- 请求阶段:
- 请求首先经过
add_custom_headerMiddleware。 - 然后经过
add_process_time_headerMiddleware。 - 最后到达路由处理函数
read_root。
- 请求首先经过
- 异常处理:
- 在
read_root中抛出ValueError。 - FastAPI 查找并调用
value_error_exception_handler,生成一个 400 错误响应。
- 在
- 响应阶段:
- 响应首先经过
add_process_time_headerMiddleware。 - 然后经过
add_custom_headerMiddleware。 - 最后返回给客户端。
- 响应首先经过
执行顺序
- Middleware:
- Middleware 是在请求到达路由处理函数之前和响应返回客户端之后执行的。
- Middleware 按照它们在应用中注册的顺序从上到下依次执行。
- 在请求阶段,Middleware 会按逆序执行(即最后一个注册的 Middleware 最先执行)。
- 在响应阶段,Middleware 会按顺序依次执行。
- Exception Handler:
- Exception Handler 是在处理请求过程中抛出异常时执行的。
- 如果在 Middleware 或路由处理函数中抛出了异常,并且该异常没有被其他代码捕获,那么 FastAPI 会查找注册的异常处理器来处理这个异常。
- Exception Handler 的执行优先级高于 Middleware 的响应处理部分。
具体流程
为了更好地理解 FastAPI 中多个 exception_handler 和 middleware 的执行顺序,可以使用以下流程图来表示:
+---------------------+
| 请求进入 |
+---------------------+
|
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
+---------------------+
| 响应发送 |
+---------------------+
解释
-
请求阶段:
- 请求首先到达 FastAPI 应用。
- 请求依次经过所有已注册的 Middleware,按照逆序执行(即最后一个注册的 Middleware 最先执行)。
- 请求最终到达路由处理函数。
-
异常处理:
- 如果在 Middleware 或路由处理函数中抛出了异常,FastAPI 会查找并调用相应的 Exception Handler 来处理异常。
-
响应阶段:
- 路由处理函数生成响应。
- 响应经过 Exception Handler(如果有异常处理的话)。
- 响应再经过所有已注册的 Middleware 的响应处理部分,按照正序执行(即第一个注册的 Middleware 最先执行)。
- 最终,响应被发送给客户端。
通过这个流程图,可以清晰地看到 FastAPI 中请求和响应的处理顺序,以及 Middleware 和 Exception Handler 的执行顺序。
通过以上分析,你可以看到 Middleware 和 Exception Handler 的执行顺序及其在请求和响应处理中的角色。希望这能帮助你更好地理解和使用 FastAPI。