websocket_server.py 9.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229
  1. import asyncio
  2. import logging
  3. import websockets
  4. from config.logger import setup_logging
  5. class SuppressInvalidHandshakeFilter(logging.Filter):
  6. """过滤掉无效握手错误日志(如HTTPS访问WS端口)"""
  7. def filter(self, record):
  8. msg = record.getMessage()
  9. suppress_keywords = [
  10. "opening handshake failed",
  11. "did not receive a valid HTTP request",
  12. "connection closed while reading HTTP request",
  13. "line without CRLF",
  14. ]
  15. return not any(keyword in msg for keyword in suppress_keywords)
  16. def _setup_websockets_logger():
  17. """配置 websockets 相关的所有 logger,过滤无效握手错误"""
  18. filter_instance = SuppressInvalidHandshakeFilter()
  19. for logger_name in ["websockets", "websockets.server", "websockets.client"]:
  20. logger = logging.getLogger(logger_name)
  21. logger.addFilter(filter_instance)
  22. _setup_websockets_logger()
  23. from core.connection import ConnectionHandler
  24. from config.config_loader import get_config_from_api_async
  25. from core.auth import AuthManager, AuthenticationError
  26. from core.utils.modules_initialize import initialize_modules
  27. from core.utils.util import check_vad_update, check_asr_update
  28. TAG = __name__
  29. class WebSocketServer:
  30. def __init__(self, config: dict):
  31. self.config = config
  32. self.logger = setup_logging()
  33. self.config_lock = asyncio.Lock()
  34. modules = initialize_modules(
  35. self.logger,
  36. self.config,
  37. "VAD" in self.config["selected_module"],
  38. "ASR" in self.config["selected_module"],
  39. "LLM" in self.config["selected_module"],
  40. False,
  41. "Memory" in self.config["selected_module"],
  42. "Intent" in self.config["selected_module"],
  43. )
  44. self._vad = modules["vad"] if "vad" in modules else None
  45. self._asr = modules["asr"] if "asr" in modules else None
  46. self._llm = modules["llm"] if "llm" in modules else None
  47. self._intent = modules["intent"] if "intent" in modules else None
  48. self._memory = modules["memory"] if "memory" in modules else None
  49. auth_config = self.config["server"].get("auth", {})
  50. self.auth_enable = auth_config.get("enabled", False)
  51. # 设备白名单
  52. self.allowed_devices = set(auth_config.get("allowed_devices", []))
  53. secret_key = self.config["server"]["auth_key"]
  54. expire_seconds = auth_config.get("expire_seconds", None)
  55. self.auth = AuthManager(secret_key=secret_key, expire_seconds=expire_seconds)
  56. async def start(self):
  57. server_config = self.config["server"]
  58. host = server_config.get("ip", "0.0.0.0")
  59. port = int(server_config.get("port", 8000))
  60. async with websockets.serve(
  61. self._handle_connection, host, port, process_request=self._http_response
  62. ):
  63. await asyncio.Future()
  64. async def _handle_connection(self, websocket: websockets.ServerConnection):
  65. headers = dict(websocket.request.headers)
  66. if headers.get("device-id", None) is None:
  67. # 尝试从 URL 的查询参数中获取 device-id
  68. from urllib.parse import parse_qs, urlparse
  69. # 从 WebSocket 请求中获取路径
  70. request_path = websocket.request.path
  71. if not request_path:
  72. self.logger.bind(tag=TAG).error("无法获取请求路径")
  73. await websocket.close()
  74. return
  75. parsed_url = urlparse(request_path)
  76. query_params = parse_qs(parsed_url.query)
  77. if "device-id" not in query_params:
  78. await websocket.send("端口正常,如需测试连接,请使用test_page.html")
  79. await websocket.close()
  80. return
  81. else:
  82. websocket.request.headers["device-id"] = query_params["device-id"][0]
  83. if "client-id" in query_params:
  84. websocket.request.headers["client-id"] = query_params["client-id"][0]
  85. if "authorization" in query_params:
  86. websocket.request.headers["authorization"] = query_params[
  87. "authorization"
  88. ][0]
  89. """处理新连接,每次创建独立的ConnectionHandler"""
  90. # 先认证,后建立连接
  91. try:
  92. await self._handle_auth(websocket)
  93. except AuthenticationError:
  94. await websocket.send("认证失败")
  95. await websocket.close()
  96. return
  97. # 创建ConnectionHandler时传入当前server实例
  98. handler = ConnectionHandler(
  99. self.config,
  100. self._vad,
  101. self._asr,
  102. self._llm,
  103. self._memory,
  104. self._intent,
  105. self, # 传入server实例
  106. )
  107. try:
  108. await handler.handle_connection(websocket)
  109. except Exception as e:
  110. self.logger.bind(tag=TAG).error(f"处理连接时出错: {e}")
  111. finally:
  112. # 强制关闭连接(如果还没有关闭的话)
  113. try:
  114. # 安全地检查WebSocket状态并关闭
  115. if hasattr(websocket, "closed") and not websocket.closed:
  116. await websocket.close()
  117. elif hasattr(websocket, "state") and websocket.state.name != "CLOSED":
  118. await websocket.close()
  119. else:
  120. # 如果没有closed属性,直接尝试关闭
  121. await websocket.close()
  122. except Exception as close_error:
  123. self.logger.bind(tag=TAG).error(
  124. f"服务器端强制关闭连接时出错: {close_error}"
  125. )
  126. async def _http_response(self, websocket, request_headers):
  127. # 检查是否为 WebSocket 升级请求
  128. connection_header = request_headers.headers.get("connection", "").lower()
  129. upgrade_header = request_headers.headers.get("upgrade", "").lower()
  130. if "upgrade" in connection_header and "websocket" in upgrade_header:
  131. # 如果是 WebSocket 请求,返回 None 允许握手继续
  132. return None
  133. else:
  134. # 如果是普通 HTTP 请求,返回 "server is running"
  135. return websocket.respond(200, "Server is running\n")
  136. async def update_config(self) -> bool:
  137. """更新服务器配置并重新初始化组件
  138. Returns:
  139. bool: 更新是否成功
  140. """
  141. try:
  142. async with self.config_lock:
  143. # 重新获取配置(使用异步版本)
  144. new_config = await get_config_from_api_async(self.config)
  145. if new_config is None:
  146. self.logger.bind(tag=TAG).error("获取新配置失败")
  147. return False
  148. self.logger.bind(tag=TAG).info(f"获取新配置成功")
  149. # 检查 VAD 和 ASR 类型是否需要更新
  150. update_vad = check_vad_update(self.config, new_config)
  151. update_asr = check_asr_update(self.config, new_config)
  152. self.logger.bind(tag=TAG).info(
  153. f"检查VAD和ASR类型是否需要更新: {update_vad} {update_asr}"
  154. )
  155. # 更新配置
  156. self.config = new_config
  157. # 重新初始化组件
  158. modules = initialize_modules(
  159. self.logger,
  160. new_config,
  161. update_vad,
  162. update_asr,
  163. "LLM" in new_config["selected_module"],
  164. False,
  165. "Memory" in new_config["selected_module"],
  166. "Intent" in new_config["selected_module"],
  167. )
  168. # 更新组件实例
  169. if "vad" in modules:
  170. self._vad = modules["vad"]
  171. if "asr" in modules:
  172. self._asr = modules["asr"]
  173. if "llm" in modules:
  174. self._llm = modules["llm"]
  175. if "intent" in modules:
  176. self._intent = modules["intent"]
  177. if "memory" in modules:
  178. self._memory = modules["memory"]
  179. self.logger.bind(tag=TAG).info(f"更新配置任务执行完毕")
  180. return True
  181. except Exception as e:
  182. self.logger.bind(tag=TAG).error(f"更新服务器配置失败: {str(e)}")
  183. return False
  184. async def _handle_auth(self, websocket: websockets.ServerConnection):
  185. # 先认证,后建立连接
  186. if self.auth_enable:
  187. headers = dict(websocket.request.headers)
  188. device_id = headers.get("device-id", None)
  189. client_id = headers.get("client-id", None)
  190. if self.allowed_devices and device_id in self.allowed_devices:
  191. # 如果属于白名单内的设备,不校验token,直接放行
  192. return
  193. else:
  194. # 否则校验token
  195. token = headers.get("authorization", "")
  196. if token.startswith("Bearer "):
  197. token = token[7:] # 移除'Bearer '前缀
  198. else:
  199. raise AuthenticationError("Missing or invalid Authorization header")
  200. # 进行认证
  201. auth_success = self.auth.verify_token(
  202. token, client_id=client_id, username=device_id
  203. )
  204. if not auth_success:
  205. raise AuthenticationError("Invalid token")