FastAPI/Starlette中普通函数注入Request对象为空的问题咨询
嘿,我来给你理清楚这个问题哈~
首先说原因:FastAPI(底层基于Starlette)的依赖注入系统只有在请求上下文范围内,并且通过框架的依赖注入流程处理时,才会给你填充当前请求的真实Request实例。
你看,路由函数是FastAPI处理请求的入口,框架会自动拦截路由的调用,解析参数里的依赖(包括Request),把当前请求的真实对象传进去。但你第二个写法里的log_ip函数,你只是把Request作为默认参数(request: Request = Request),这其实只是创建了一个Request类的空占位符,根本不是当前请求的真实实例——因为框架根本没在这个函数的调用链里触发依赖注入流程,自然拿不到请求头信息。
接下来给你几个解决办法,都能帮你避免在每个路由里显式注入Request:
方法1:把工具函数改成依赖函数,用Depends触发注入
你可以把log_ip改成一个依赖函数,让FastAPI在请求上下文里自动解析它的依赖(也就是Request):
from fastapi import Request, Depends def log_ip(request: Request = Depends()): print(request.headers.get("X-Forwarded-For")) # 路由里调用时,只需要把这个依赖注入进去即可 @app.get("/logip") async def log_request_ip(_=Depends(log_ip)): # 这里log_ip已经被框架执行过,会自动打印请求IP pass
如果你的工具函数需要在路由逻辑中间调用(不是作为路由的前置依赖),也可以在函数内部通过Depends获取Request,不过这种场景下方法2会更灵活。
方法2:直接从请求上下文获取当前Request
Starlette自带了请求上下文机制,你可以直接在工具函数里拿到当前请求的实例,完全不用显式注入:
from starlette.requests import Request from starlette.context import request_context def log_ip(): try: # 从当前请求上下文里取出真实的Request实例 request = request_context.get() print(request.headers.get("X-Forwarded-For")) except RuntimeError: # 如果在请求上下文之外调用(比如启动时的后台任务),会抛出错误,这里可以加降级逻辑 print("当前不在请求上下文范围内,无法获取Request对象")
这个方法的好处是,你在路由里直接调用log_ip()就行,不用传任何参数——只要这个函数是在请求处理的调用链里执行的(比如路由函数内部调用、路由里调用的其他函数),就能正常拿到请求信息。
方法3:全局依赖注入(适合全路由都需要的场景)
如果你的大部分路由都需要用到这类请求相关的逻辑,可以把Request的注入加到全局依赖里,这样每个路由都会自动处于请求上下文的覆盖中,工具函数依然可以用方法2的方式获取Request:
from fastapi import FastAPI, Request, Depends app = FastAPI(dependencies=[Depends(Request)]) # 之后的所有路由都自动处于请求上下文里,工具函数直接用方法2即可
不过这个方法比较重,如果你只是少数函数需要用到请求信息,方法2会更轻量。
最后再给你划个重点:
Request对象必须在请求上下文范围内才能拿到真实实例- 只有FastAPI的依赖注入流程(路由参数、
Depends、全局依赖)才会给你填充当前请求的Request - 你之前的写法只是创建了
Request类的空占位符,不是当前请求的真实实例,所以拿不到请求头
这样你就不用在每个路由里都注入Request,直接在需要的工具函数里处理就行啦~
备注:内容来源于stack exchange,提问作者c8999c 3f964f64

