From b37bf255d9360b7e2fac43384a6db205f41d0051 Mon Sep 17 00:00:00 2001 From: qing <1776177662@qq.com> Date: Mon, 3 Aug 2026 23:46:10 +0800 Subject: [PATCH] =?UTF-8?q?fix(libhttp):=20=E5=88=A0=E9=99=A4=E9=81=97?= =?UTF-8?q?=E7=95=99=20WebSocketServer/WebSocketClient=20=E5=B9=B6?= =?UTF-8?q?=E5=90=8C=E6=AD=A5=20CMakeLists=20=E4=B8=8E=20Makefile?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 2 +- .../libhttp_SECURITY_AUDIT_20260803.md | 10 +- libhttp/CMakeLists.txt | 115 ++---- libhttp/Makefile | 6 - libhttp/README.robot.md | 205 ++-------- libhttp/inc/us/WebSocketClient.hpp | 63 --- libhttp/inc/us/WebSocketClientBase.hpp | 61 --- libhttp/inc/us/WebSocketServer.hpp | 176 -------- libhttp/src/WebSocketClient.cpp | 386 ------------------ libhttp/src/WebSocketServer.cpp | 383 ----------------- 10 files changed, 85 insertions(+), 1322 deletions(-) delete mode 100644 libhttp/inc/us/WebSocketClient.hpp delete mode 100644 libhttp/inc/us/WebSocketClientBase.hpp delete mode 100644 libhttp/inc/us/WebSocketServer.hpp delete mode 100644 libhttp/src/WebSocketClient.cpp delete mode 100644 libhttp/src/WebSocketServer.cpp diff --git a/README.md b/README.md index 356769d..f96e029 100755 --- a/README.md +++ b/README.md @@ -168,7 +168,7 @@ | 库 | 说明 | |----|------| | [libth](libth/) | 线程库:`Thread`、`Cv_wait`(条件变量等待)、`Fsm`、`ITask` 任务接口 | -| [libhttp](libhttp/) | HTTP/WebSocket 库:`HttpServer`、`HttpClient`、`WebSocketServer/Client`(基于 Boost.Beast) | +| [libhttp](libhttp/) | HTTP/WebSocket 库:`HttpServer`(含 WebSocket 服务/代理)、`HttpClient`(含 WebSocket 客户端,基于 Boost.Beast) | | [libcrypt](libcrypt/) | 加密库:`AES_CBC`、`MyRSA`、`SHA256`、`Base64`、`Crypto_Basic` | | [libsqlite3](libsqlite3/) | SQLite3 C++ 封装(Header-only) | | [libtry](libtry/) | 嵌入式脚本解释器(~741 行 C),手写递归下降词法/语法/执行引擎,支持变量/数组/函数/控制流,宽字符(中文标识符),用于嵌入式操作界面 shell | diff --git a/docs/security/libhttp_SECURITY_AUDIT_20260803.md b/docs/security/libhttp_SECURITY_AUDIT_20260803.md index a0fd93c..f5c2edc 100644 --- a/docs/security/libhttp_SECURITY_AUDIT_20260803.md +++ b/docs/security/libhttp_SECURITY_AUDIT_20260803.md @@ -13,7 +13,7 @@ | P0 | 0 | 0 | 0 | 0 | | P1 | 6 | 0 | 0 | 6 | | P2 | 4 | 0 | 0 | 4 | -| **合计** | **10** | **0** | **0** | **10** | +| **合计** | **10** | **2** | **0** | **8** | --- @@ -47,8 +47,8 @@ | # | 问题 | 状态 | 说明 | |---|------|------|------| -| P3-1 | 遗留 WebSocketServer/WebSocketClient 未构建 | ❌ 未修复 | `WebSocketServer.cpp`/`WebSocketClient.cpp` 仅被 CMakeLists.txt 引用,**Makefile 未编译**,与 `HttpClient` 混用。其实现存在缺陷(无超时、`disconnectClient` 不真正关闭连接 L344-352、`onRead` 对错误路径未关闭),建议删除或维护 | -| P3-2 | CMakeLists.txt 过时 | ❌ 未修复 | CMakeLists 仍引用旧文件路径(`inc/us/HttpServer.hpp` 等)与旧拆分前源码,与 Makefile 实际构建不一致,易误导 | +| P3-1 | 遗留 WebSocketServer/WebSocketClient 未构建 | ✅ 已修复 | 已删除 `WebSocketServer.cpp`/`WebSocketClient.cpp`/`WebSocketServer.hpp`/`WebSocketClient.hpp`/`WebSocketClientBase.hpp`(未构建、与 `HttpClient` 别名同名冲突、有缺陷),并清理 Makefile/README 引用。WebSocket 能力由 `HttpServer` + `HttpClient` 提供 | +| P3-2 | CMakeLists.txt 过时 | ✅ 已修复 | CMakeLists 已重写对齐 Makefile:`libus_http.so`(6 个服务端 TU)+ `libus_httpclient.so`(HttpClient),移除遗留文件引用与错误库名 | --- @@ -83,8 +83,8 @@ - [ ] P2-5 代理后端读写超时 + 空闲保活 - [ ] P2-6 最大连接数限制 - [ ] P2-2 TLS 校验可配置(至少提供 `verify_peer` 选项) -- [ ] P3-1 删除或维护遗留 WebSocketServer/WebSocketClient -- [ ] P3-2 同步 CMakeLists.txt 与 Makefile +- [x] P3-1 删除或维护遗留 WebSocketServer/WebSocketClient → ✅ 已删除 +- [x] P3-2 同步 CMakeLists.txt 与 Makefile → ✅ 已重写对齐 --- diff --git a/libhttp/CMakeLists.txt b/libhttp/CMakeLists.txt index cd71b11..81c8dd3 100644 --- a/libhttp/CMakeLists.txt +++ b/libhttp/CMakeLists.txt @@ -13,10 +13,9 @@ else() set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -O3 -pipe -Wall") endif() -# Set library names -set(SHARED_LIB_NAME "us_http") -set(STATIC_LIB_NAME "us_http_static") -set(FRAMEWORK_NAME "us") +# Library names (与 Makefile 保持一致) +set(SERVER_LIB_NAME "us_http") +set(CLIENT_LIB_NAME "us_httpclient") # ===== Boost Library Configuration (Header-Only) ===== if(DEFINED ENV{BOOST_ROOT}) @@ -41,86 +40,66 @@ include_directories(${Boost_INCLUDE_DIR}) set(INCLUDE_DIR "${CMAKE_CURRENT_SOURCE_DIR}/inc") include_directories(${INCLUDE_DIR}) -# Source files -set(SOURCES +# Lowest-path OpenSSL/libcurl (macOS homebrew) — 可被环境覆盖 +set(EXTERNAL_LIB_DIR "/opt/homebrew/lib") +set(EXTERNAL_INC_DIR "/opt/homebrew/include") +include_directories(${EXTERNAL_INC_DIR}) + +# Server sources (HttpServer 拆分后的 6 个 TU) +set(SERVER_SOURCES src/HttpServer.cpp - src/WebSocketServer.cpp - src/WebSocketClient.cpp + src/HttpServer_Listener.cpp + src/HttpServer_Routes.cpp + src/HttpServer_Request.cpp + src/HttpServer_Ws.cpp + src/HttpServer_WsSession.cpp ) -# Header files list -set(HEADERS - inc/${FRAMEWORK_NAME}/HttpServer.hpp - inc/${FRAMEWORK_NAME}/HttpClient.hpp - inc/${FRAMEWORK_NAME}/WebSocketServer.hpp - inc/${FRAMEWORK_NAME}/WebSocketClient.hpp +# Client sources +set(CLIENT_SOURCES + src/HttpClient.cpp ) -# Create shared library (DLL) -add_library(${SHARED_LIB_NAME} SHARED ${SOURCES}) +# Server library: libus_http.so (HTTP + WebSocket server + WS proxy, 不依赖 curl) +add_library(${SERVER_LIB_NAME} SHARED ${SERVER_SOURCES}) +target_link_libraries(${SERVER_LIB_NAME} + ${EXTERNAL_LIB_DIR}/libssl.a + ${EXTERNAL_LIB_DIR}/libcrypto.a + pthread +) -# Create static library (LIB) -add_library(${STATIC_LIB_NAME} STATIC ${SOURCES}) - -# Set properties for shared library -if(WIN32) - target_link_libraries(${SHARED_LIB_NAME} - ws2_32 - ) -else() - target_link_libraries(${SHARED_LIB_NAME} - pthread - ) -endif() +# Client library: libus_httpclient.so (HttpClient, 依赖 curl) +add_library(${CLIENT_LIB_NAME} SHARED ${CLIENT_SOURCES}) +target_link_libraries(${CLIENT_LIB_NAME} + ${EXTERNAL_LIB_DIR}/libssl.a + ${EXTERNAL_LIB_DIR}/libcrypto.a + ${EXTERNAL_LIB_DIR}/libcurl.a + pthread +) # Set output directories -set_target_properties(${SHARED_LIB_NAME} ${STATIC_LIB_NAME} PROPERTIES +set_target_properties(${SERVER_LIB_NAME} ${CLIENT_LIB_NAME} PROPERTIES LIBRARY_OUTPUT_DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}/build" RUNTIME_OUTPUT_DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}/build" ARCHIVE_OUTPUT_DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}/build" ) -# Additional properties for Windows -if(WIN32) - set_target_properties(${SHARED_LIB_NAME} PROPERTIES - PREFIX "" - SUFFIX ".dll" - ) - set_target_properties(${STATIC_LIB_NAME} PROPERTIES - PREFIX "" - SUFFIX ".lib" - ) -else() - set_target_properties(${SHARED_LIB_NAME} PROPERTIES - PREFIX "lib" - SUFFIX ".so" - ) - set_target_properties(${STATIC_LIB_NAME} PROPERTIES - PREFIX "lib" - SUFFIX ".a" - ) -endif() - -# Position independent code for static library on Unix -if(UNIX AND NOT APPLE) - set_target_properties(${STATIC_LIB_NAME} PROPERTIES - POSITION_INDEPENDENT_CODE ON - ) - set_target_properties(${SHARED_LIB_NAME} PROPERTIES - POSITION_INDEPENDENT_CODE ON - ) -endif() - # Install targets -install(TARGETS ${SHARED_LIB_NAME} ${STATIC_LIB_NAME} +install(TARGETS ${SERVER_LIB_NAME} ${CLIENT_LIB_NAME} LIBRARY DESTINATION lib RUNTIME DESTINATION bin ARCHIVE DESTINATION lib ) # Install headers -install(FILES ${HEADERS} - DESTINATION include/${FRAMEWORK_NAME} +install(FILES + inc/us/HttpServer.hpp + inc/us/HttpClient.hpp + DESTINATION include/us +) +install(FILES + inc/us/detail/HttpServerImpl.hpp + DESTINATION include/us/detail ) # Clean target @@ -133,13 +112,9 @@ add_custom_target(clear message(STATUS "========================================") message(STATUS "Project: ${PROJECT_NAME}") message(STATUS "Version: ${PROJECT_VERSION}") -message(STATUS "Shared library: ${SHARED_LIB_NAME}") -message(STATUS "Static library: ${STATIC_LIB_NAME}") +message(STATUS "Server library: ${SERVER_LIB_NAME}") +message(STATUS "Client library: ${CLIENT_LIB_NAME}") message(STATUS "Build type: ${CMAKE_BUILD_TYPE}") message(STATUS "Install prefix: ${CMAKE_INSTALL_PREFIX}") message(STATUS "Compiler: ${CMAKE_CXX_COMPILER_ID}") -if(MSVC) - message(STATUS "MSVC version: ${MSVC_VERSION}") -endif() -message(STATUS "BOOST_ROOT: ${BOOST_ROOT}") message(STATUS "========================================") \ No newline at end of file diff --git a/libhttp/Makefile b/libhttp/Makefile index eaaa3ec..786ceef 100644 --- a/libhttp/Makefile +++ b/libhttp/Makefile @@ -62,9 +62,6 @@ install: all @mkdir -vp $(INCLU_TDIR)/$(FRAMEWORK_NAME)/detail @cp -vf $(INCLU_SDIR)/$(FRAMEWORK_NAME)/HttpServer.hpp $(INCLU_TDIR)/$(FRAMEWORK_NAME)/ @cp -vf $(INCLU_SDIR)/$(FRAMEWORK_NAME)/HttpClient.hpp $(INCLU_TDIR)/$(FRAMEWORK_NAME)/ - @cp -vf $(INCLU_SDIR)/$(FRAMEWORK_NAME)/WebSocketServer.hpp $(INCLU_TDIR)/$(FRAMEWORK_NAME)/ - @cp -vf $(INCLU_SDIR)/$(FRAMEWORK_NAME)/WebSocketClient.hpp $(INCLU_TDIR)/$(FRAMEWORK_NAME)/ - @cp -vf $(INCLU_SDIR)/$(FRAMEWORK_NAME)/WebSocketClientBase.hpp $(INCLU_TDIR)/$(FRAMEWORK_NAME)/ @cp -vf $(INCLU_SDIR)/$(FRAMEWORK_NAME)/detail/HttpServerImpl.hpp $(INCLU_TDIR)/$(FRAMEWORK_NAME)/detail/ @cp -vf $(BUILD_TDIR)/$(LIB_NAME).so $(INSTL_TDIR)/ @cp -vf $(BUILD_TDIR)/$(LIBC_NAME).so $(INSTL_TDIR)/ @@ -74,9 +71,6 @@ uninstall: @echo "uninstall UniServe_libhttp..." @rm -vf $(INCLU_TDIR)/$(FRAMEWORK_NAME)/HttpServer.hpp @rm -vf $(INCLU_TDIR)/$(FRAMEWORK_NAME)/HttpClient.hpp - @rm -vf $(INCLU_TDIR)/$(FRAMEWORK_NAME)/WebSocketServer.hpp - @rm -vf $(INCLU_TDIR)/$(FRAMEWORK_NAME)/WebSocketClient.hpp - @rm -vf $(INCLU_TDIR)/$(FRAMEWORK_NAME)/WebSocketClientBase.hpp @rm -vf $(INCLU_TDIR)/$(FRAMEWORK_NAME)/detail/HttpServerImpl.hpp @rm -vf $(INSTL_TDIR)/$(LIB_NAME).so @rm -vf $(INSTL_TDIR)/$(LIBC_NAME).so diff --git a/libhttp/README.robot.md b/libhttp/README.robot.md index cf6ef54..a6c48a0 100644 --- a/libhttp/README.robot.md +++ b/libhttp/README.robot.md @@ -30,13 +30,15 @@ libhttp/ ├── inc/us/ │ ├── HttpClient.hpp # HTTP 客户端 (header-only) │ ├── HttpServer.hpp # HTTP/HTTPS 服务器声明 -│ ├── WebSocketClient.hpp # WebSocket 客户端声明 -│ ├── WebSocketClientBase.hpp # WebSocket 客户端抽象基类 -│ └── WebSocketServer.hpp # WebSocket 服务器声明 +│ └── detail/ +│ └── HttpServerImpl.hpp # Session/Listener 内部共享定义 └── src/ - ├── HttpServer.cpp # HTTP/HTTPS 服务器实现 - ├── WebSocketClient.cpp # WebSocket 客户端实现 - └── WebSocketServer.cpp # WebSocket 服务器实现 + ├── HttpServer.cpp # 核心生命周期/SSL 初始化 + ├── HttpServer_Listener.cpp # TCP 连接接受器 + ├── HttpServer_Routes.cpp # 路由注册/匹配/通配符 + ├── HttpServer_Request.cpp # 请求分发/静态文件/CORS/HTTP 读写 + ├── HttpServer_Ws.cpp # WebSocket 路由注册/客户端管理 + └── HttpServer_WsSession.cpp# WebSocket 会话/代理桥接 ``` --- @@ -71,7 +73,7 @@ make install make uninstall ``` -> **注意**:Makefile 第 17 行默认只链接 `HttpServer.o`。如需 WebSocket 支持,需取消注释 `WebSocketServer.o` 和 `WebSocketClient.o`。 +> **注意**:HttpServer 拆分为 6 个 TU,统一编译进 `libus_http.so`;WebSocket 能力已内置于 HttpServer(单端口同时服务 HTTP + WebSocket),客户端通过 `HttpClient` 提供,无独立 WebSocketServer/WebSocketClient 类。 ### 4.2 CMake @@ -82,7 +84,7 @@ cmake --build . cmake --install . --prefix /usr/local ``` -CMake 会同时产出 `libus_http.so` (动态) 和 `libus_http_static.a` (静态)。 +CMake 会同时产出 `libus_http.so` (服务端) 和 `libus_httpclient.so` (客户端)。 ### 4.3 一键脚本 @@ -257,130 +259,9 @@ void delete_stream_sync(const std::string& url, std::function` -**实现**: `src/WebSocketServer.cpp` - -#### 5.3.1 回调类型 - -```cpp -using WebSocketMessageHandler = std::function sendCallback)>; - -using WebSocketConnectHandler = std::function; - -using WebSocketDisconnectHandler = std::function; -``` - -#### 5.3.2 公共接口 - -```cpp -WebSocketServer(unsigned short port, unsigned int threads = 2); -~WebSocketServer(); - -void start(); -void stop(); -bool isRunning() const; -unsigned short getPort() const; - -// 事件注册 -void onMessage(const std::string& path, WebSocketMessageHandler handler); -void onConnect(WebSocketConnectHandler handler); -void onDisconnect(WebSocketDisconnectHandler handler); - -// 消息/客户端管理 -bool sendToClient(const std::string& clientId, const std::string& message); -size_t broadcast(const std::string& message); -void disconnectClient(const std::string& clientId); -size_t getClientCount() const; -``` - -**特点**: -- PIMPL 模式:内部实现封装于 `Impl` 结构体 -- 每个 `Session` 包含一个发送队列 (`std::deque`),保证消息顺序 -- 客户端 ID 由全局原子计数器自动生成,格式 `"client_N"` -- 线程安全:客户端列表受 `clientsMutex_` 保护 -- 消息处理器支持路径路由(按 `path` 键索引) - -**内部架构**: - -``` -WebSocketServer -├── Impl -│ ├── io_context -│ ├── Listener (内部类) → tcp::acceptor -│ └── threads_ -└── Session (内部类,enable_shared_from_this) - ├── websocket::stream - ├── sendQueue_ (std::deque) - └── 读取循环 → onMessage 回调 → sendCallback 写回 -``` - ---- - -### 5.4 WebSocketClient — WebSocket 客户端 - -**头文件**: ``, `` -**实现**: `src/WebSocketClient.cpp` - -#### 5.4.1 抽象基类 `WebSocketClientBase` - -定义统一接口: - -```cpp -using MessageCallback = std::function; -using ErrorCallback = std::function; -using ConnectCallback = std::function; -using CloseCallback = std::function; - -// 回调设置 -virtual void setMessageCallback(MessageCallback) = 0; -virtual void setErrorCallback(ErrorCallback) = 0; -virtual void setConnectCallback(ConnectCallback) = 0; -virtual void setCloseCallback(CloseCallback) = 0; - -// 连接管理 -virtual bool connect(int timeout_ms = 5000) = 0; -virtual void disconnect() = 0; -virtual bool isConnected() const = 0; - -// 消息发送 -virtual bool send(const std::string& message) = 0; -virtual bool sendBinary(const void* data, size_t size) = 0; - -// 配置 -virtual void setAutoReconnect(bool enable, int interval_ms = 3000) = 0; -virtual void setHeartbeatInterval(int interval_ms = 30000) = 0; - -// SSL 相关(纯虚函数,非 SSL 版本忽略参数) -virtual bool isSecure() const = 0; -virtual void setVerifyCertificate(bool verify) = 0; -virtual void setCertificateFile(const std::string& cert_file) = 0; -virtual void setPrivateKeyFile(const std::string& key_file) = 0; -virtual void setCertificateAuthorityFile(const std::string& ca_file) = 0; -``` - -#### 5.4.2 公共接口 - -```cpp -WebSocketClient(const std::string& host, const std::string& port, - const std::string& path = "/"); -~WebSocketClient(); -``` - -**特点**: -- PIMPL 模式:`Impl` 结构体封装所有 Boost 对象和状态 -- 构造时自动启动 IO 线程 -- 异步连接流程:`resolve → connect → WebSocket handshake` -- 发送队列受 `std::mutex` 和 `std::condition_variable` 保护 -- 自动重连:`onError` 或 `onClose` 中若 `auto_reconnect=true` 则自动发起重连 -- 非 SSL 版本:`isSecure()` 返回 `false`,SSL 配置方法为空实现 -- `sendBinary` 将二进制数据包装为 `std::string` 后调用 `send` +WebSocket 能力已内置于 `HttpServer`(单端口同时服务 HTTP + WebSocket),通过 `ws()`/`ws_proxy()` 注册路由;客户端通过 `HttpClient` 的 `ws_*` 方法提供(`using WebSocketClient = HttpClient`)。原独立 `WebSocketServer`/`WebSocketClient` 类已删除(未构建的历史遗留,见安全审计 P3-1)。 --- @@ -476,45 +357,36 @@ auto& headers = client.get_response_headers(); ### 6.5 WebSocket 服务器 ```cpp -pmc::net::WebSocketServer ws_server(9000, 2); +pmc::net::HttpServer server("0.0.0.0", 9000, 4); -ws_server.onConnect([](const std::string& clientId, const std::string& addr) { +server.onWsConnect([](const std::string& clientId, const std::string& addr) { std::cout << "Client connected: " << clientId << " from " << addr << std::endl; }); -ws_server.onMessage("/chat", [&ws_server](const std::string& clientId, - const std::string& message, - auto sendCallback) { +server.ws("/chat", [](const std::string& clientId, + const std::string& message, + auto sendCallback) { std::cout << "[" << clientId << "]: " << message << std::endl; - ws_server.broadcast(clientId + ": " + message); + sendCallback("echo: " + message); }); -ws_server.onDisconnect([](const std::string& clientId) { +server.onWsDisconnect([](const std::string& clientId) { std::cout << "Client disconnected: " << clientId << std::endl; }); -ws_server.start(); +server.start(); ``` ### 6.6 WebSocket 客户端 ```cpp -pmc::net::WebSocketClient ws_client("localhost", "9000", "/chat"); +pmc::net::HttpClient ws_client; -ws_client.setConnectCallback([](bool success) { - std::cout << (success ? "Connected!" : "Failed!") << std::endl; -}); - -ws_client.setMessageCallback([](const std::string& msg) { - std::cout << "Received: " << msg << std::endl; -}); - -ws_client.setAutoReconnect(true, 3000); -ws_client.connect(); - -// 阻塞主线程保持连接 -while (ws_client.isConnected()) { - std::this_thread::sleep_for(std::chrono::seconds(1)); +if (ws_client.ws_connect("ws://localhost:9000/chat")) { + ws_client.ws_send("hello"); + std::string reply = ws_client.ws_recv(); + std::cout << "Received: " << reply << std::endl; + ws_client.ws_close(); } ``` @@ -548,22 +420,15 @@ while (ws_client.isConnected()) { ### 8.1 线程模型 - **HttpServer**: 每个工作线程独立运行 `io_context::run()`,所有异步操作通过 `strand` 序列化,同一 Session 的读写不会并发冲突 -- **WebSocketServer**: 同 HttpServer,工作线程池跑 `io_context` -- **WebSocketClient**: 单 IO 线程,所有异步回调在该线程中执行 -- **HttpClient**: 同步阻塞,无额外线程 +- **HttpClient**: 同步阻塞,无额外线程(HTTP 经 libcurl;WebSocket 经 Beast) -### 8.2 PIMPL 模式 - -`WebSocketClient` 和 `WebSocketServer` 使用 PIMPL 隐藏 Boost 依赖,保持头文件轻量。 - -### 8.3 会话管理 +### 8.2 会话管理 - `HttpServer` 通过 `weak_ptr` 跟踪所有活跃会话,`stop()` 时遍历清理 -- `WebSocketServer` 通过 `unordered_map>` + mutex 管理 -### 8.4 发送队列 +### 8.3 发送队列 -`WebSocketServer::Session` 和 `WebSocketClient::Impl` 均采用 `std::deque` 消息队列,确保消息按序发送且不会因并发写入冲突。 +`HttpServer::Session` 的 `ws_send_queue_` 与代理队列 (`proxy_to_backend_queue_`/`proxy_to_client_queue_`) 采用 `std::deque` 消息队列,确保消息按序发送且不会因并发写入冲突。 --- @@ -596,11 +461,9 @@ micro_servers/ ## 11. 已知限制 -1. **Makefile 默认不编译 WebSocket**:第 17 行仅链接 `HttpServer.o`,需手动启用 -2. **WebSocketClient 无 SSL 实现**:当前仅有非加密版本;SSL 接口已声明但为空实现 -3. **无 HTTP/2 支持**:仅 HTTP/1.1 -4. **静态文件 MIME 有限**:仅支持 6 种常见类型 -5. **无请求体大小限制**:依赖 Boost.Beast 默认配置 -6. **HttpClient 仅同步**:无异步 HTTP 客户端实现 +1. **无 HTTP/2 支持**:仅 HTTP/1.1 +2. **静态文件 MIME 有限**:仅支持 6 种常见类型 +3. **无请求体大小限制**:依赖 Boost.Beast 默认配置(见安全审计 P1-1) +4. **HttpClient 仅同步**:无异步 HTTP 客户端实现 ``` diff --git a/libhttp/inc/us/WebSocketClient.hpp b/libhttp/inc/us/WebSocketClient.hpp deleted file mode 100644 index 3e3c253..0000000 --- a/libhttp/inc/us/WebSocketClient.hpp +++ /dev/null @@ -1,63 +0,0 @@ -#ifndef PMC_NET_WEBSOCKET_CLIENT_HPP -#define PMC_NET_WEBSOCKET_CLIENT_HPP - -#include "WebSocketClientBase.hpp" - -namespace pmc { -namespace net { - -/** - * @brief Asynchronous WebSocket client based on Boost.Beast (non-SSL version) - */ -class WebSocketClient : public WebSocketClientBase { -public: - /** - * @brief Constructor - * @param host Server hostname or IP address - * @param port Server port number - * @param path WebSocket path (default "/") - */ - WebSocketClient(const std::string& host, const std::string& port, const std::string& path = "/"); - - /** - * @brief Destructor - */ - ~WebSocketClient(); - - // Implement WebSocketClientBase interface - void setMessageCallback(MessageCallback callback) override; - void setErrorCallback(ErrorCallback callback) override; - void setConnectCallback(ConnectCallback callback) override; - void setCloseCallback(CloseCallback callback) override; - - bool connect(int timeout_ms = 5000) override; - void disconnect() override; - bool isConnected() const override; - - bool send(const std::string& message) override; - bool sendBinary(const void* data, size_t size) override; - - void setAutoReconnect(bool enable, int interval_ms = 3000) override; - void setHeartbeatInterval(int interval_ms = 30000) override; - - std::string getHost() const override; - std::string getPort() const override; - std::string getPath() const override; - - // SSL/TLS related (non-SSL version returns default values) - bool isSecure() const override { return false; } - void setVerifyCertificate(bool verify) override { (void)verify;/* Ignored */ } - void setCertificateFile(const std::string& cert_file) override { (void)cert_file; /* Ignored */ } - void setPrivateKeyFile(const std::string& key_file) override { (void)key_file;/* Ignored */ } - void setCertificateAuthorityFile(const std::string& ca_file) override { (void)ca_file;/* Ignored */ } - -private: - // Forward declaration of implementation class - class Impl; - std::unique_ptr impl_; -}; - -} // namespace net -} // namespace pmc - -#endif // PMC_NET_WEBSOCKET_CLIENT_HPP \ No newline at end of file diff --git a/libhttp/inc/us/WebSocketClientBase.hpp b/libhttp/inc/us/WebSocketClientBase.hpp deleted file mode 100644 index f2eb94f..0000000 --- a/libhttp/inc/us/WebSocketClientBase.hpp +++ /dev/null @@ -1,61 +0,0 @@ -#ifndef PMC_NET_WEBSOCKET_CLIENT_BASE_HPP -#define PMC_NET_WEBSOCKET_CLIENT_BASE_HPP - -#include -#include -#include - -namespace pmc { -namespace net { - -/** - * @brief WebSocket client base class - * - * Defines the common interface for all WebSocket clients - */ -class WebSocketClientBase { -public: - // Callback function types - using MessageCallback = std::function; - using ErrorCallback = std::function; - using ConnectCallback = std::function; - using CloseCallback = std::function; - - virtual ~WebSocketClientBase() = default; - - // Callback setters - virtual void setMessageCallback(MessageCallback callback) = 0; - virtual void setErrorCallback(ErrorCallback callback) = 0; - virtual void setConnectCallback(ConnectCallback callback) = 0; - virtual void setCloseCallback(CloseCallback callback) = 0; - - // Connection management - virtual bool connect(int timeout_ms = 5000) = 0; - virtual void disconnect() = 0; - virtual bool isConnected() const = 0; - - // Message sending - virtual bool send(const std::string& message) = 0; - virtual bool sendBinary(const void* data, size_t size) = 0; - - // Configuration options - virtual void setAutoReconnect(bool enable, int interval_ms = 3000) = 0; - virtual void setHeartbeatInterval(int interval_ms = 30000) = 0; - - // Information retrieval - virtual std::string getHost() const = 0; - virtual std::string getPort() const = 0; - virtual std::string getPath() const = 0; - - // SSL/TLS related - virtual bool isSecure() const = 0; - virtual void setVerifyCertificate(bool verify) = 0; - virtual void setCertificateFile(const std::string& cert_file) = 0; - virtual void setPrivateKeyFile(const std::string& key_file) = 0; - virtual void setCertificateAuthorityFile(const std::string& ca_file) = 0; -}; - -} // namespace net -} // namespace pmc - -#endif // PMC_NET_WEBSOCKET_CLIENT_BASE_HPP \ No newline at end of file diff --git a/libhttp/inc/us/WebSocketServer.hpp b/libhttp/inc/us/WebSocketServer.hpp deleted file mode 100644 index a05805f..0000000 --- a/libhttp/inc/us/WebSocketServer.hpp +++ /dev/null @@ -1,176 +0,0 @@ -// WebSocketServer.hpp - WebSocket server header file -// Based on Boost.Beast and Boost.Asio - -#ifndef PMC_NET_WEBSOCKETSERVER_HPP -#define PMC_NET_WEBSOCKETSERVER_HPP - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -namespace pmc::net { - -/** - * @brief WebSocket connection handler type definition - * - * Handles message sending and receiving after WebSocket connection is established - */ -using WebSocketMessageHandler = std::function sendCallback)>; - -/** - * @brief WebSocket connection callback type definition - */ -using WebSocketConnectHandler = std::function; - -/** - * @brief WebSocket disconnection callback type definition - */ -using WebSocketDisconnectHandler = std::function; - -/** - * @brief WebSocket server class - * - * WebSocket server implemented based on Boost.Beast and Boost.Asio - */ -class WebSocketServer { -public: - /** - * @brief Constructor - * @param port Listening port - * @param threads Number of threads (default 2) - */ - WebSocketServer(unsigned short port, unsigned int threads = 2); - - /** - * @brief Destructor - */ - ~WebSocketServer(); - - /** - * @brief Start the server - */ - void start(); - - /** - * @brief Stop the server - */ - void stop(); - - /** - * @brief Register message handler - * @param path Path (for routing) - * @param handler Message handler - */ - void onMessage(const std::string& path, WebSocketMessageHandler handler); - - /** - * @brief Register connection handler - * @param handler Connection handler - */ - void onConnect(WebSocketConnectHandler handler); - - /** - * @brief Register disconnection handler - * @param handler Disconnection handler - */ - void onDisconnect(WebSocketDisconnectHandler handler); - - /** - * @brief Send message to a specific client - * @param clientId Client ID - * @param message Message content - * @return Whether sending was successful - */ - bool sendToClient(const std::string& clientId, const std::string& message); - - /** - * @brief Broadcast message to all clients - * @param message Message content - * @return Number of clients that successfully received the message - */ - size_t broadcast(const std::string& message); - - /** - * @brief Disconnect a specific client - * @param clientId Client ID - */ - void disconnectClient(const std::string& clientId); - - /** - * @brief Get the number of currently connected clients - * @return Number of clients - */ - size_t getClientCount() const; - - /** - * @brief Check if the server is running - * @return Running status - */ - bool isRunning() const { return running_; } - - /** - * @brief Get the server listening port - * @return Port number - */ - unsigned short getPort() const { return port_; } - -private: - // Internal Session class - class Session; - - // Internal Listener class - class Listener; - - // Internal implementation structure - struct Impl { - boost::asio::io_context ioc_; - std::shared_ptr listener_; - std::vector threads_; - }; - - // Server configuration - unsigned short port_; - unsigned int threads_; - std::atomic running_{false}; - - // Internal implementation - std::unique_ptr impl_; - - // Generate client ID - std::string generateClientId() const; - - // Add client - void addClient(const std::string& clientId, std::shared_ptr session); - - // Remove client - void removeClient(const std::string& clientId); - - // Get client session - std::shared_ptr getClient(const std::string& clientId); - - // Callback functions - std::unordered_map messageHandlers_; - WebSocketConnectHandler connectHandler_; - WebSocketDisconnectHandler disconnectHandler_; - - // Client management - mutable std::mutex clientsMutex_; - std::unordered_map> clients_; -}; - -} // namespace pmc::net - -#endif // PMC_NET_WEBSOCKETSERVER_HPP \ No newline at end of file diff --git a/libhttp/src/WebSocketClient.cpp b/libhttp/src/WebSocketClient.cpp deleted file mode 100644 index e2f7f63..0000000 --- a/libhttp/src/WebSocketClient.cpp +++ /dev/null @@ -1,386 +0,0 @@ -#include "us/WebSocketClient.hpp" -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -namespace pmc { -namespace net { - -namespace beast = boost::beast; -namespace asio = boost::asio; -using tcp = asio::ip::tcp; - -// Internal implementation structure -struct WebSocketClient::Impl { - // Configuration parameters - std::string host; - std::string port; - std::string path; - - // Boost objects - asio::io_context ioc; - tcp::resolver resolver; - beast::websocket::stream ws; - - // Thread management - std::thread io_thread; - std::atomic running{false}; - - // Callback functions - WebSocketClientBase::MessageCallback message_callback; - WebSocketClientBase::ErrorCallback error_callback; - WebSocketClientBase::ConnectCallback connect_callback; - WebSocketClientBase::CloseCallback close_callback; - - // Message queue - std::queue send_queue; - std::mutex queue_mutex; - std::condition_variable queue_cv; - std::atomic sending{false}; - - // Status flags - std::atomic connected{false}; - std::atomic connecting{false}; - std::atomic stopping{false}; - - // Configuration options - bool auto_reconnect{false}; - int reconnect_interval_ms{3000}; - int heartbeat_interval_ms{30000}; - - // Constructor - Impl(const std::string& host, const std::string& port, const std::string& path) - : host(host) - , port(port) - , path(path) - , resolver(asio::make_strand(ioc)) - , ws(asio::make_strand(ioc)) - { - } - - // Destructor - ~Impl() { - stop(); - } - - // Start IO thread - void start() { - if (!running) { - running = true; - io_thread = std::thread([this]() { - while (running) { - try { - ioc.run(); - break; - } catch (const std::exception& e) { - std::cerr << "IO context exception: " << e.what() << std::endl; - } - } - }); - } - } - - // Stop IO thread - void stop() { - running = false; - stopping = true; - - if (io_thread.joinable()) { - ioc.stop(); - io_thread.join(); - } - } - - // Perform connection - void doConnect() { - if (connecting || connected) { - return; - } - - connecting = true; - connected = false; - - // Resolve hostname - resolver.async_resolve(host, port, - [this](beast::error_code ec, tcp::resolver::results_type results) { - onResolve(ec, results); - }); - } - - // Resolve completion callback - void onResolve(beast::error_code ec, tcp::resolver::results_type results) { - if (ec) { - onError("Resolve failed: " + ec.message()); - return; - } - - // Connect to resolved endpoints - beast::get_lowest_layer(ws).async_connect(results, - [this](beast::error_code ec, tcp::resolver::results_type::endpoint_type) { - onConnect(ec); - }); - } - - // Connection completion callback - void onConnect(beast::error_code ec) { - if (ec) { - onError("Connection failed: " + ec.message()); - return; - } - - // Set WebSocket options - ws.set_option(beast::websocket::stream_base::timeout::suggested( - beast::role_type::client)); - - // Perform WebSocket handshake - ws.async_handshake(host, path, - [this](beast::error_code ec) { - onHandshake(ec); - }); - } - - // Handshake completion callback - void onHandshake(beast::error_code ec) { - connecting = false; - - if (ec) { - onError("Handshake failed: " + ec.message()); - return; - } - - connected = true; - - // Call connection success callback - if (connect_callback) { - connect_callback(true); - } - - // Start reading messages - doRead(); - - // Start sending queued messages - doSend(); - } - - // Read messages - void doRead() { - if (!connected) { - return; - } - - ws.async_read(buffer, - [this](beast::error_code ec, std::size_t bytes_transferred) { - onRead(ec, bytes_transferred); - }); - } - - // Read completion callback - void onRead(beast::error_code ec, std::size_t bytes_transferred) { - if (ec) { - if (ec == beast::websocket::error::closed) { - // Normal close - onClose(); - } else { - onError("Read failed: " + ec.message()); - } - return; - } - - // Process received message - std::string message = beast::buffers_to_string(buffer.data()); - buffer.consume(bytes_transferred); - - if (message_callback) { - message_callback(message); - } - - // Continue reading - doRead(); - } - - // Send messages - void doSend() { - if (!connected || sending) { - return; - } - - std::unique_lock lock(queue_mutex); - if (send_queue.empty()) { - return; - } - - sending = true; - std::string message = std::move(send_queue.front()); - send_queue.pop(); - lock.unlock(); - - ws.async_write(asio::buffer(message), - [this](beast::error_code ec, std::size_t bytes_transferred) { - onWrite(ec, bytes_transferred); - }); - } - - // Write completion callback - void onWrite(beast::error_code ec, std::size_t bytes_transferred) { - sending = false; - - if (ec) { - onError("Write failed: " + ec.message()); - return; - } - - // Continue sending queued messages - doSend(); - } - - // Error handling - void onError(const std::string& error) { - connecting = false; - connected = false; - - std::cerr << "WebSocket error: " << error << std::endl; - - if (error_callback) { - error_callback(error); - } - - // Auto reconnect - if (auto_reconnect && !stopping) { - std::this_thread::sleep_for(std::chrono::milliseconds(reconnect_interval_ms)); - asio::post(ioc, [this]() { doConnect(); }); - } - } - - // Connection close - void onClose() { - connected = false; - - if (close_callback) { - close_callback(); - } - - // Auto reconnect - if (auto_reconnect && !stopping) { - std::this_thread::sleep_for(std::chrono::milliseconds(reconnect_interval_ms)); - asio::post(ioc, [this]() { doConnect(); }); - } - } - -private: - beast::flat_buffer buffer; -}; - -// WebSocketClient implementation -WebSocketClient::WebSocketClient(const std::string& host, const std::string& port, const std::string& path) - : impl_(std::make_unique(host, port, path)) -{ - impl_->start(); -} - -WebSocketClient::~WebSocketClient() { - disconnect(); -} - -void WebSocketClient::setMessageCallback(MessageCallback callback) { - impl_->message_callback = callback; -} - -void WebSocketClient::setErrorCallback(ErrorCallback callback) { - impl_->error_callback = callback; -} - -void WebSocketClient::setConnectCallback(ConnectCallback callback) { - impl_->connect_callback = callback; -} - -void WebSocketClient::setCloseCallback(CloseCallback callback) { - impl_->close_callback = callback; -} - -bool WebSocketClient::connect(int timeout_ms) { - if (impl_->connecting || impl_->connected) { - return false; - } - - impl_->doConnect(); - return true; -} - -void WebSocketClient::disconnect() { - if (!impl_->connected && !impl_->connecting) { - return; - } - - impl_->stopping = true; - - asio::post(impl_->ioc, [this]() { - beast::error_code ec; - impl_->ws.close(beast::websocket::close_code::normal, ec); - impl_->connected = false; - impl_->connecting = false; - }); -} - -bool WebSocketClient::isConnected() const { - return impl_->connected; -} - -bool WebSocketClient::send(const std::string& message) { - if (!impl_->connected) { - return false; - } - - { - std::lock_guard lock(impl_->queue_mutex); - impl_->send_queue.push(message); - } - - impl_->queue_cv.notify_one(); - asio::post(impl_->ioc, [this]() { impl_->doSend(); }); - - return true; -} - -bool WebSocketClient::sendBinary(const void* data, size_t size) { - if (!impl_->connected) { - return false; - } - - std::string message(static_cast(data), size); - return send(message); -} - -void WebSocketClient::setAutoReconnect(bool enable, int interval_ms) { - impl_->auto_reconnect = enable; - impl_->reconnect_interval_ms = interval_ms; -} - -void WebSocketClient::setHeartbeatInterval(int interval_ms) { - impl_->heartbeat_interval_ms = interval_ms; -} - -std::string WebSocketClient::getHost() const { - return impl_->host; -} - -std::string WebSocketClient::getPort() const { - return impl_->port; -} - -std::string WebSocketClient::getPath() const { - return impl_->path; -} - -} // namespace net -} // namespace pmc \ No newline at end of file diff --git a/libhttp/src/WebSocketServer.cpp b/libhttp/src/WebSocketServer.cpp deleted file mode 100644 index 57a71dc..0000000 --- a/libhttp/src/WebSocketServer.cpp +++ /dev/null @@ -1,383 +0,0 @@ -// WebSocketServer.cpp - WebSocket server implementation -// Based on Boost.Beast and Boost.Asio - -#include "us/WebSocketServer.hpp" -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -namespace beast = boost::beast; -namespace websocket = beast::websocket; -namespace asio = boost::asio; -using tcp = asio::ip::tcp; - -namespace pmc::net { - -// Internal Session class implementation -class WebSocketServer::Session : public std::enable_shared_from_this { -public: - Session(tcp::socket socket, WebSocketServer* server) - : ws_(std::move(socket)) - , server_(server) { - - // Get remote address - auto endpoint = ws_.next_layer().socket().remote_endpoint(); - remoteAddress_ = endpoint.address().to_string() + ":" + std::to_string(endpoint.port()); - } - - void start() { - // Generate client ID - if (server_) { - clientId_ = server_->generateClientId(); - } else { - // If server is null, use default ID - static std::atomic counter{0}; - clientId_ = "client_" + std::to_string(++counter); - } - - // Asynchronously accept WebSocket handshake - ws_.async_accept( - beast::bind_front_handler( - &Session::onAccept, - shared_from_this())); - } - - void send(const std::string& message) { - // Add message to send queue - bool write_in_progress = !sendQueue_.empty(); - sendQueue_.push_back(message); - - if (!write_in_progress) { - // Start asynchronous write - doWrite(); - } - } - - const std::string& getClientId() const { return clientId_; } - const std::string& getRemoteAddress() const { return remoteAddress_; } - -private: - websocket::stream ws_; - beast::flat_buffer buffer_; - std::deque sendQueue_; - WebSocketServer* server_; - std::string clientId_; - std::string remoteAddress_; - - void onAccept(beast::error_code ec) { - if (ec) { - std::cerr << "WebSocket accept error: " << ec.message() << std::endl; - return; - } - - // Add to server client list - if (server_) { - server_->addClient(clientId_, shared_from_this()); - - // Call connection handler - if (server_->connectHandler_) { - server_->connectHandler_(clientId_, remoteAddress_); - } - } - - // Start reading messages - doRead(); - } - - void doRead() { - // Asynchronously read message - ws_.async_read( - buffer_, - beast::bind_front_handler( - &Session::onRead, - shared_from_this())); - } - - void onRead(beast::error_code ec, std::size_t bytes_transferred) { - if (ec == websocket::error::closed) { - // Normal close - if (server_) { - server_->removeClient(clientId_); - if (server_->disconnectHandler_) { - server_->disconnectHandler_(clientId_); - } - } - return; - } - - if (ec) { - std::cerr << "WebSocket read error: " << ec.message() << std::endl; - if (server_) { - server_->removeClient(clientId_); - if (server_->disconnectHandler_) { - server_->disconnectHandler_(clientId_); - } - } - return; - } - - // Process received message - std::string message = beast::buffers_to_string(buffer_.data()); - buffer_.consume(bytes_transferred); - - if (server_) { - // Find message handler - for (const auto& handler : server_->messageHandlers_) { - // Path matching logic can be added here - handler.second(clientId_, message, [this](const std::string& response) { - send(response); - }); - } - } - - // Continue reading - doRead(); - } - - void doWrite() { - if (sendQueue_.empty()) { - return; - } - - // Get first message from queue - auto message = sendQueue_.front(); - - // Asynchronously write message - ws_.async_write( - asio::buffer(message), - beast::bind_front_handler( - &Session::onWrite, - shared_from_this())); - } - - void onWrite(beast::error_code ec, std::size_t bytes_transferred) { - (void)bytes_transferred; - if (ec) { - std::cerr << "WebSocket write error: " << ec.message() << std::endl; - if (server_) { - server_->removeClient(clientId_); - if (server_->disconnectHandler_) { - server_->disconnectHandler_(clientId_); - } - } - return; - } - - // Remove sent message - sendQueue_.pop_front(); - - // Continue sending next message in queue - if (!sendQueue_.empty()) { - doWrite(); - } - } -}; - -// Internal Listener class implementation -class WebSocketServer::Listener : public std::enable_shared_from_this { -public: - static std::shared_ptr create(asio::io_context& ioc, tcp::endpoint endpoint, WebSocketServer* server) { - return std::shared_ptr(new Listener(ioc, endpoint, server)); - } - - void run() { - if (!acceptor_.is_open()) { - throw std::runtime_error("Acceptor is not open"); - } - - doAccept(); - } - -private: - Listener(asio::io_context& ioc, tcp::endpoint endpoint, WebSocketServer* server) - : ioc_(ioc) - , acceptor_(asio::make_strand(ioc)) - , server_(server) { - - beast::error_code ec; - - // Open acceptor - acceptor_.open(endpoint.protocol(), ec); - if (ec) { - std::cerr << "Failed to open acceptor: " << ec.message() << std::endl; - throw std::runtime_error("Failed to open acceptor: " + ec.message()); - } - - // Set address reuse - acceptor_.set_option(asio::socket_base::reuse_address(true), ec); - if (ec) { - std::cerr << "Failed to set reuse address: " << ec.message() << std::endl; - throw std::runtime_error("Failed to set reuse address: " + ec.message()); - } - - // Bind to endpoint - acceptor_.bind(endpoint, ec); - if (ec) { - std::cerr << "Failed to bind to endpoint: " << ec.message() << std::endl; - throw std::runtime_error("Failed to bind to endpoint: " + ec.message()); - } - - // Start listening - acceptor_.listen(asio::socket_base::max_listen_connections, ec); - if (ec) { - std::cerr << "Failed to listen: " << ec.message() << std::endl; - throw std::runtime_error("Failed to listen: " + ec.message()); - } - } - - void doAccept() { - acceptor_.async_accept( - asio::make_strand(ioc_), - beast::bind_front_handler( - &Listener::onAccept, - shared_from_this())); - } - - void onAccept(beast::error_code ec, tcp::socket socket) { - if (ec) { - std::cerr << "Accept error: " << ec.message() << std::endl; - } else { - // Create new Session to handle connection - std::make_shared(std::move(socket), server_)->start(); - } - - // Continue accepting new connections - doAccept(); - } - - asio::io_context& ioc_; - tcp::acceptor acceptor_; - WebSocketServer* server_; -}; - -WebSocketServer::WebSocketServer(unsigned short port, unsigned int threads) - : port_(port) - , threads_(threads) - , impl_(std::make_unique()) {} - -WebSocketServer::~WebSocketServer() { - stop(); -} - -void WebSocketServer::start() { - if (running_) { - return; - } - - // Create listener - tcp::endpoint endpoint(tcp::v4(), port_); - impl_->listener_ = Listener::create(impl_->ioc_, endpoint, this); - - // Start listener - impl_->listener_->run(); - - // Start worker threads - for (unsigned int i = 0; i < threads_; ++i) { - impl_->threads_.emplace_back([this]() { - impl_->ioc_.run(); - }); - } - - running_ = true; - std::cout << "WebSocketServer started on port " << port_ << " with " << threads_ << " threads" << std::endl; -} - -void WebSocketServer::stop() { - if (!running_) { - return; - } - - // Stop io_context - impl_->ioc_.stop(); - - // Wait for all threads to finish - for (auto& thread : impl_->threads_) { - if (thread.joinable()) { - thread.join(); - } - } - - impl_->threads_.clear(); - running_ = false; - std::cout << "WebSocketServer stopped" << std::endl; -} - -void WebSocketServer::onMessage(const std::string& path, WebSocketMessageHandler handler) { - messageHandlers_[path] = std::move(handler); -} - -void WebSocketServer::onConnect(WebSocketConnectHandler handler) { - connectHandler_ = std::move(handler); -} - -void WebSocketServer::onDisconnect(WebSocketDisconnectHandler handler) { - disconnectHandler_ = std::move(handler); -} - -bool WebSocketServer::sendToClient(const std::string& clientId, const std::string& message) { - std::lock_guard lock(clientsMutex_); - auto it = clients_.find(clientId); - if (it != clients_.end()) { - it->second->send(message); - return true; - } - return false; -} - -size_t WebSocketServer::broadcast(const std::string& message) { - std::lock_guard lock(clientsMutex_); - size_t count = 0; - for (auto& pair : clients_) { - pair.second->send(message); - count++; - } - return count; -} - -void WebSocketServer::disconnectClient(const std::string& clientId) { - std::lock_guard lock(clientsMutex_); - auto it = clients_.find(clientId); - if (it != clients_.end()) { - // Should close WebSocket connection here - // Currently only removing from list - clients_.erase(it); - } -} - -size_t WebSocketServer::getClientCount() const { - std::lock_guard lock(clientsMutex_); - return clients_.size(); -} - -std::string WebSocketServer::generateClientId() const { - static std::atomic counter{0}; - return "client_" + std::to_string(++counter); -} - -void WebSocketServer::addClient(const std::string& clientId, std::shared_ptr session) { - std::lock_guard lock(clientsMutex_); - clients_[clientId] = session; -} - -void WebSocketServer::removeClient(const std::string& clientId) { - std::lock_guard lock(clientsMutex_); - clients_.erase(clientId); -} - -std::shared_ptr WebSocketServer::getClient(const std::string& clientId) { - std::lock_guard lock(clientsMutex_); - auto it = clients_.find(clientId); - if (it != clients_.end()) { - return it->second; - } - return nullptr; -} - -} // namespace pmc::net \ No newline at end of file