FastAPI部署中间件实战:从CORS到自定义,让你的API更健壮

本文摘要:你是否为FastAPI应用的跨域请求(CORS)头疼过?或者想给所有请求统一加点"料"却不知从何下手?本文通过实战演示FastAPI中间件的使用,涵盖CORS处理、自定义中间件编写、多个中间件执行顺序等核心内容,帮你构建更安全、高效的Web应用。


你的FastAPI应用在本地跑得风生水起,一部署上线,前端同学就火急火燎地找你:"老大,接口跨域了,报错!" 🚨

或者,你突然接到需求:"所有请求都要记录日志,敏感接口还要额外校验一个自定义Header。" 难不成要给每个路由都加一遍代码?

别慌,这就是今天要聊的"中间件"(Middleware)要解决的经典问题。它就像你API的"前厅经理",所有请求和响应都得先经过它手,验身份、记日志、处理跨域......妥妥的。

🎯 一、中间件是啥?咱用"餐厅"打个比方

想象一下,你的FastAPI应用是个高级餐厅。
👉 顾客(客户端请求)来到门口。

  • 迎宾(CORS中间件):先看你是不是从允许的街区(域名)来的,不是就直接劝退(返回CORS错误)。

  • 领位员(日志中间件):记下顾客几点来的、几个人。

  • 安检(认证中间件):检查你有没有预约码(Token)。

👉 之后,顾客才能见到**真正的厨师(你的路径操作函数)**点菜吃饭(处理业务逻辑)。

👉 吃完离开时,还得经过传菜员(响应处理中间件),可能给打包盒贴个标签(添加响应头)。

这一系列站在"核心业务"前后的服务人员,就是中间件。它的核心价值在于:全局处理、业务无侵入。

官方文档讲得比较散,咱们今天就把这块硬骨头啃透,直接上代码。

🔧 二、核心:怎么用?从最常用的CORS开始

好,咱们先来解决开头的"跨域"问题。这是99%的Web应用都会遇到的。

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

app = FastAPI()

# 1. 配置允许的源、方法、请求头
origins = [
    "http://localhost:3000",  # 你的前端开发地址
    "https://your-production-site.com",
]

# 2. 添加中间件
app.add_middleware(
    CORSMiddleware,
    allow_origins=origins,  # 允许的源列表,也可以用 ["*"] 放行所有(不安全!)
    allow_credentials=True, # 允许携带Cookie
    allow_methods=["*"],    # 允许所有方法 (GET, POST, 等)
    allow_headers=["*"],    # 允许所有请求头
)

@app.get("/")
async def main():
    return {"message": "Hello World"}

这里千万别学我当初偷懒,图省事直接上 `allow_origins=["*"]`,这在生产环境是安全大忌,相当于餐厅大门敞开,谁都能进。线上务必明确指定前端域名!

🧪 三、动手:写一个自己的中间件

接下来重点来了,自定义中间件。比如,我们要给每个请求记日志,并计算处理耗时。

复制代码
import time
from fastapi import FastAPI, Request
app = FastAPI()

@app.middleware("http") # 这是关键装饰器
async def log_request_time(request: Request, call_next):
    # 1. 请求进来时
    # 这里我们使用 time.perf_counter() 而不是 time.time(),因为它对于这些用例可能更精确
    start_time = time.perf_counter()
    path = request.url.path
    method = request.method
    print(f"👉 收到请求: {method} {path}")

    # 2. 把请求交给下一个处理环节(可能是其他中间件,或者是最终的路由)
    response = await call_next(request)

    # 3. 响应返回前
    process_time = time.perf_counter() - start_time
    response.headers["X-Process-Time"] = str(process_time) # 可以往响应头加东西
    print(f"✅ 请求完成: {method} {path}, 耗时: {process_time:.4f}秒")
    return response

@app.get("/test")
async def test():
    return {"message": "ok"}

访问 /test,看看控制台,是不是日志和响应头都有了?这就是一个最基础的HTTP中间件。

划重点: 中间件函数接收一个 call_next,它就像接力棒,你必须调用它(await call_next(request)),请求才能继续往后走。你在它前面和后面写的代码,就分别对应了"请求处理"和"响应处理"两个阶段。

🔄 四、灵魂拷问:多个中间件,谁先谁后?

你是不是以为加完就完事了?多个中间件的执行顺序是超级易错点!

想象一下,你既加了CORS中间件,又加了上面的日志中间件,还加了一个认证中间件。它们怎么排队?

结论:按照添加的相反顺序执行"请求阶段",再按照添加的正序执行"响应阶段"。 像洋葱一样,一层层进去,再一层层出来。

复制代码
app = FastAPI()

# 假设我们按顺序添加三个中间件
app.add_middleware(MiddlewareC) # 第三个添加
app.add_middleware(MiddlewareB) # 第二个添加
app.middleware("http")(middleware_a) # 第一个添加(装饰器写法)

# 实际执行顺序(请求阶段):
# 1. middleware_a 的请求处理代码
# 2. MiddlewareB 的请求处理代码
# 3. MiddlewareC 的请求处理代码
# --- 到达路由函数 ---
# 4. MiddlewareC 的响应处理代码
# 5. MiddlewareB 的响应处理代码
# 6. middleware_a 的响应处理代码
# --- 响应返回给客户端 ---

官方文档虽然说了是"装饰器顺序",但用 app.add_middleware() 添加时更容易迷糊。记不住就背下这个口诀:"后来居上(请求),原路返回(响应)"。 设计时,要把依赖关系想清楚,比如认证应该放在靠"里"层(后添加),日志可以放在最"外"层(先添加)。

🚀 五、进阶:更底层的ASGI中间件

再说个容易翻车的点。上面我们用 @app.middleware("http") 叫HTTP中间件,是FastAPI封装好的。还有一种更底层、更强大的叫ASGI中间件

它和HTTP中间件啥区别?好比一个是高级餐厅的固定流程(HTTP),另一个是后厨的原子操作(ASGI),能处理WebSocket等更多协议。

怎么用?通常你需要一个第三方库,比如 starlette-context 来在请求中传递全局数据,或者自己封装(FastAPI在fastapi.middleware中提供了几个中间件,仅仅是为了方便开发者,但大多数可用的中间件直接来自Starlette):

复制代码
from fastapi import FastAPI
from starlette.middleware.base import BaseHTTPMiddleware

class CustomHeaderMiddleware(BaseHTTPMiddleware):
    async def dispatch(self, request, call_next):
        response = await call_next(request)
        response.headers["X-Custom-Header"] = "MyValue"
        return response

app = FastAPI()
app.add_middleware(CustomHeaderMiddleware) # 这里添加的就是ASGI中间件

注意: BaseHTTPMiddleware 使用简单,但官方提示可能有轻微性能损耗,因为每个请求都会创建新的类实例。对于超高并发场景,用之前的函数式 @app.middleware("http") 或直接写纯ASGI中间件是更优解。

💎 六、我的踩坑经验与最佳实践

最后啰嗦几句,都是实战中总结的血泪教训:
1️⃣ 响应式中间件要小心: 如果你想在中间件里读取响应的body,比如做全局响应体格式化,需要先把它读出来(body = await response.body()),但这会让响应体在内存中多存一份。对于大文件流响应,慎用!

2️⃣ 异常处理要覆盖: 中间件里的 call_next 可能会抛出异常,记得用 try...except 包住,并返回一个合理的错误响应,而不是让服务崩掉。

3️⃣ 区分中间件和依赖注入: 像认证这种,每个路由可能要求不同(有的需要登录,有的不需要),用依赖注入(Depends)更灵活。中间件更适合做全局的、无差异的处理,比如CORS、全局日志、GZip压缩。

工具的选择,好比选螺丝刀,不是最贵的就好,而是**"合适"**最好。对于大部分业务场景,HTTP中间件+依赖注入的组合,已经能解决99%的问题了。


好了,关于FastAPI中间件的实战经验,今天就先聊到这。中间件就像你应用的"基础设施",搭建好了,后面写业务代码才能安心、省心。

老规矩,代码片段都跑过,放心参考。 如果你在配置中间件时还遇到了其他妖魔鬼怪,欢迎留言,咱们一起降妖除魔。

觉得有用的话,点赞、收藏、关注三连一下呗?你的支持是我熬夜写干货的最大动力。下期咱们可以聊聊FastAPI的依赖注入怎么玩出花,或者部署上线的那些坑。回见!👩💻

相关推荐
郝学胜-神的一滴9 小时前
Python中的bisect模块:优雅处理有序序列的艺术
开发语言·数据结构·python·程序人生·算法
jackylzh9 小时前
PyTorch 2.x 中 `torch.load` 的 `FutureWarning` 与 `weights_only=False` 参数分析
人工智能·pytorch·python
MACKEI9 小时前
服务器流式传输接口问题排查与解决方案
python·nginx·流式
毕设源码-郭学长9 小时前
【开题答辩全过程】以 基于Python爬取学院师资队伍信息的设计与分析为例,包含答辩的问题和答案
开发语言·python
2301_765703149 小时前
工具、测试与部署
jvm·数据库·python
Jackson@ML9 小时前
Kimi K2.5横空出世!K2.5模型功能详解
python·大语言模型·kimi
BYSJMG10 小时前
计算机毕设选题推荐:基于大数据的癌症数据分析与可视化系统
大数据·vue.js·python·数据挖掘·数据分析·课程设计
我材不敲代码10 小时前
Python爬虫介绍——简单了解一下爬虫
开发语言·爬虫·python
naruto_lnq10 小时前
Python日志记录(Logging)最佳实践
jvm·数据库·python