FastAPI 中间件

FastAPI 是基于 Starlette 框架构建的,中间件功能直接来自 Starlette。中间件相当于套在接口外面的一层代码,每一次HTTP请求到达接口路由之前、响应返回给浏览器之前,都会经过中间件。它作用于全部请求。

什么时候使用中间件,用来做什么

适合做全请求生效的通用处理:记录请求日志、统计接口耗时、设置跨域、统一修改请求头或者响应头。

不适合写业务逻辑:接口参数校验、业务鉴权、数据库查询这类和业务强相关的逻辑不要放在中间件。

中间件基础结构

这里使用装饰器写法,最简单直观,完整模板如下:

复制代码
from fastapi import FastAPI, Request
from starlette.responses import JSONResponse

app = FastAPI()

@app.middleware("http")
async def demo_middleware(request: Request, call_next):
    # 这里写【预处理逻辑】:请求还没进入接口路由
    print("请求来了")

    # 放行请求,向后传递,拿到接口处理完成后的响应
    response = await call_next(request)

    # 这里写【后处理逻辑】:接口已经执行完毕,响应即将返回客户端
    response.headers["X‑Demo"] = "hello"

    return response

@app.get("/hello")
async def hello():
    return {"msg":"hello world"}

call_next 是什么

call_next 不是我们自己定义的函数,是框架自动传入的参数。

调用 await call_next(request) 就是把当前请求放行,交给后面的逻辑。后面逻辑可能是下一个中间件,也可能是最终的接口路由函数。 程序执行到这一行的时候,会暂停当前中间件代码,去执行内层逻辑;等内层全部处理完成之后,会得到一个 response 对象,再继续执行 call_next 后面的代码。

await call_next(request) 是代码分水岭

  1. call_next 之前:预处理,处理进来的请求
  2. call_next 之后:后处理,处理要返回出去的响应

放行和拦截请求

放行(正常走完整流程)

执行 response = await call_next(request),请求继续向后走到接口,拿到响应,最后 return response。上面示例就是放行。

拦截(终止请求,不走接口)

满足条件的时候,不去调用 call_next,直接返回一个响应对象。此时后续中间件、接口路由完全不会执行,请求直接结束。

注意:中间件只能返回 Response 类型对象,不能返回字典,不能返回Pydantic实例。要返回json就用JSONResponse。

拦截示例:

复制代码
@app.middleware("http")
async def demo_middleware(request: Request, call_next):
    # 如果访问 /block 直接拦截
    if request.url.path == "/block":
        return JSONResponse(content={"msg":"禁止访问"}, status_code=403)

    # 其余路径正常放行
    response = await call_next(request)
    return response

多个中间件的执行顺序(洋葱模型)

@app.middleware("http") 装饰器注册,代码靠下方后书写的中间件,属于最外层

示例代码:

复制代码
# 写在上方,先定义mid_A
@app.middleware("http")
async def mid_A(request, call_next):
    print("A预处理")
    res = await call_next(request)
    print("A后处理")
    return res

# 写在下方,后定义mid_B
@app.middleware("http")
async def mid_B(request, call_next):
    print("B预处理")
    res = await call_next(request)
    print("B后处理")
    return res

包裹关系:mid_B( mid_A(路由) )

访问接口,打印输出:

复制代码
B预处理
A预处理
# 执行接口路由
A后处理
B后处理

执行流程:

  1. 请求进来,先进入最外层mid_B,执行B预处理
  2. mid_B调用call_next,进入内层mid_A,执行A预处理
  3. mid_A调用call_next,进入接口路由执行业务代码
  4. 接口处理完成得到响应,回到mid_A,执行A后处理
  5. mid_A返回响应,回到mid_B,执行B后处理
  6. mid_B把响应返回给客户端

简单理解: 请求向内钻,从外层中间件走到接口;响应向外冒,从接口回到外层中间件。

洋葱路径图示

复制代码
HTTP请求
    ↓
【mid_B 预处理】
    ↓ call_next()
        【mid_A 预处理】
            ↓ call_next()
                接口路由执行
            ↑ 【mid_A 后处理】
    ↑ 【mid_B 后处理】
↓ 返回客户端

自定义逻辑写在哪里

  1. 请求预处理逻辑(拿到请求,还没跑接口):写在 await call_next(request) 这一行代码的前面。在这里可以做判断,满足条件直接拦截返回。
  2. 响应后处理逻辑(接口已经跑完,准备返回数据):写在 await call_next(request) 之后,return response之前。在这里修改响应头。
  3. 放行:执行 await call_next(request),最后return拿到的response。
  4. 拦截:不调用call_next,直接return Response对象,内层后续中间件和接口路由不会执行;外层中间件的后处理仍会继续执行。

小结

  1. 中间件对所有http请求生效,适合做全局通用处理。
  2. call_next 是框架传入的回调,await call_next(request)用来放行请求。
  3. call_next之前处理请求;call_next之后处理响应。
  4. 放行:调用call_next;拦截:不调用call_next,直接返回响应对象。
  5. @app.middleware("http"):代码靠后的中间件为最外层;多个中间件遵循洋葱模型,请求由外到内,响应由内到外。
  6. 中间件只能返回Response子类对象,不能返回普通字典。
相关推荐
刘新洲1 天前
我以为 AI Agent 只是调模型,直到我亲手补上审批、Outbox 和故障恢复
python·agent·fastapi
(((φ(◎ロ◎;)φ)))牵丝戏安1 天前
fastapi-auth-template — JWT 认证模板
运维·服务器·fastapi
l1258651 天前
# RAG多轮对话检索设计:Query重写如何让“那它呢“变成完整问题
前端·数据库·人工智能·python·算法·fastapi·milvus
紫水木鱼1 天前
物联网通讯协议_MQTT_持续更新
物联网·中间件
赵广陆3 天前
企业实战:web服务集成
前端·pycharm·fastapi
雪碧聊技术3 天前
使用Docker,将fastApi项目部署到linux服务器
服务器·docker·fastapi·docker部署fastapi
meilindehuzi_a3 天前
Express 基础到中间件:系统掌握常用 API 与请求处理链
中间件·express
丑过三八线3 天前
002 - 【FastAPI 入门教程】请求体与数据校验
fastapi
丑过三八线3 天前
001 -【FastAPI 入门教程】 FastAPI Helloword
前端·chrome·fastapi