接口集成:为什么 HTTP 做控制面,Socket.IO 做数据面
复盘 RespiraScope 的对外接口设计:启动、停止、记录和复算走 HTTP,实时 raw、filtered、peak、valley、metrics 走 Socket.IO。
背景问题
RespiraScope 的前端不是唯一消费者。外部业务系统也可能要启动接收、订阅实时数据、记录扫描片段、保存文件或复算历史波形。因此接口设计必须同时满足“控制命令可靠”和“实时数据高频推送”。
如果所有事情都走 HTTP,实时图表就会变成频繁轮询;如果所有事情都走 WebSocket,开始记录、错误响应、保存文件这些命令又会失去 HTTP 的清晰语义。项目最终把接口拆成控制面和数据面。
设计方案对比
| 方案 | 优点 | 问题 | 项目判断 |
|---|---|---|---|
| 全 HTTP 轮询 | 调试简单,curl 可测 | 高频数据浪费,请求多,延迟抖动 | 不适合实时波形 |
| 全 WebSocket | 实时性强 | 命令语义和错误结构需要自定义 | 控制面不够清晰 |
| Server-Sent Events | 单向推送简单 | 不适合双向控制,浏览器外生态有限 | 可选但不够全面 |
| HTTP + Socket.IO | 控制清晰,数据实时,客户端生态好 | 两套接口需要协调 | 当前采用 |
当前方案
HTTP 接口负责“做一次动作并返回状态”。典型接口有:
| 接口 | 作用 |
|---|---|
GET /health | 检查后端和配置 |
GET /runtime/config | 读取运行时配置 |
GET /stream/status | 查看接收、队列、记录、质量状态 |
POST /startReceive | 启动传感器接收和实时滤波 |
POST /stopReceive | 停止接收并清理实时上下文 |
POST /setRTFilterParams | 更新实时滤波参数 |
POST /record/start | 开始记录 |
POST /record/end | 结束记录并返回离线滤波结果 |
POST /applyFilter | 对历史 raw_data 做离线复算 |
Socket.IO 只负责实时推送。命名空间是 /breath,事件名统一是 breath,消息通过 type 区分:
{
"type": "filtered",
"data": [[1024, 187.6], [1025, 188.1]]
}
这种结构让前端和外部系统只监听一个事件,再按 type 分发处理。
为什么这样选
HTTP 的强项是请求和响应。比如 /record/start 如果前置条件不满足,后端可以返回 HTTP 400,并在 detail.message 里说明 Receive has not been started 或 No raw data has been received。这比在 WebSocket 上自定义错误事件更直观。
Socket.IO 的强项是实时广播。后端通过 client_ids 维护连接,send_socket_io_message() 对所有客户端 emit。同一份实时数据可以同时被 Monitor 和外部系统消费。新客户端连接时,send_recent_snapshot() 会补发最近快照,减少空白等待。
推荐调用流程
典型外部系统接入流程:
- 调
GET /health或GET /stream/status检查状态。 - 调
POST /startReceive启动接收。 - 连接 Socket.IO
/breath命名空间。 - 监听
breath事件,并按type处理raw、filtered、peak、valley、metrics、signal_quality。 - 需要记录时调用
/record/start、/scan/start、/scan/end、/record/end。 - 停止时调用
POST /stopReceive。
代价和边界
HTTP 与 Socket.IO 混合意味着客户端要维护两条通道。Monitor 前端用 fetch 处理控制命令,用 Socket.IO 处理实时消息。好处是清晰,代价是启动顺序要小心:通常先连接 socket,再调用 /startReceive,这样尽量不漏掉刚启动时的数据。
当前 Socket.IO 消息是内存广播,不提供持久化确认。它适合实时显示,不保证每个客户端都收到每个点。如果外部系统需要严格归档,应通过记录文件或额外持久化链路实现。
关键函数表
| 函数或类 | 作用 | 设计点 |
|---|---|---|
create_routes() | 注册 HTTP 路由 | 根据 mock 开关决定是否注册 /mock/* |
success_response() | 统一成功响应结构 | 新旧前端兼容,保留顶层字段 |
record_http_error() | 统一记录相关错误 | HTTP 400 带当前 record 状态 |
StreamStatusResponse | Pydantic 响应模型 | /stream/status 结构稳定 |
socketio.AsyncServer | Socket.IO 服务端 | WebSocket 传输实时数据 |
send_recent_snapshot() | 新连接补发快照 | 提高刷新后的可用性 |
send_socket_io_message() | 广播实时消息 | 所有客户端共享同一数据面 |
读源码建议
先读 http_service.py 的 register_core_routes(),理解每个 HTTP 接口如何调用 breath_system。再读 socket_io_service.py,看连接、断开、快照和广播。最后读 frontend-monitor/modules/socket.js,它展示了浏览器端如何把 HTTP 控制和 Socket.IO 实时消息组合起来。