中间件是Web开发中一个非常重要的概念,它就像是你家门前的一道安检门,所有进出的人(请求和响应)都要经过它的检查和处理。在FastAPI中,中间件是一个能够在请求到达路由处理器之前和/或响应返回客户端之前执行操作的组件。
想象一下这样的流程:当一个HTTP请求到达你的FastAPI应用时,它会先经过一系列中间件,就像通过一道道安检门。每个中间件都可以对请求进行检查、修改或者添加额外信息。处理完请求后,响应在返回给客户端之前,又会反向经过这些中间件,让它们有机会对响应进行处理。
下面是一个最简单的FastAPI中间件示例:
python复制from fastapi import FastAPI, Request
from fastapi.responses import JSONResponse
import uvicorn
app = FastAPI()
@app.middleware("http")
async def simple_middleware(request: Request, call_next):
# 请求处理前的操作
print(f"收到请求: {request.method} {request.url}")
# 调用下一个中间件或路由处理器
response = await call_next(request)
# 响应返回前的操作
if isinstance(response, JSONResponse):
response.headers["X-Processed-By"] = "SimpleMiddleware"
print(f"处理完成: {response.status_code}")
return response
@app.get("/")
async def root():
return {"message": "Hello World"}
if __name__ == "__main__":
uvicorn.run(app, host="127.0.0.1", port=8000)
这个简单的中间件做了三件事:在请求到达时打印日志,调用下一个处理环节,在响应返回前添加自定义头部。这就是中间件的基本工作模式。
中间件在FastAPI中有很多实际应用场景,比如记录请求日志、认证和授权、修改响应内容、性能监控、跨域资源共享(CORS)处理等。它们就像是应用的门卫、记录员和质检员,各司其职但又协同工作。
在现代Web开发中,前端和后端经常部署在不同的域名下,这就涉及跨域问题。CORSMiddleware就是FastAPI提供的解决跨域问题的利器。
跨域问题的本质是浏览器的同源策略限制。简单来说,浏览器不允许一个域下的网页脚本直接访问另一个域的资源。这是出于安全考虑,但给开发带来了不便。
CORSMiddleware的使用非常简单:
python复制from fastapi import FastAPI
from fastapi.middleware.cors import CORSMiddleware
app = FastAPI()
# 配置CORS中间件
app.add_middleware(
CORSMiddleware,
allow_origins=["http://localhost:3000"], # 允许的前端地址
allow_credentials=True,
allow_methods=["*"], # 允许所有HTTP方法
allow_headers=["*"], # 允许所有头部
)
@app.get("/")
async def main():
return {"message": "Hello World"}
这个配置允许来自http://localhost:3000的跨域请求,支持所有HTTP方法和头部。在实际项目中,你应该根据需求精确配置允许的源、方法和头部,而不是简单地使用通配符"*",这能更好地保障应用安全。
网络传输中,数据压缩能显著减少传输量,提高响应速度。GZipMiddleware就是用来自动压缩HTTP响应的中间件。
python复制from fastapi import FastAPI
from fastapi.middleware.gzip import GZipMiddleware
app = FastAPI()
# 添加GZip中间件,默认只压缩大于1000字节的响应
app.add_middleware(GZipMiddleware, minimum_size=1000)
@app.get("/large-data")
async def get_large_data():
# 返回一个大的JSON响应
return {"data": [str(i) for i in range(10000)]}
这个中间件会自动检测客户端是否支持gzip压缩(通过Accept-Encoding头部),如果支持就会对响应进行压缩。注意,对于已经是压缩格式的内容(如图片、视频),再次压缩可能反而会增加体积,所以中间件默认会跳过这些类型。
这个中间件用于验证请求的Host头部,防止主机头攻击。它可以确保你的应用只处理来自可信域名的请求。
python复制from fastapi import FastAPI
from starlette.middleware.trustedhost import TrustedHostMiddleware
app = FastAPI()
app.add_middleware(
TrustedHostMiddleware,
allowed_hosts=["example.com", "*.example.com"]
)
@app.get("/")
async def main():
return {"message": "Hello World"}
如果请求的Host头部不在允许列表中,中间件会返回400 Bad Request响应。这在生产环境中是很有用的安全措施。
除了使用内置中间件,我们经常需要开发自己的中间件。FastAPI基于Starlette,所以自定义中间件的方式与Starlette一致。
下面是一个记录请求处理时间的中间件:
python复制import time
from fastapi import FastAPI, Request
from starlette.middleware.base import BaseHTTPMiddleware
class TimingMiddleware(BaseHTTPMiddleware):
async def dispatch(self, request: Request, call_next):
start_time = time.time()
response = await call_next(request)
process_time = time.time() - start_time
response.headers["X-Process-Time"] = str(process_time)
return response
app = FastAPI()
app.add_middleware(TimingMiddleware)
@app.get("/")
async def root():
return {"message": "Hello World"}
这个中间件会在响应头中添加X-Process-Time,记录请求处理耗时。BaseHTTPMiddleware是Starlette提供的基类,它简化了中间件的开发。
有时我们需要可配置的中间件。下面是一个限流中间件的例子,可以配置每个IP的请求频率限制:
python复制from fastapi import FastAPI, Request, HTTPException
from starlette.middleware.base import BaseHTTPMiddleware
from starlette.responses import JSONResponse
import time
class RateLimitMiddleware(BaseHTTPMiddleware):
def __init__(self, app, max_requests: int, time_window: int):
super().__init__(app)
self.max_requests = max_requests
self.time_window = time_window
self.request_counts = {}
async def dispatch(self, request: Request, call_next):
client_ip = request.client.host
current_time = time.time()
if client_ip in self.request_counts:
if current_time - self.request_counts[client_ip]["timestamp"] > self.time_window:
self.request_counts[client_ip] = {"count": 1, "timestamp": current_time}
else:
self.request_counts[client_ip]["count"] += 1
if self.request_counts[client_ip]["count"] > self.max_requests:
return JSONResponse(
status_code=429,
content={"error": "Too many requests"}
)
else:
self.request_counts[client_ip] = {"count": 1, "timestamp": current_time}
return await call_next(request)
app = FastAPI()
app.add_middleware(RateLimitMiddleware, max_requests=5, time_window=60)
@app.get("/")
async def root():
return {"message": "Hello World"}
这个中间件限制每个IP每分钟最多5次请求。当超过限制时,返回429 Too Many Requests响应。
中间件不仅可以读取响应,还可以修改它。下面是一个统一修改JSON响应格式的中间件:
python复制from fastapi import FastAPI, Request
from starlette.middleware.base import BaseHTTPMiddleware
from starlette.responses import JSONResponse
class ResponseFormatterMiddleware(BaseHTTPMiddleware):
async def dispatch(self, request: Request, call_next):
response = await call_next(request)
if isinstance(response, JSONResponse):
original_content = response.body.decode()
formatted_content = {
"success": True,
"data": original_content,
"timestamp": int(time.time())
}
return JSONResponse(
content=formatted_content,
status_code=response.status_code,
headers=dict(response.headers)
)
return response
app = FastAPI()
app.add_middleware(ResponseFormatterMiddleware)
@app.get("/")
async def root():
return {"message": "Hello World"}
这个中间件将所有JSON响应包装成统一格式,添加了success标志和时间戳。这在API开发中很有用,可以保持响应格式的一致性。
中间件的执行顺序很重要,它遵循"先进后出"的栈原则。先添加的中间件会先处理请求,但后处理响应。
python复制from fastapi import FastAPI, Request
from starlette.middleware.base import BaseHTTPMiddleware
class MiddlewareA(BaseHTTPMiddleware):
async def dispatch(self, request: Request, call_next):
print("MiddlewareA: before request")
response = await call_next(request)
print("MiddlewareA: after response")
return response
class MiddlewareB(BaseHTTPMiddleware):
async def dispatch(self, request: Request, call_next):
print("MiddlewareB: before request")
response = await call_next(request)
print("MiddlewareB: after response")
return response
app = FastAPI()
app.add_middleware(MiddlewareA) # 先添加
app.add_middleware(MiddlewareB) # 后添加
@app.get("/")
async def root():
print("Handler processing")
return {"message": "Hello World"}
执行这个示例,你会看到输出顺序是:
code复制MiddlewareA: before request
MiddlewareB: before request
Handler processing
MiddlewareB: after response
MiddlewareA: after response
中间件虽然强大,但过度使用会影响性能。以下是一些优化建议:
python复制import os
from fastapi import FastAPI
from .middlewares import DebugMiddleware, LoggingMiddleware
app = FastAPI()
if os.getenv("ENV") == "development":
# 只在开发环境添加调试中间件
app.add_middleware(DebugMiddleware)
app.add_middleware(LoggingMiddleware)
中间件中的错误需要特别处理,因为它们不会自动被FastAPI的错误处理器捕获。
python复制from fastapi import FastAPI, Request, HTTPException
from starlette.middleware.base import BaseHTTPMiddleware
from starlette.responses import JSONResponse
class ErrorHandlerMiddleware(BaseHTTPMiddleware):
async def dispatch(self, request: Request, call_next):
try:
return await call_next(request)
except HTTPException as http_exc:
return JSONResponse(
content={"error": http_exc.detail},
status_code=http_exc.status_code
)
except Exception as exc:
# 记录详细错误信息
print(f"Unexpected error: {exc}")
return JSONResponse(
content={"error": "Internal Server Error"},
status_code=500
)
app = FastAPI()
app.add_middleware(ErrorHandlerMiddleware)
@app.get("/error-test")
async def error_test():
raise ValueError("Something went wrong")
这个错误处理中间件可以捕获各种异常,并返回适当的响应。注意在生产环境中,你应该使用更完善的日志记录系统。
中间件和路由处理器之间经常需要共享状态。可以使用request.state来实现:
python复制from fastapi import FastAPI, Request
from starlette.middleware.base import BaseHTTPMiddleware
import uuid
class RequestIDMiddleware(BaseHTTPMiddleware):
async def dispatch(self, request: Request, call_next):
request.state.request_id = str(uuid.uuid4())
response = await call_next(request)
response.headers["X-Request-ID"] = request.state.request_id
return response
app = FastAPI()
app.add_middleware(RequestIDMiddleware)
@app.get("/")
async def root(request: Request):
return {"request_id": request.state.request_id}
这个中间件为每个请求生成唯一ID,并通过request.state共享给路由处理器,同时在响应头中返回这个ID。这在分布式系统中特别有用,便于追踪请求链路。