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子类对象,不能返回普通字典。
相关推荐
FlyWIHTSKY2 小时前
Agent阶段一:基础夯实
python·ai·fastapi
Json____14 小时前
用 FastAPI + Vue3 打造一套完整的在线考试系统:从组卷到阅卷的业务闭环实践
后端·node.js·fastapi·it学习·wwwoop.com
dyxal19 小时前
A2A 协议 v1.0 完全指南:方法名、Agent Card、三种绑定与 FastAPI 实战
开发语言·qt·fastapi
vHelios20 小时前
Python FastAPI 422 Unprocessable Entity报错排查实录 error:Invalid \\escape
fastapi
梦影_20 小时前
MySQL进阶教程(一)—— 储存过程与触发器(上)
开发语言·数据库·后端·sql·mysql·中间件
溪语流沙1 天前
【Web全栈进阶】Nginx反向代理 + HTTPS:正式上线
前端·vue.js·python·nginx·https·fastapi
肖永威1 天前
python版微电网仿真工具的前端托管选型:从http.server到FastAPI StaticFiles
前端·python·http·fastapi
七点半.2 天前
FastAPI · SSE · async/await
fastapi·sse
eleven_4u2 天前
RocketMQ相关问题
java·运维·中间件
张张123y3 天前
后端第十一课:RAG 落地实战(从 0 到 1 接上私有知识库,并设计一个好用的 RAG)
开发语言·人工智能·python·机器学习·fastapi·rag