|
| 1 | ++++ |
| 2 | +date = '2025-11-25T8:00:00+08:00' |
| 3 | +draft = true |
| 4 | +title = 'FastAPI app and request' |
| 5 | +tags = ['FastAPI'] |
| 6 | ++++ |
| 7 | + |
| 8 | +在 FastAPI 中,`Request` 对象和 `FastAPI` 应用实例 (`app`) 是核心概念,它们在应用状态管理和依赖注入中扮演着关键角色。 |
| 9 | +本文将介绍它们的关系、设计理念,以及如何利用 `app.state` 实现单例模式。 |
| 10 | + |
| 11 | +--- |
| 12 | + |
| 13 | +## FastAPI 对象 |
| 14 | + |
| 15 | +`FastAPI` 对象是整个应用的核心实例: |
| 16 | + |
| 17 | +```python |
| 18 | +from fastapi import FastAPI |
| 19 | + |
| 20 | +app = FastAPI(title="示例应用") |
| 21 | +``` |
| 22 | + |
| 23 | +### 核心职责 |
| 24 | + |
| 25 | +- **路由管理**:通过 `@app.get()`、`@app.post()` 等装饰器定义 URL 到视图函数的映射。 |
| 26 | +- **中间件和事件管理**:可注册中间件处理请求/响应,支持 `startup` 与 `shutdown` 事件。 |
| 27 | +- **应用状态管理**:提供 `app.state`,可存放全局单例对象、数据库连接池、配置等。 |
| 28 | +- **异常处理与依赖注入**:管理异常处理器,并协助依赖注入机制。 |
| 29 | + |
| 30 | +### 单例模式存储 |
| 31 | + |
| 32 | +这里要使用 app 的 State 对象存储单例,app 中定义如下 |
| 33 | + |
| 34 | +```python |
| 35 | +# app |
| 36 | +self.state: Annotated[ |
| 37 | + State, |
| 38 | + Doc( |
| 39 | + """ |
| 40 | + A state object for the application. This is the same object for the |
| 41 | + entire application, it doesn't change from request to request. |
| 42 | +
|
| 43 | + You normally wouldn't use this in FastAPI, for most of the cases you |
| 44 | + would instead use FastAPI dependencies. |
| 45 | +
|
| 46 | + This is simply inherited from Starlette. |
| 47 | +
|
| 48 | + Read more about it in the |
| 49 | + [Starlette docs for Applications](https://www.starlette.dev/applications/#storing-state-on-the-app-instance). |
| 50 | + """ |
| 51 | + ), |
| 52 | +] = State() |
| 53 | + |
| 54 | +``` |
| 55 | + |
| 56 | +State 源码如下,简单看就是一个字典 |
| 57 | + |
| 58 | +```Python |
| 59 | +# State |
| 60 | +class State: |
| 61 | + """ |
| 62 | + An object that can be used to store arbitrary state. |
| 63 | +
|
| 64 | + Used for `request.state` and `app.state`. |
| 65 | + """ |
| 66 | + |
| 67 | + _state: dict[str, Any] |
| 68 | + |
| 69 | + def __init__(self, state: dict[str, Any] | None = None): |
| 70 | + if state is None: |
| 71 | + state = {} |
| 72 | + super().__setattr__("_state", state) |
| 73 | + |
| 74 | + def __setattr__(self, key: Any, value: Any) -> None: |
| 75 | + self._state[key] = value |
| 76 | + |
| 77 | + def __getattr__(self, key: Any) -> Any: |
| 78 | + try: |
| 79 | + return self._state[key] |
| 80 | + except KeyError: |
| 81 | + message = "'{}' object has no attribute '{}'" |
| 82 | + raise AttributeError(message.format(self.__class__.__name__, key)) |
| 83 | + |
| 84 | + def __delattr__(self, key: Any) -> None: |
| 85 | + del self._state[key] |
| 86 | + |
| 87 | +``` |
| 88 | + |
| 89 | +使用示例 |
| 90 | + |
| 91 | +```Python |
| 92 | +@asynccontextmanager |
| 93 | +async def lifespan(app: FastAPI) -> AsyncGenerator[None, None]: |
| 94 | + """ |
| 95 | + FastAPI 应用生命周期管理 |
| 96 | +
|
| 97 | + 启动时初始化资源,关闭时清理资源 |
| 98 | + """ |
| 99 | + # 初始化单例 |
| 100 | + print("[应用启动] 创建 Admin Agent 服务实例...") |
| 101 | + admin_agent_service = AdminService(mcp_server_configuration) |
| 102 | + app.state.admin_agent_service = admin_agent_service |
| 103 | + await admin_agent_service.initialize() # 立即初始化 Agent |
| 104 | + |
| 105 | + yield # 应用运行其间 |
| 106 | + |
| 107 | + # 关闭数据库连接池 |
| 108 | + await close_database() |
| 109 | + |
| 110 | + # 关闭 Redis 连接 |
| 111 | + await close_redis() |
| 112 | +``` |
| 113 | + |
| 114 | +当需要获取该单例的时候,通过依赖注入得到 |
| 115 | + |
| 116 | +```Python |
| 117 | +def get_admin_service(request: Request) -> AdminService: |
| 118 | + """获取 Admin Agent 服务实例 (app.state)""" |
| 119 | + return request.app.state.admin_agent_service |
| 120 | + |
| 121 | +@app.post() |
| 122 | +async def send_admin_message(request: Request) -> StreamingResponse: |
| 123 | + ... |
| 124 | + |
| 125 | + # 获取 Admin Agent 服务实例 |
| 126 | + admin_service = get_admin_service(request) |
| 127 | + |
| 128 | + ... |
| 129 | + |
| 130 | + # SSE |
| 131 | + return StreamingResponse( |
| 132 | + media_type="application/x-ndjson", |
| 133 | + headers={ |
| 134 | + "Cache-Control": "no-cache", |
| 135 | + "Connection": "keep-alive", |
| 136 | + "X-Accel-Buffering": "no", |
| 137 | + } |
| 138 | + ) |
| 139 | +``` |
| 140 | + |
| 141 | +- **作用**:确保整个应用中只有一个 `AdminService` 实例被创建和共享。 |
| 142 | +- **优势**: |
| 143 | + - 避免重复初始化资源 |
| 144 | + - 统一管理全局服务 |
| 145 | + - 可在请求中安全访问 |
| 146 | + |
| 147 | +## Request 对象 |
| 148 | + |
| 149 | +`Request` 对象封装了一次 HTTP 请求的上下文: |
| 150 | + |
| 151 | +```python |
| 152 | +def get_admin_service(request: Request) -> AdminService: |
| 153 | + """获取 Admin Agent 服务实例 (app.state)""" |
| 154 | + return request.app.state.admin_agent_service |
| 155 | +``` |
| 156 | + |
| 157 | +### 核心职责 |
| 158 | + |
| 159 | +- **封装请求信息**:包含方法、路径、headers、query 参数、body 等。 |
| 160 | +- **提供应用访问入口**:`request.app` 指向该请求所属的 `FastAPI` 实例。 |
| 161 | +- **支持依赖注入**:可以作为依赖函数参数,让函数获取应用状态或服务实例。 |
| 162 | + |
| 163 | +`Request` 与 `app` 的关联 |
| 164 | + |
| 165 | +1. **解耦请求与全局变量** |
| 166 | + |
| 167 | + 请求处理函数无需直接引用全局 `app`,通过 `request.app` 即可访问应用实例和状态。 |
| 168 | + |
| 169 | +2. **支持多实例应用** |
| 170 | + |
| 171 | + 同一个 Python 进程可以启动多个 FastAPI 实例,每个实例的 `state` 独立。请求绑定到具体实例,`request.app` 指向对应实例。 |
| 172 | + |
| 173 | +3. **依赖注入友好** |
| 174 | + |
| 175 | + 在依赖函数中,通过 `Request` 获取 `app.state` 中的单例对象: |
| 176 | + |
| 177 | +```python |
| 178 | +from fastapi import Depends |
| 179 | + |
| 180 | +def get_admin_service(request: Request) -> AdminService: |
| 181 | + return request.app.state.admin_service |
| 182 | + |
| 183 | +@app.get("/admin") |
| 184 | +async def admin_endpoint(service: AdminService = Depends(get_admin_service)): |
| 185 | + return {"message": service.hello()} |
| 186 | +``` |
| 187 | + |
| 188 | +- **工作流程**: |
| 189 | + 1. FastAPI 收到请求,创建 `Request` 对象。 |
| 190 | + 2. DI 系统调用 `get_admin_service`,将 `Request` 注入。 |
| 191 | + 3. 函数通过 `request.app.state.admin_service` 获取全局单例。 |
| 192 | + 4. 依赖对象传入 endpoint。 |
| 193 | + |
| 194 | +## 总结 |
| 195 | + |
| 196 | +- `app`:FastAPI 实例,负责路由、事件、中间件、依赖和状态管理。 |
| 197 | +- `app.state`:存储单例对象和全局资源,实现单例模式。 |
| 198 | +- `Request`:每次请求的封装,携带请求信息和对 `app` 的引用。 |
| 199 | +- `request.app.state`:结合依赖注入,让每个请求安全访问全局单例对象,无需使用全局变量。 |
| 200 | + |
| 201 | +这种设计模式既保证了应用单例对象的统一管理,又支持依赖注入和多实例应用,非常适合现代 Web 应用开发。 |
0 commit comments