When I use request
inside a FastAPI route, the request
is filled with the proper headers, such as:
request.headers.get('X-Forwarded-For')
However, when I use the request
object inside a function, which isn't a route, all the values are empty. I guess the whole object is not evaluated?
Working:
from fastapi import Request
@app.get("/logip")
async def log_request_ip(request: Request):
print(request.headers.get("X-Forwarded-For))
Not working:
from fastapi import Request
@app.get("/logip")
async def log_request_ip():
log_ip()
def log_ip(request: Request = Request):
print(request.headers.get("X-Forwarded-For))
Is there a reason for this? Does FastAPI only evaluate the request object when it is injected into the route, but not in a function? Is there any way to make this possible? I want to avoid injecting the request into every route, and just keep this in the function that actually uses this value.
Is there maybe a way to tell FastAPI that this function is only used within a request context?
It won't be possible.
Your log_ip
function is not called by FastAPI, so it can't inject the request into it.
I think you have two options to make it work:
Pass the request yourself: log_ip(request=request)
This will be enough if you only need to call log_ip
in this specific endpoint.
The better solution would be to create a middleware that will automatically call your function on every request, this way you won't have to manually call log_ip
at all and it will be handled by FastAPI
# Register the middleware on app instance
@app.middleware('http')
async def log_ip_middleware(request: Request, call_next: Callable[..., Awaitable[Any]]) -> Response:
# Call log_ip
log_ip(request=request)
# Return result of call_next (the response)
return await call_next(request)
EDIT.
With your comment in mind, you can change the middleware to set request to the contextvar that will be accessible from anywhere.
So the middleware becomes:
from contextvars import ContextVar
current_request: ContextVar[Request] = ContextVar('current_request')
# Register the middleware on app instance
@app.middleware('http')
async def current_request_middleware(request: Request, call_next: Callable[..., Awaitable[Any]]) -> Response:
current_request.set(request)
# Return result of call_next (the response)
return await call_next(request)
And then in your log_ip
function:
from .<module> import current_request
def log_ip():
# now this function can access the current request
request = current_request.get()
print(request.headers.get("X-Forwarded-For"))
This way your log_ip
function can always access the request no matter the place it is being called from. Keep in mind that it might be a good idea to make contextvar Request | None
type, setting default to None
, and then check if request was set inside the log_ip
in case you will call it outside the framework scope (like inside a background worker).
You can read more on contextvars here.