首页
/ FastAPI 进阶实践:直接注入并使用 Request 对象

FastAPI 进阶实践:直接注入并使用 Request 对象

2026-09-06 15:19:45作者:幸俭卉

在 FastAPI 中,绝大多数请求数据(路径参数、查询参数、请求头、Cookie、请求体)都是通过声明带类型的参数来获取的,FastAPI 会自动完成校验、类型转换和 OpenAPI 文档生成。但在一些场景下——例如需要获取客户端 IP 地址——这些数据并不属于参数声明体系,必须绕过常规机制,直接访问底层的 Request 对象。本文基于仓库中的 直接访问 Request 文档,完整覆盖其核心内容与示例,并结合 FastAPI 源码深入解析 Request 参数的注入原理与行为边界。

读完本文,你将能够:

  • 在路径操作函数中声明 request: Request 参数,直接拿到 Starlette 的 Request 对象;
  • 理解直接从 Request 读取数据与声明式参数在“校验、转换、文档化”上的本质区别;
  • 从源码层面确认 FastAPI 是如何识别并注入 Request(以及同类的 WebSocketResponse 等特殊类型)的。

背景:声明式参数的局限

到目前为止,使用 FastAPI 获取请求数据的方式都是“声明即所得”:

  • 从路径中提取参数(item_id: int);
  • 从请求头(Header)、Cookie 中提取数据;
  • 从请求体(Body,通常是 Pydantic 模型)中提取数据。

这么做的好处是:FastAPI 会自动校验(validate)这些数据、把它们转换(convert)成声明的类型,并自动生成 API 文档(OpenAPI schema),供交互式文档界面使用。

然而,有一部分请求信息不属于“参数”的范畴——典型例子就是客户端 IP 地址。这类信息无法用类型声明来约束,需要直接访问 Request 对象。

Request 对象详解:FastAPI 建立在 Starlette 之上

FastAPI 的底层实际上是 Starlette,FastAPI 在其之上叠加了一层工具。因此,当你需要直接访问请求时,可以直接使用 Starlette 的 Request 对象。

这一点在源码中体现得非常直白。FastAPI 对外的 Request 定义文件 fastapi/requests.py 全部内容只有两行再导出:

from starlette.requests import HTTPConnection as HTTPConnection  # noqa: F401
from starlette.requests import Request as Request  # noqa: F401

也就是说,from fastapi import Request 拿到的就是 Starlette 的 Request 类本身,FastAPI 只是顺手提供这个导入路径,作为对开发者的便利。

需要特别注意的行为边界:

  • 如果你直接从 Request 对象获取数据(例如 await request.body() 读取请求体),这些数据不会被 FastAPI 校验、转换,也不会出现在 OpenAPI 文档中;
  • 但同时以正常方式声明的其他参数(例如用 Pydantic 模型声明的请求体)仍然会被正常校验、转换和标注。

两者可以共存,互不干扰。

直接使用 Request 对象:获取客户端 IP

设想一个常见需求:在路径操作函数中获取客户端的 IP 地址(host)。此时需要直接访问请求对象。仓库中的完整示例 docs_src/using_request_directly/tutorial001_py310.py 如下:

from fastapi import FastAPI, Request

app = FastAPI()


@app.get("/items/{item_id}")
def read_root(item_id: str, request: Request):
    client_host = request.client.host
    return {"client_host": client_host, "item_id": item_id}

关键就是那一行 request: Request只要把路径操作函数的参数类型声明为 Request,FastAPI 就会知道要把 Request 实例传入该参数

与其他参数声明并存

注意上面的示例中,路径参数 item_id: strrequest: Request 同时出现在同一函数签名里。这体现了该机制的一个实用特性:

  • 路径参数 item_id 会被照常提取、校验、转换,并写入 OpenAPI 文档;
  • request 参数则只是接收 Request 对象,不出现在文档里;
  • 同理,你可以在同一函数中正常声明任何数量的其他参数(查询参数、请求头、请求体等),同时额外拿到 Request

仓库中的测试 tests/test_tutorial/test_using_request_directly/test_tutorial001.py 验证了这一行为:

def test_path_operation():
    response = client.get("/items/foo")
    assert response.status_code == 200
    assert response.json() == {"client_host": "testclient", "item_id": "foo"}

使用 TestClient 发起请求时,request.client.host 返回 "testclient"(测试客户端的虚拟主机名),而 item_id 正常返回路径值 "foo"——两个参数各司其职。

同文件中的 test_openapi 则通过 inline_snapshot 快照了 /openapi.json 的完整输出,可以确认该操作的 parameters只有 item_id 这一个 path 参数,request 参数完全不出现在 OpenAPI Schema 中,与文档中“直接从 Request 取数不生成文档”的描述一致。

源码级解析:FastAPI 如何识别并注入 Request

在依赖解析阶段,FastAPI 会把函数签名中的参数分为两类:常规“字段参数”(生成校验逻辑的 Query/Path/Body 等)和“非字段特殊参数”。识别后者的逻辑位于 fastapi/dependencies/utils.py

def add_non_field_param_to_dependency(
    *, param_name: str, type_annotation: Any, dependant: Dependant
) -> bool | None:
    if lenient_issubclass(type_annotation, Request):
        dependant.request_param_name = param_name
        return True
    elif lenient_issubclass(type_annotation, WebSocket):
        dependant.websocket_param_name = param_name
        return True
    elif lenient_issubclass(type_annotation, HTTPConnection):
        dependant.http_connection_param_name = param_name
        return True
    elif lenient_issubclass(type_annotation, Response):
        dependant.response_param_name = param_name
        return True
    ...

从源码结构看,RequestWebSocketHTTPConnectionResponseBackgroundTasksSecurityScopes 这一族类型被统一归为“非字段参数”(non-field param):它们不走校验/文档流程,而是在 Dependant 上记录对应的参数名(如 request_param_name),等到真正调用路径操作函数时,再把当前请求对应的实例按键注入。

analyze_param 中还能看到配套约束:

# Handle non-param type annotations like Request
if depends is None and lenient_issubclass(
    type_annotation,
    (
        Request,
        WebSocket,
        HTTPConnection,
        Response,
        StarletteBackgroundTasks,
        SecurityScopes,
    ),
):
    assert field_info is None, (
        f"Cannot specify FastAPI annotation for type {type_annotation!r}"
    )

这段代码说明两点:一是 Request 这类特殊类型的识别是按类型注解完成的(lenient_issubclass 兼容了 Annotated 包装);二是这类参数不能再叠加 FastAPI 的参数注解(如 Query(...)),否则会在启动构建路由时直接触发断言错误。这也解释了为什么 request 参数在 OpenAPI 文档里永远“隐身”——它根本没有进入字段参数的生成路径。

Request 的完整用法与进阶参考

由于 Request 对象就是 Starlette 的对象,其完整属性与 API(request.urlrequest.headersrequest.cookiesrequest.query_paramsawait request.body()request.scope 等)都遵循 Starlette 的行为,FastAPI 文档建议读者前往 Starlette 官方文档查阅 Request 的详细说明。

从源码注入机制(Dependant 是通用依赖结构)来看,Request 注入不限于路径操作函数本身——依赖函数(Depends)的参数解析走同一套 analyze_param / add_non_field_param_to_dependency 逻辑,因此在依赖中声明 request: Request 同样会被注入,可用于在多个端点间复用客户端 IP、来源信息等逻辑。

小结

  • 声明优先,直取兜底:能用类型声明获取的数据(路径、Header、Cookie、Body)尽量用声明式参数,享受自动校验、转换与文档生成;只有 IP 地址这类非参数信息,才需要声明 request: Request 直接访问。
  • 混合无冲突request: Request 可与任意常规参数并存,常规参数照常进入 OpenAPI,Request 参数对文档完全透明(见 test_openapi 快照)。
  • 来源即 Starlettefastapi/requests.py 直接再导出 Starlette 的 Request,你既可以用 from fastapi import Request,也可以 from starlette.requests import Request,两者等价。
  • 注入机制:类型为 Request 的参数在 fastapi/dependencies/utils.py 中被识别为“非字段参数”,记录参数名后在调用时注入,且不允许叠加 FastAPI 参数注解。
登录后查看全文
热门项目推荐
相关项目推荐