返回系列

接口集成:为什么 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 分发处理。

前端或外部系统 Monitor / Client HTTP 控制面 start / stop / record / save Socket.IO 数据面 raw / filtered / peak / metrics BreathSystem 采集与处理

为什么这样选

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() 会补发最近快照,减少空白等待。

推荐调用流程

典型外部系统接入流程:

  1. 调 GET /health 或 GET /stream/status 检查状态。
  2. 调 POST /startReceive 启动接收。
  3. 连接 Socket.IO /breath 命名空间。
  4. 监听 breath 事件,并按 type 处理 raw、filtered、peak、valley、metrics、signal_quality。
  5. 需要记录时调用 /record/start、/scan/start、/scan/end、/record/end。
  6. 停止时调用 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 状态
StreamStatusResponsePydantic 响应模型/stream/status 结构稳定
socketio.AsyncServerSocket.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 实时消息组合起来。