如何在Starlette中间件中获取请求的路径模板?
问题解答
1. 什么是scope?
scope是FastAPI/Starlette中存储请求上下文元数据的字典,包含请求方法、客户端地址、URL路径等核心信息,它贯穿整个请求处理流程,不同阶段会逐步填充更多属性。
2. 为什么中间件里scope没有route属性?
你使用的BaseHTTPMiddleware属于全局前置中间件,它的执行时机在FastAPI完成路由匹配之前。路由匹配是FastAPI根据请求URL找到对应端点函数的过程,只有匹配完成后,scope才会被注入route属性(包含原始路由路径、端点信息等)。而端点函数是在路由匹配完成后执行的,所以能拿到route。
3. 解决方法
方法一:使用路由级中间件(路由匹配后执行)
如果日志需求针对特定路由,可使用Starlette的路由级中间件,此时scope已包含route属性:
from starlette.middleware.base import RequestResponseEndpoint from fastapi import Request, APIRouter router = APIRouter() async def route_log_middleware(request: Request, call_next: RequestResponseEndpoint): # 路由已匹配,直接获取原始路由路径 route_path = request.scope["route"].path print(f"原始路由路径: {route_path}") response = await call_next(request) return response router.middleware("http")(route_log_middleware) @router.get("/success/{id}", status_code=201) def success(id: str): return {"id": id} # 挂载到主应用 app.include_router(router)
方法二:在BaseHTTPMiddleware中通过反向路由生成原始路径
如果必须用全局BaseHTTPMiddleware,可通过端点函数名称,用app.url_path_for反向生成原始路由路径:
from starlette.middleware.base import BaseHTTPMiddleware from fastapi import Request class RequestInfo(BaseHTTPMiddleware): async def dispatch(self, request: Request, call_next): response = await call_next(request) # 从request.state获取提前传递的端点名称 if hasattr(request.state, "endpoint_name"): # 生成带占位符的原始路由路径 raw_path = request.app.url_path_for(request.state.endpoint_name, id="*") print(f"原始路由路径格式: {raw_path}") return response # 在端点函数中传递端点名称到request.state @app.get("/success/{id}", status_code=201) def success(request: Request, id: str): request.state.endpoint_name = "success" return {"id": id}
方法三:预加载路由规则,手动匹配请求路径
应用启动时加载所有路由的路径规则,在中间件中手动匹配当前请求路径,得到原始路由格式:
from starlette.middleware.base import BaseHTTPMiddleware from fastapi import Request from starlette.routing import Route class RequestInfo(BaseHTTPMiddleware): def __init__(self, app): super().__init__(app) # 预加载所有路由的路径规则 self.route_paths = [route.path for route in app.routes if isinstance(route, Route)] async def dispatch(self, request: Request, call_next): current_path = request.url.path # 简单匹配示例(复杂路径需用正则精确匹配) for raw_path in self.route_paths: prefix = raw_path.split("{")[0] if current_path.startswith(prefix): print(f"匹配到的原始路由路径: {raw_path}") break response = await call_next(request) return response
内容的提问来源于stack exchange,提问作者Sarah
相关产品推荐
相关产品推荐

