FastAPI 中间件(Middleware)详解
一、什么是中间件
中间件是介于客户端请求和接口路由函数之间的一层通用逻辑,请求到达接口之前、响应返回客户端之前都会经过它。
简单流程:客户端请求→中间件(前置逻辑)→ 接口函数执行业务 →中间件(后置逻辑)→响应返回客户端
特点:
- 全局生效:默认对项目所有路由/接口统一处理
- 统一抽离:把多个接口重复的逻辑抽出来,不用每个接口重复写代码
- 链式执行:可注册多个中间件,按注册顺序依次执行
FastAPI 底层基于 Starlette,中间件写法完全兼容 Starlette。
二、基础语法 & 最简示例
1. 标准异步中间件写法(推荐,FastAPI 主流)
fromfastapiimportFastAPI,Requestimporttime app=FastAPI()# 注册中间件@app.middleware("http")asyncdefcalc_cost_time(request:Request,call_next):# ========== 1. 请求进入接口【之前】执行(前置逻辑) ==========start_time=time.time()print("请求进来了:",request.url.path)# 执行后续逻辑:调用真正的接口函数response=awaitcall_next(request)# ========== 2. 接口执行完毕,响应返回【之前】执行(后置逻辑) ==========cost=time.time()-start_timeprint(f"接口耗时:{cost:.4f}s")# 可以追加响应头response.headers["X-Process-Time"]=str(cost)returnresponse# 测试接口@app.get("/hello")asyncdefhello():return{"msg":"Hello FastAPI"}关键说明
@app.middleware("http"):声明这是 HTTP 全局中间件call_next(request):放行请求,执行后续中间件 + 接口逻辑,必须调用- 顺序:前置代码 →
call_next→ 后置代码 - 能拿到
Request对象(读请求头、请求参数、客户端IP)、Response对象(改响应头)
三、核心应用场景(工作最常用)
场景 1:统一统计接口耗时、性能监控
上面示例就是典型用法,全局统计每个接口执行耗时,方便排查慢接口。
额外追加X-Process-Time响应头,前端/网关也能看到耗时。
场景 2:全局日志(统一打印请求/响应日志)
不用在每个接口写日志,中间件统一记录:客户端IP、请求路径、请求方式、状态码。
@app.middleware("http")asyncdeflog_request(request:Request,call_next):client_ip=request.client.host method=request.method path=request.url.pathprint(f"【请求】IP:{client_ip}方法:{method}路径:{path}")response=awaitcall_next(request)print(f"【响应】状态码:{response.status_code}")returnresponse场景 3:全局跨域(CORS)
FastAPI 内置CORSMiddleware,专门解决前端跨域请求,项目必备。
fromfastapi.middleware.corsimportCORSMiddleware app=FastAPI()# 配置跨域app.add_middleware(CORSMiddleware,allow_origins=["*"],# 允许所有域名,生产建议指定具体域名allow_credentials=True,allow_methods=["*"],allow_headers=["*"],)场景 4:全局鉴权 / 登录拦截
统一校验 Token、身份信息,不用每个接口单独写鉴权代码。
注意:中间件是全局拦截,可搭配白名单(登录接口、文档接口放行)。
fromfastapiimportHTTPException@app.middleware("http")asyncdefauth_middleware(request:Request,call_next):# 白名单:不需要登录的接口white_list=["/login","/docs","/redoc","/openapi.json"]ifrequest.url.pathnotinwhite_list:# 从请求头拿 Tokentoken=request.headers.get("X-Token")ifnottokenortoken!="123456":raiseHTTPException(status_code=401,detail="未授权,请登录")# 校验通过,放行response=awaitcall_next(request)returnresponse场景 5:全局限流、防刷
结合 IP + 计数器,在中间件实现全局限流,拒绝高频恶意请求。
场景 6:统一添加/修改响应头
给所有接口统一追加自定义头、安全响应头(如X-Content-Type-Options、Retry-After等)。
场景 7:链路追踪(全链路 Trace ID)
全局生成唯一Request-ID,塞入请求头、响应头、日志,微服务排查问题必备。
importuuid@app.middleware("http")asyncdeftrace_id_middleware(request:Request,call_next):trace_id=str(uuid.uuid4())# 传给后续接口/日志request.state.trace_id=trace_id response=awaitcall_next(request)# 响应头返回追踪IDresponse.headers["X-Request-Id"]=trace_idreturnresponse场景 8:请求/响应统一加密、解码
所有接口统一做参数解密、响应数据加密,业务接口只关心逻辑。
四、多中间件执行顺序
注册多个中间件时,遵循:
先注册 → 先执行前置逻辑;后注册 → 先执行后置逻辑
示例:
fromfastapiimportFastAPI,Request app=FastAPI()# ========== 第一个注册的中间件 M1 ==========@app.middleware("http")asyncdefm1(request:Request,call_next):print("===== M1 前置 执行 =====")response=awaitcall_next(request)print("===== M1 后置 执行 =====")returnresponse# ========== 第二个注册的中间件 M2 ==========@app.middleware("http")asyncdefm2(request:Request,call_next):print("===== M2 前置 执行 =====")response=awaitcall_next(request)print("===== M2 后置 执行 =====")returnresponse# 测试接口(路由函数)@app.get("/test")asyncdeftest():print("----- 路由函数 业务逻辑执行 -----")return{"msg":"ok"}运行后控制台输出(顺序固定):
===== M1 前置 执行 ===== ===== M2 前置 执行 ===== ----- 路由函数 业务逻辑执行 ----- ===== M2 后置 执行 ===== ===== M1 后置 执行 =====逐行解读
先执行 M1 前置 → 再执行 M2 前置(正序:按注册先后)
走到 路由函数(接口核心逻辑)
路由执行完毕,开始走后置逻辑:
先执行 M2 后置 → 再执行 M1 后置
✅ 这里就是:按和注册相反的顺序执行
五、中间件 vs 依赖项(Depends)区别(高频面试/实战区分)
很多人混淆两者,一张表分清:
| 特性 | 中间件 Middleware | 依赖项 Depends |
|---|---|---|
| 作用范围 | 全局,所有接口默认生效 | 局部,哪个接口加哪个生效 |
| 执行位置 | 路由匹配之前执行 | 路由匹配之后、函数调用前执行 |
| 适合场景 | 跨域、全局日志、全局限流、全链路追踪、全局鉴权 | 单个/分组接口鉴权、参数校验、局部复用逻辑 |
| 操作对象 | 可直接操作原始Request、Response | 侧重函数参数、业务数据 |
选型建议:
- 全项目统一规则 → 用中间件
- 部分接口复用逻辑 → 用Depends 依赖
六、常见注意事项
- 必须调用
await call_next(request)
不调用 = 请求卡死,接口永远不会执行。 - 中间件尽量轻量化
全局执行,逻辑太复杂会拖慢所有接口性能。 - 异步项目用异步中间件,不要混用同步阻塞代码。
- 全局鉴权一定要配置白名单
登录接口、文档/docs、静态资源必须放行,否则无法访问。 - 中间件内可以抛出
HTTPException,正常返回错误响应。
七、一句话总结
FastAPI 中间件是全局统一拦截层,在请求进接口前、响应返回前执行通用逻辑;
主要用来做:跨域、全局日志、接口耗时统计、全局限流、统一鉴权、链路追踪、统一响应头。
全局通用逻辑抽中间件,局部接口复用抽 Depends。
前端跨域请求
1. 前端跨域请求是什么?
结合浏览器同源策略、场景、现象、原因一步步讲清楚,再配示例和 FastAPI 对应处理。
一、先搞懂:同源策略(浏览器安全规则)
同源策略是浏览器默认的安全机制,用来防止恶意网站窃取数据、发起非法请求。
什么是「同源」?
两个 URL 必须同时满足协议、域名、端口三者完全一致,才算同源;
任意一个不一样,就是跨域。
判断三要素:
- 协议(
http/https) - 域名(主域名 + 子域名)
- 端口号(
80/8000/3000等)
注意:浏览器中,
http://a.com:80和http://a.com视为同源(80是 http 默认端口,可省略)。
二、举例:同源 vs 跨域
假设当前页面地址:http://localhost:3000/index.html(前端页面)
| 请求目标地址 | 是否跨域 | 原因 |
|---|---|---|
http://localhost:3000/api/xxx | 同源 | 协议、域名、端口全相同 |
http://localhost:8000/api/xxx | 跨域 | 端口不同(3000 ↔ 8000) |
https://localhost:3000/api/xxx | 跨域 | 协议不同(http ↔ https) |
http://127.0.0.1:3000/api/xxx | 跨域 | 域名不同(localhost ↔ 127.0.0.1,浏览器判定为不同域名) |
http://api.xxx.com:3000/data | 跨域 | 域名不同 |
三、什么是「前端跨域请求」?
前端页面(JS/Fetch/Axios)在浏览器环境中,发起了一个非同源的后端接口请求,这个行为就叫跨域请求。
典型开发场景(你日常一定会遇到)
- 前端项目:运行在
http://localhost:3000(Vue/React/静态页面) - FastAPI 后端:运行在
http://localhost:8000 - 前端 JS 直接调用后端接口 →端口不一致,触发跨域
四、跨域会出现什么现象?
- 请求其实已经发到后端:接口正常接收、执行业务逻辑;
- 浏览器拦截返回结果:不让前端 JS 拿到响应数据;
- 控制台报经典错误:
翻译:跨域策略拦截,响应头缺少允许跨域的字段。Access to fetch at 'http://localhost:8000/xxx' from origin 'http://localhost:3000' has been blocked by CORS policy: No 'Access-Control-Allow-Origin' header is present on the requested resource.
补充:
- 后端收得到请求,但结果回不到前端;
- 跨域限制只存在于浏览器,Postman、curl、APP、小程序 不受同源策略限制。
五、CORS 是什么?
上面报错里的CORS=跨域资源共享(Cross-Origin Resource Sharing)
它是一套HTTP 响应头规范,作用:
后端主动在响应头声明「允许哪些前端域名跨域访问我」,以此说服浏览器放行。
简单理解:
浏览器不让跨域 → 后端配置 CORS 响应头 → 浏览器看到允许规则 → 正常互通。
六、FastAPI 如何解决跨域(实操代码)
FastAPI 内置CORSMiddleware中间件,一行配置搞定,也是你之前学过的中间件用法。
1. 完整代码
fromfastapiimportFastAPIfromfastapi.middleware.corsimportCORSMiddleware app=FastAPI()# 配置跨域中间件app.add_middleware(CORSMiddleware,# 1. 允许的前端源(域名/地址)allow_origins=["http://localhost:3000",# 前端地址,精准配置(生产推荐)"https://xxx.com"],allow_credentials=True,# 允许携带 Cookie、认证头(如 X-Token)allow_methods=["*"],# 允许所有请求方法:GET/POST/PUT/DELETEallow_headers=["*"],# 允许所有请求头)# 测试接口@app.get("/hello")asyncdefhello():return{"msg":"跨域请求成功"}2. 参数说明
allow_origins- 写具体地址:安全,生产环境首选;
- 写
["*"]:允许所有域名跨域,开发调试用,生产不推荐。
allow_credentials=True
前端需要传Cookie、Token等身份凭证时,必须开启。allow_methods/allow_headers
限制允许的请求方法、请求头,["*"]代表全部放行。
七、两种跨域请求:简单请求 & 预检请求(了解即可)
1. 简单请求
满足条件:GET/POST/HEAD + 无特殊请求头、仅普通表单/JSON。
- 流程:直接发起真实请求,后端返回 CORS 头,浏览器判断放行。
2. 预检请求(OPTIONS)
当请求带自定义头(比如X-Token)、或者非简单请求时:
- 浏览器先自动发一条
OPTIONS预检请求,询问后端:「你允许我跨域吗?」 - 后端返回跨域规则;
- 浏览器确认允许后,再发起真实业务请求。
你之前用到
X-Token自定义请求头时,前端跨域一定会触发 OPTIONS 预检。
八、总结(精简记忆)
- 同源策略:浏览器安全规则,协议、域名、端口任一不同 = 跨域。
- 跨域请求:前端页面(浏览器中)调用了非同源的后端接口。
- 现象:后端收到请求,前端拿不到数据,浏览器报 CORS 错误。
- 解决方案:后端配置CORS 跨域中间件,通过响应头声明访问白名单。
- 跨域只限制浏览器,接口测试工具、客户端 APP 不受影响。