【Fastapi学习笔记(7)】—— Fastapi 中间件、前端跨域请求

FastAPI 中间件(Middleware)详解

一、什么是中间件

中间件 是介于 客户端请求接口路由函数 之间的一层通用逻辑,请求到达接口之前 、响应返回客户端之前都会经过它。

简单流程:

客户端请求中间件(前置逻辑) → 接口函数执行业务 → 中间件(后置逻辑)响应返回客户端

特点:

  1. 全局生效 :默认对项目所有路由/接口统一处理
  2. 统一抽离:把多个接口重复的逻辑抽出来,不用每个接口重复写代码
  3. 链式执行:可注册多个中间件,按注册顺序依次执行

FastAPI 底层基于 Starlette,中间件写法完全兼容 Starlette。


二、基础语法 & 最简示例

1. 标准异步中间件写法(推荐,FastAPI 主流)

python 复制代码
from fastapi import FastAPI, Request
import time

app = FastAPI()

# 注册中间件
@app.middleware("http")
async def calc_cost_time(request: Request, call_next):
    # ========== 1. 请求进入接口【之前】执行(前置逻辑) ==========
    start_time = time.time()
    print("请求进来了:", request.url.path)

    # 执行后续逻辑:调用真正的接口函数
    response = await call_next(request)

    # ========== 2. 接口执行完毕,响应返回【之前】执行(后置逻辑) ==========
    cost = time.time() - start_time
    print(f"接口耗时: {cost:.4f} s")

    # 可以追加响应头
    response.headers["X-Process-Time"] = str(cost)

    return response

# 测试接口
@app.get("/hello")
async def hello():
    return {"msg": "Hello FastAPI"}

关键说明

  • @app.middleware("http"):声明这是 HTTP 全局中间件
  • call_next(request)放行请求,执行后续中间件 + 接口逻辑,必须调用
  • 顺序:前置代码 → call_next → 后置代码
  • 能拿到 Request 对象(读请求头、请求参数、客户端IP)、Response 对象(改响应头)

三、核心应用场景(工作最常用)

场景 1:统一统计接口耗时、性能监控

上面示例就是典型用法,全局统计每个接口执行耗时,方便排查慢接口。

额外追加 X-Process-Time 响应头,前端/网关也能看到耗时。

场景 2:全局日志(统一打印请求/响应日志)

不用在每个接口写日志,中间件统一记录:客户端IP、请求路径、请求方式、状态码。

python 复制代码
@app.middleware("http")
async def log_request(request: Request, call_next):
    client_ip = request.client.host
    method = request.method
    path = request.url.path
    print(f"【请求】IP:{client_ip} 方法:{method} 路径:{path}")

    response = await call_next(request)

    print(f"【响应】状态码:{response.status_code}")
    return response

场景 3:全局跨域(CORS)

FastAPI 内置 CORSMiddleware,专门解决前端跨域请求,项目必备

python 复制代码
from fastapi.middleware.cors import CORSMiddleware

app = FastAPI()

# 配置跨域
app.add_middleware(
    CORSMiddleware,
    allow_origins=["*"],  # 允许所有域名,生产建议指定具体域名
    allow_credentials=True,
    allow_methods=["*"],
    allow_headers=["*"],
)

场景 4:全局鉴权 / 登录拦截

统一校验 Token、身份信息,不用每个接口单独写鉴权代码

注意:中间件是全局拦截,可搭配白名单(登录接口、文档接口放行)。

python 复制代码
from fastapi import HTTPException

@app.middleware("http")
async def auth_middleware(request: Request, call_next):
    # 白名单:不需要登录的接口
    white_list = ["/login", "/docs", "/redoc", "/openapi.json"]
    if request.url.path not in white_list:
        # 从请求头拿 Token
        token = request.headers.get("X-Token")
        if not token or token != "123456":
            raise HTTPException(status_code=401, detail="未授权,请登录")
    
    # 校验通过,放行
    response = await call_next(request)
    return response

场景 5:全局限流、防刷

结合 IP + 计数器,在中间件实现全局限流,拒绝高频恶意请求。

场景 6:统一添加/修改响应头

给所有接口统一追加自定义头、安全响应头(如 X-Content-Type-OptionsRetry-After 等)。

场景 7:链路追踪(全链路 Trace ID)

全局生成唯一 Request-ID,塞入请求头、响应头、日志,微服务排查问题必备。

python 复制代码
import uuid

@app.middleware("http")
async def trace_id_middleware(request: Request, call_next):
    trace_id = str(uuid.uuid4())
    # 传给后续接口/日志
    request.state.trace_id = trace_id

    response = await call_next(request)
    # 响应头返回追踪ID
    response.headers["X-Request-Id"] = trace_id
    return response

场景 8:请求/响应统一加密、解码

所有接口统一做参数解密、响应数据加密,业务接口只关心逻辑。


四、多中间件执行顺序

注册多个中间件时,遵循:

先注册 → 先执行前置逻辑;后注册 → 先执行后置逻辑

示例:

python 复制代码
from fastapi import FastAPI, Request

app = FastAPI()

# ========== 第一个注册的中间件 M1 ==========
@app.middleware("http")
async def m1(request: Request, call_next):
    print("===== M1 前置 执行 =====")
    response = await call_next(request)
    print("===== M1 后置 执行 =====")
    return response

# ========== 第二个注册的中间件 M2 ==========
@app.middleware("http")
async def m2(request: Request, call_next):
    print("===== M2 前置 执行 =====")
    response = await call_next(request)
    print("===== M2 后置 执行 =====")
    return response

# 测试接口(路由函数)
@app.get("/test")
async def test():
    print("----- 路由函数 业务逻辑执行 -----")
    return {"msg": "ok"}

运行后控制台输出(顺序固定):

复制代码
===== M1 前置 执行 =====
===== M2 前置 执行 =====
----- 路由函数 业务逻辑执行 -----
===== M2 后置 执行 =====
===== M1 后置 执行 =====

逐行解读

先执行 M1 前置 → 再执行 M2 前置(正序:按注册先后)

走到 路由函数(接口核心逻辑)

路由执行完毕,开始走后置逻辑:

先执行 M2 后置 → 再执行 M1 后置

✅ 这里就是:按和注册相反的顺序执行


五、中间件 vs 依赖项(Depends)区别(高频面试/实战区分)

很多人混淆两者,一张表分清:

特性 中间件 Middleware 依赖项 Depends
作用范围 全局,所有接口默认生效 局部,哪个接口加哪个生效
执行位置 路由匹配之前执行 路由匹配之后、函数调用前执行
适合场景 跨域、全局日志、全局限流、全链路追踪、全局鉴权 单个/分组接口鉴权、参数校验、局部复用逻辑
操作对象 可直接操作原始 RequestResponse 侧重函数参数、业务数据

选型建议

  • 全项目统一规则 → 用中间件
  • 部分接口复用逻辑 → 用 Depends 依赖

六、常见注意事项

  1. 必须调用 await call_next(request)
    不调用 = 请求卡死,接口永远不会执行。
  2. 中间件尽量轻量化
    全局执行,逻辑太复杂会拖慢所有接口性能。
  3. 异步项目用异步中间件,不要混用同步阻塞代码。
  4. 全局鉴权一定要配置白名单
    登录接口、文档 /docs、静态资源必须放行,否则无法访问。
  5. 中间件内可以抛出 HTTPException,正常返回错误响应。

七、一句话总结

FastAPI 中间件是全局统一拦截层 ,在请求进接口前、响应返回前执行通用逻辑;

主要用来做:跨域、全局日志、接口耗时统计、全局限流、统一鉴权、链路追踪、统一响应头

全局通用逻辑抽中间件,局部接口复用抽 Depends。



前端跨域请求

1. 前端跨域请求是什么?

结合浏览器同源策略、场景、现象、原因一步步讲清楚,再配示例和 FastAPI 对应处理。


一、先搞懂:同源策略(浏览器安全规则)

同源策略是浏览器默认的安全机制,用来防止恶意网站窃取数据、发起非法请求。

什么是「同源」?

两个 URL 必须同时满足 协议、域名、端口 三者完全一致,才算同源

任意一个不一样,就是跨域

判断三要素:

  1. 协议(http / https
  2. 域名(主域名 + 子域名)
  3. 端口号(80/8000/3000 等)

注意:浏览器中,http://a.com:80http://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 直接调用后端接口 → 端口不一致,触发跨域

四、跨域会出现什么现象?

  1. 请求其实已经发到后端:接口正常接收、执行业务逻辑;

  2. 浏览器拦截返回结果:不让前端 JS 拿到响应数据;

  3. 控制台报经典错误:

    复制代码
    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. 完整代码
python 复制代码
from fastapi import FastAPI
from fastapi.middleware.cors import CORSMiddleware

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/DELETE
    allow_headers=["*"],          # 允许所有请求头
)

# 测试接口
@app.get("/hello")
async def hello():
    return {"msg": "跨域请求成功"}
2. 参数说明
  1. allow_origins
    • 写具体地址:安全,生产环境首选;
    • ["*"]:允许所有域名 跨域,开发调试用,生产不推荐
  2. allow_credentials=True
    前端需要传 CookieToken 等身份凭证时,必须开启。
  3. allow_methods / allow_headers
    限制允许的请求方法、请求头,["*"] 代表全部放行。

七、两种跨域请求:简单请求 & 预检请求(了解即可)

1. 简单请求

满足条件:GET/POST/HEAD + 无特殊请求头、仅普通表单/JSON。

  • 流程:直接发起真实请求,后端返回 CORS 头,浏览器判断放行。
2. 预检请求(OPTIONS)

当请求带自定义头(比如 X-Token)、或者非简单请求时:

  1. 浏览器先自动发一条 OPTIONS 预检请求,询问后端:「你允许我跨域吗?」
  2. 后端返回跨域规则;
  3. 浏览器确认允许后,再发起真实业务请求

你之前用到 X-Token 自定义请求头时,前端跨域一定会触发 OPTIONS 预检。


八、总结(精简记忆)

  1. 同源策略:浏览器安全规则,协议、域名、端口任一不同 = 跨域。
  2. 跨域请求:前端页面(浏览器中)调用了非同源的后端接口。
  3. 现象:后端收到请求,前端拿不到数据,浏览器报 CORS 错误。
  4. 解决方案 :后端配置 CORS 跨域中间件,通过响应头声明访问白名单。
  5. 跨域只限制浏览器,接口测试工具、客户端 APP 不受影响。
相关推荐
问心无愧05132 小时前
ctf show web入门109
android·前端·笔记
踏着七彩祥云的小丑2 小时前
嵌入式测试学习第 31 天:兼容性测试:版本兼容、外设兼容、硬件版本兼容
单片机·嵌入式硬件·学习
hans汉斯2 小时前
【人工智能与机器人研究】基于分层控制的多智能体编队协同控制
网络·人工智能·学习·yolo·机器人
Kobebryant-Manba2 小时前
学习模型构造
python·深度学习·学习
一锅炖出任易仙2 小时前
创梦汤锅学习日记day29
学习·ai·ue5·游戏引擎
MartinYeung52 小时前
[论文学习]无资料选择性遗忘:透过模型反演实现 LLM 的资料免隐私保护(DFSU)
学习
阿寻寻2 小时前
【人工智能学习-20260608】什么是生成式AI?
人工智能·学习
sensen_kiss2 小时前
CPT304 SoftwareEngineeringII 软件工程 2 Pt.5 软件复用(Software Reuse)
学习·软件工程
xian_wwq2 小时前
【学习笔记】倾斜摄影、高斯泼溅(3DGS)、点云与数字孪生“族谱”全盘点
笔记·学习·3d