Request 模板文档¶
概述¶
request 模板是在 EventBus 事件总线之上构建的同步风格异步 RPC 调用工具。它封装了“发布请求事件 → 等待匹配的响应事件”这一常见模式,使业务代码能以直观的 await request(...) 方式发起远程调用,而无需手动管理临时处理器、会话匹配和超时控制。
核心协议¶
使用 request 前,请求与响应的负载模型必须遵循约定的协议基类。
| 基类 | 作用 |
|---|---|
RequestProtocol |
所有请求负载的基类,强制包含 session_id 和 request_id 字段,由框架自动注入。 |
ResponseProtocol |
所有响应负载的基类,强制包含 session_id、request_id、success 和 error_msg 字段,提供 raise_if_failed() 方法快速检查失败。 |
协议字段说明¶
RequestProtocol¶
session_id: str— 会话标识,用于关联同一会话内的多次请求。request_id: str— 请求唯一标识,用于精准匹配请求与响应。
ResponseProtocol¶
session_id: str— 必须与对应请求的session_id一致。request_id: str— 必须与对应请求的request_id一致。success: bool— 业务处理是否成功,默认为True。error_msg: Optional[str]— 失败时的错误信息。
工作流程¶
1. 定义请求与响应事件¶
首先,创建继承自 RequestProtocol 和 ResponseProtocol 的 Pydantic 模型,并声明对应的事件。
from pydantic import BaseModel, Field
from infinity_bus import EventDeclaration
from infinity_bus.templates import RequestProtocol, ResponseProtocol, build_response
# 定义负载
class GetUserRequest(RequestProtocol):
user_id: int = Field(description="要查询的用户ID")
class GetUserResponse(ResponseProtocol):
user_name: str = Field(description="用户名")
email: str = Field(description="邮箱")
# 声明事件
class GetUserRequestEvent(EventDeclaration):
name = "user.get.request"
payload_type = GetUserRequest
class GetUserResponseEvent(EventDeclaration):
name = "user.get.response"
payload_type = GetUserResponse
2. 注册事件¶
将事件声明注册到 EventRegistry(通常在应用启动时完成)。
registry = EventRegistry()
registry.register(GetUserRequestEvent)
registry.register(GetUserResponseEvent)
3. 实现服务端处理器¶
在服务端实现一个 EventHandler 监听请求事件,处理完成后发布响应事件。
class GetUserHandler(EventHandler):
def __init__(self):
super().__init__(subscriptions=["user.get.request"])
async def handle(self, payload, bus_proxy, raw_event):
if not isinstance(payload, GetUserRequest):
return
# 业务逻辑:查询用户
user = await db.get_user(payload.user_id)
# 构造响应负载:build_response 自动回传 session_id / request_id
response = build_response(
payload,
GetUserResponse,
success=user is not None,
error_msg=None if user else "User not found",
user_name=user.name if user else "",
email=user.email if user else "",
)
await bus_proxy.publish("user.get.response", response)
4. 客户端发起请求¶
客户端通过 request 函数发起调用,等待响应。
from infinity_bus.templates.request import request
# 假设已获得 EventBus.Proxy 实例(通常由总线注入或创建)
proxy = bus.proxy(source="UserServiceClient")
try:
resp = await request(
bus_proxy=proxy,
req_event="user.get.request",
req_data={"user_id": 123},
resp_event="user.get.response",
session_id=None, # 留空则自动生成新会话ID
timeout=10.0, # 超时时间(秒),None 表示无限等待
)
resp.raise_if_failed() # 检查 success 字段,失败时抛出 RuntimeError
print(f"User: {resp.user_name}, Email: {resp.email}")
except asyncio.TimeoutError:
print("请求超时")
except RuntimeError as e:
print(f"业务失败: {e}")
函数签名¶
async def request(
bus_proxy: EventBus.Proxy,
req_event: str,
req_data: Dict[str, Any],
resp_event: str,
session_id: Optional[str] = None,
timeout: Optional[float] = 60.0,
) -> ResponseProtocol
| 参数 | 类型 | 说明 |
|---|---|---|
bus_proxy |
EventBus.Proxy |
事件总线代理,用于发布请求事件。 |
req_event |
str |
请求事件名称(必须在注册表中存在且负载继承 RequestProtocol)。 |
req_data |
Dict[str, Any] |
请求负载数据,会自动注入 session_id 和 request_id。 |
resp_event |
str |
期望的响应事件名称(必须在注册表中存在且负载继承 ResponseProtocol)。 |
session_id |
Optional[str] |
会话ID,若为 None 则自动生成 UUID。 |
timeout |
Optional[float] |
等待响应的超时时间(秒),超时抛出 asyncio.TimeoutError。None 表示无限等待。 |
返回值:ResponseProtocol 实例(具体类型由响应事件声明的 payload_type 决定)。
build_response 辅助函数¶
服务端构造响应负载的辅助函数:自动回传请求的 session_id / request_id,其余字段由 kargs 提供。
def build_response(
request: RequestProtocol,
response_type: type[ResponseProtocol],
**kargs: Any,
) -> ResponseProtocol
| 参数 | 类型 | 说明 |
|---|---|---|
request |
RequestProtocol |
对应的请求负载(session_id / request_id 的事实来源)。 |
response_type |
type[ResponseProtocol] |
响应负载类。 |
kargs |
Any |
其余业务字段,直接交给响应模型的 model_validate。 |
返回值:response_type 的实例。
kargs中若携带冲突的session_id/request_id会被请求值强制覆盖(请求是唯一事实源),无需手动回传。ServiceHandler的自动响应构造内部使用同一个函数(见 handlers/service.md)。
异常说明¶
| 异常类型 | 触发条件 |
|---|---|
ValueError |
请求/响应事件未在注册表中注册。 |
TypeError |
事件负载不符合 RequestProtocol 或 ResponseProtocol 要求。 |
BusShuttingDown |
事件总线正在停止,无法发布新事件。 |
asyncio.TimeoutError |
在指定 timeout 内未收到匹配的响应。 |
asyncio.CancelledError |
外部取消了请求任务(此时临时处理器自动清理)。 |
RuntimeError |
调用 resp.raise_if_failed() 且响应中 success=False 时抛出。 |
内部机制¶
- 临时处理器:每次调用
request会在当前上下文动态注册一个OneShotHandler,监听resp_event,并在函数返回(无论成功、失败、取消)后自动注销,杜绝资源泄漏。 - 会话隔离:通过
session_id+request_id双重匹配,确保并发请求互不干扰。 - 类型安全:发布前校验事件声明的负载类型,响应时校验实际负载是否继承
ResponseProtocol,不一致则立即失败。
注意事项¶
- 响应处理器必须原样回传
session_id和request_id,否则客户端无法匹配,导致请求超时。 - 不要在
req_data中手动提供session_id或request_id,框架会强制覆盖以保证唯一性。 - 服务端应尽快发布响应,避免客户端长时间等待。对于耗时操作,可考虑先返回“已受理”状态,再通过独立事件推送结果。
- 超时时间的设定应略大于业务逻辑的最大预期耗时,但不宜过长以免资源占用。
- 与
EventBus配合使用时,请确保总线已启动,且相关事件声明均已注册。
完整示例¶
参见 tests/templates/request_test.py
其中包含了正常请求、超时、取消、类型错误等场景的测试用例,可作为使用参考。