fix(ws): echo WebSocket subprotocol on accept to fix browser handshake

后端 ws_manager.connect/connect_employee 与 ws.py 调用未回显客户端协商的 Sec-WebSocket-Protocol (bearer.{token}),导致浏览器以 'Sent non-empty Sec-WebSocket-Protocol header but no response' 拒绝握手,H5/坐席 WS 实际一直走 3s 轮询兜底而非流式打字机。现回显 subprotocol 修复握手。验证:dev 真实浏览器 E2E 343 帧 typewriter;生产 docker cp 部署后日志显示 H5/坐席 WS 连接 [accepted] 且 连接建立。
This commit is contained in:
Simon
2026-07-09 09:16:43 +08:00
parent ba068d3642
commit bacd34c34d
2 changed files with 14 additions and 8 deletions
+4 -4
View File
@@ -132,8 +132,8 @@ async def websocket_endpoint(
# 认证通过,建立连接 # 认证通过,建立连接
# ====================================================================== # ======================================================================
# 注册连接(内部会调用 websocket.accept() # 注册连接(内部会调用 websocket.accept(),并回显协商的 subprotocol
await ws_manager.connect(agent_id, websocket) await ws_manager.connect(agent_id, websocket, subprotocol=subprotocol)
logger.info(f"坐席 WebSocket 连接已认证: agent_id={agent_id}") logger.info(f"坐席 WebSocket 连接已认证: agent_id={agent_id}")
try: try:
@@ -289,8 +289,8 @@ async def h5_websocket_endpoint(
# 认证通过,建立连接 # 认证通过,建立连接
# ====================================================================== # ======================================================================
# 注册员工连接(内部会调用 websocket.accept() # 注册员工连接(内部会调用 websocket.accept(),并回显协商的 subprotocol
await ws_manager.connect_employee(employee_id, websocket) await ws_manager.connect_employee(employee_id, websocket, subprotocol=subprotocol)
logger.info(f"H5员工 WebSocket 连接已认证: employee_id={employee_id}") logger.info(f"H5员工 WebSocket 连接已认证: employee_id={employee_id}")
try: try:
+10 -4
View File
@@ -56,7 +56,7 @@ class ConnectionManager:
# 坐席连接管理 # 坐席连接管理
# ========================================================================== # ==========================================================================
async def connect(self, agent_id: str, websocket: WebSocket) -> None: async def connect(self, agent_id: str, websocket: WebSocket, subprotocol: str = "") -> None:
"""接受坐席 WebSocket 握手并注册连接。 """接受坐席 WebSocket 握手并注册连接。
做什么:完成 WebSocket 握手(accept),然后将连接存入映射表 做什么:完成 WebSocket 握手(accept),然后将连接存入映射表
@@ -70,7 +70,10 @@ class ConnectionManager:
websocket: FastAPI WebSocket 对象 websocket: FastAPI WebSocket 对象
""" """
# 完成 WebSocket 握手(必须先 accept 才能收发消息) # 完成 WebSocket 握手(必须先 accept 才能收发消息)
await websocket.accept() # 回显客户端协商的 subprotocol(如 bearer.{token});浏览器在客户端主动
# 发送了 Sec-WebSocket-Protocol 时,要求服务端也必须回显,否则会以
# "Sent non-empty 'Sec-WebSocket-Protocol' header but no response" 拒绝握手
await websocket.accept(subprotocol=subprotocol if subprotocol else None)
# 如果该坐席已有连接,先关闭旧连接 # 如果该坐席已有连接,先关闭旧连接
# 场景:坐席刷新页面或重新登录,会产生新连接 # 场景:坐席刷新页面或重新登录,会产生新连接
@@ -163,7 +166,7 @@ class ConnectionManager:
# H5员工连接管理 # H5员工连接管理
# ========================================================================== # ==========================================================================
async def connect_employee(self, employee_id: str, websocket: WebSocket) -> None: async def connect_employee(self, employee_id: str, websocket: WebSocket, subprotocol: str = "") -> None:
"""接受H5员工 WebSocket 握手并注册连接。 """接受H5员工 WebSocket 握手并注册连接。
做什么:完成 WebSocket 握手(accept),然后将连接存入员工映射表 做什么:完成 WebSocket 握手(accept),然后将连接存入员工映射表
@@ -176,7 +179,10 @@ class ConnectionManager:
websocket: FastAPI WebSocket 对象 websocket: FastAPI WebSocket 对象
""" """
# 完成 WebSocket 握手 # 完成 WebSocket 握手
await websocket.accept() # 回显客户端协商的 subprotocol(如 bearer.{token});浏览器在客户端主动
# 发送了 Sec-WebSocket-Protocol 时,要求服务端也必须回显,否则会以
# "Sent non-empty 'Sec-WebSocket-Protocol' header but no response" 拒绝握手
await websocket.accept(subprotocol=subprotocol if subprotocol else None)
# 如果该员工已有连接,先关闭旧连接 # 如果该员工已有连接,先关闭旧连接
if employee_id in self.employee_connections: if employee_id in self.employee_connections: