首页 / FastAPI 入门教程 / 中间件 Middleware 与 CORS

FastAPI 入门教程

中间件 Middleware 与 CORS

本教程共 50 篇 · 第 41 篇 · 更新于 2026-08-12 · 约 7 分钟阅读

FastAPIFastAPI 入门教程中间件MiddlewareCORS跨域

本节目标:学会写中间件去统一拦截请求和响应,并搞懂前后端分离时浏览器报的跨域错误该怎么用 CORS 解决。

有些逻辑希望”对所有请求都生效”,比如统计每个接口花了多少时间、给所有响应加个统一头。这种横切逻辑不该写进每个接口里。中间件就是干这个的。另外,只要你的前端和后端不在同一个地址上,浏览器就会用 CORS 规则拦你。这两件事常常一起出现,所以放一章讲。

41-1 什么是中间件

中间件是一个函数,它对每一个进来的请求、每一个出去的响应都起作用。它干的事可以分成几步:

  • 收到请求,在交给具体接口之前,可以先看一眼、先改一改。
  • 把请求交给后面的路径操作函数处理。
  • 拿到响应,在返回给客户端之前,再加工一下。
  • 把响应返回。

想象成小区的门卫:每个访客进门他都要登记,出门时他再检查一遍。所有请求都得过他这关,所以适合做统一的、全局的事。

41-2 写一个中间件

在 FastAPI 里用 @app.middleware("http") 装饰一个函数,它就成中间件了。这个函数固定收两个参数:一个是 request,另一个是 call_nextcall_next 负责把请求交给后面的接口,并返回响应。

import time
from fastapi import FastAPI, Request

app = FastAPI()


@app.middleware("http")
async def add_process_time_header(request: Request, call_next):
    start_time = time.perf_counter()
    response = await call_next(request)
    process_time = time.perf_counter() - start_time
    response.headers["X-Process-Time"] = str(process_time)
    return response

这段代码的流程是:记下开始时间,用 await call_next(request) 把请求交给接口拿到响应,算出耗时,然后往响应头里塞一个 X-Process-Time。浏览器或调用方就能在响应头里看到这次处理花了几秒。

Tip

这里用 time.perf_counter() 而不是 time.time(),因为它精度更高,更适合量这种短时间。注意 call_next 前面要加 await,因为它本身是异步的。

41-3 在请求前和响应后都能动手

中间件最灵活的地方:你在 call_next 之前写的代码,是在接口处理请求之前运行的;在 call_next 之后写的代码,是在拿到响应、还没返回之前运行的。

比如你想在请求进来时打印日志,再在响应里加头:

@app.middleware("http")
async def log_requests(request: Request, call_next):
    print(f"收到请求:{request.method} {request.url.path}")
    response = await call_next(request)
    response.headers["X-Powered-By"] = "FastAPI"
    return response

请求前进门打印一行,响应后加个头,两不耽误。

Note

自定义响应头如果想让浏览器里的 JavaScript 读到,得在 CORS 的 expose_headers 里登记(见下文)。否则浏览器出于安全会把它藏起来。

41-4 多个中间件的执行顺序

你可以叠好几个中间件。它们像套娃一样包着应用:后加的是最外层,先加的是最内层。请求时从最外层进,响应时从最内层出。

# MiddlewareA、MiddlewareB 仅为示意,实际传入你要用的中间件类本身
app.add_middleware(MiddlewareA)
app.add_middleware(MiddlewareB)

执行顺序是:请求走 MiddlewareB → MiddlewareA → 接口;响应走 接口 → MiddlewareA → MiddlewareB。记住”后进先出”这个口诀,排错时很有用。

41-5 跨域 CORS 到底在拦什么

现在说 CORS。当前端(浏览器里跑的 JavaScript)去请求一个”源”不同的后端时,浏览器会按 CORS 规则把关。所谓”源”,是协议 + 域名 + 端口三件套。只要有一项不同,就是不同源。

举例,下面这些都是不同的源:

  • http://localhost
  • https://localhost
  • http://localhost:8080

哪怕都在 localhost,协议或端口不同,浏览器就认为是跨域。假设你的前端在 http://localhost:8080,后端在 http://localhost(默认 80 端口),前端发请求就会被拦。

浏览器会先发一个 OPTIONS 预检请求探口风。后端如果返回”允许这个源”的响应头,浏览器才放行真正的请求。所以后端必须有一份”允许的源”清单。

41-6 用 CORSMiddleware 放开跨域

FastAPI 自带 CORSMiddleware,配置起来很直接:导入它,列一份允许源清单,再用 app.add_middleware 挂上。

from fastapi import FastAPI
from fastapi.middleware.cors import CORSMiddleware

app = FastAPI()

origins = [
    "http://localhost.tiangolo.com",
    "https://localhost.tiangolo.com",
    "http://localhost",
    "http://localhost:8080",
]

app.add_middleware(
    CORSMiddleware,
    allow_origins=origins,
    allow_credentials=True,
    allow_methods=["*"],
    allow_headers=["*"],
)

这样,清单里的源就能正常调你的接口了。allow_methods=["*"] 表示放行所有 HTTP 方法,allow_headers=["*"] 表示放行所有请求头。

41-7 常用配置项说明

CORSMiddleware 支持这些重要参数:

  • allow_origins:允许跨域的源列表,如 ["https://example.org"]。写 ["*"] 表示放行全部。
  • allow_origin_regex:用正则匹配源,比如 'https://.*\.example\.org'
  • allow_methods:允许的 HTTP 方法,默认只放行 GET。写 ["*"] 放行全部。
  • allow_headers:允许的请求头,默认空。写 ["*"] 放行全部。
  • allow_credentials:是否允许带凭证(Cookie、Authorization 头)。默认 False
  • expose_headers:允许浏览器前端读到的响应头,默认空。
  • max_age:浏览器缓存 CORS 结果的秒数,默认 600。
Warning

一旦 allow_credentials=Trueallow_origins 就不能用 ["*"],必须明确列出具体源,这是浏览器的硬规定。注意限制只针对 origins:allow_methods=["*"]allow_headers=["*"] 仍可用,Starlette 会按实际请求回显对应的方法和头。

41-8 前后端分离的真实场景

前后端分离是最常见的踩坑现场。比如前端跑在 http://localhost:5173(Vite 开发服务器),后端跑在 http://localhost:8000。两者端口不同,必然跨域。后端清单里加上前端那个源即可:

origins = [
    "http://localhost:5173",
    "https://my-frontend.com",
]

app.add_middleware(
    CORSMiddleware,
    allow_origins=origins,
    allow_credentials=True,
    allow_methods=["*"],
    allow_headers=["*"],
)

开发阶段图省事,有人会写 allow_origins=["*"]。但要注意:带了凭证就不能用通配符。生产环境请务必写清具体源,别偷懒,否则会有安全风险。

CORS 中间件只处理两类请求:带 Origin 头的简单请求,以及带 OriginAccess-Control-Request-MethodOPTIONS 预检请求。预检通过了,真正的请求才放行。

41-9 小结

这一章我们打通了全局拦截和跨域:

  • 中间件用 @app.middleware("http"),靠 call_next 串起请求和响应。
  • 计时、加头、记日志这类全局逻辑都适合放中间件里。
  • 多个中间件”后进先出”,请求从外进、响应从外回。
  • CORS 是浏览器对跨域请求的安全限制,用 CORSMiddleware 配置允许的源、方法、头来解决。
  • 带凭证时不能用 * 通配符,必须列具体源。

下一章我们讲自定义响应,比如直接返回 HTML、文件,或者流式地一点点吐数据。