#include "WebServer.h" #include "pages/index.h" #include namespace ccu { //--------------------------------------------------------- // 事件处理:HTTP 路由 + WebSocket 收发 void WebServer::handleEvent(struct mg_connection* c, int ev, void* ev_data) { if (ev == MG_EV_HTTP_MSG) { struct mg_http_message* hm = (struct mg_http_message*)ev_data; // WebSocket 升级 if (mg_match(hm->uri, mg_str("/ws"), NULL)) { mg_ws_upgrade(c, hm, NULL); return; } // /api/* 接口:交给宿主程序处理,返回 JSON std::string path(hm->uri.buf, hm->uri.len); if (path.rfind("/api/", 0) == 0) { std::string body; if (m_apiHandler) { std::string query(hm->query.buf, hm->query.len); body = m_apiHandler(path, query); } if (!body.empty()) { mg_http_reply(c, 200, "Content-Type: application/json\r\n", "%.*s", (int)body.size(), body.c_str()); } else { mg_http_reply(c, 404, "Content-Type: text/plain\r\n", "Not Found\n"); } return; } // 监控页面 if (path == "/" || path == "/index.html") { mg_http_reply(c, 200, "Content-Type: text/html; charset=utf-8\r\n", "%.*s", (int)INDEX_HTML.size(), INDEX_HTML.c_str()); return; } mg_http_reply(c, 404, "Content-Type: text/plain\r\n", "Not Found\n"); } else if (ev == MG_EV_WS_OPEN) { // Web 线程内:先构建欢迎快照(可能涉及 DB 查询,勿持锁),再登记连接 std::string welcome; if (m_onOpen) welcome = m_onOpen(); m_wsConnections.push_back(c); if (!welcome.empty()) { mg_ws_send(c, welcome.c_str(), welcome.size(), WEBSOCKET_OP_TEXT); } } else if (ev == MG_EV_CLOSE || ev == MG_EV_ERROR) { for (auto it = m_wsConnections.begin(); it != m_wsConnections.end(); ++it) { if (*it == c) { m_wsConnections.erase(it); break; } } } else if (ev == MG_EV_WS_MSG) { // 纯监控,忽略客户端消息 } } WebServer::WebServer() { mg_mgr_init(&m_mgr); } WebServer::~WebServer() { stop(); mg_mgr_free(&m_mgr); } void WebServer::setOnOpen(std::function handler) { m_onOpen = std::move(handler); } void WebServer::setApiHandler(std::function handler) { m_apiHandler = std::move(handler); } bool WebServer::start(int port) { if (m_running) return true; m_port = port; std::string addr = "http://0.0.0.0:" + std::to_string(port); mg_http_listen(&m_mgr, addr.c_str(), [](mg_connection* c, int ev, void* ev_data) { WebServer* server = static_cast(c->fn_data); if (server) server->handleEvent(c, ev, ev_data); }, this); m_running = true; m_thread = std::thread([this]() { serverThreadFunc(); }); std::cout << "pCCU web UI listening on " << addr << std::endl; return true; } void WebServer::stop() { if (!m_running) return; m_running = false; if (m_thread.joinable()) m_thread.join(); // Web 线程已退出,此处触碰连接列表安全 for (auto c : m_wsConnections) { mg_ws_send(c, "", 0, WEBSOCKET_OP_CLOSE); } m_wsConnections.clear(); } bool WebServer::serverThreadCB(void* pParam) { WebServer* pThis = static_cast(pParam); return pThis->serverThreadFunc(); } bool WebServer::serverThreadFunc() { while (m_running) { mg_mgr_poll(&m_mgr, 200); // 仅在本线程(poll 间隙)发送,避免跨线程操作 mongoose 连接 flushPending(); } return true; } void WebServer::flushPending() { std::string text; { std::lock_guard lock(m_pendingMutex); text = std::move(m_pending); m_pending.clear(); } if (text.empty()) return; for (auto c : m_wsConnections) { mg_ws_send(c, text.c_str(), text.size(), WEBSOCKET_OP_TEXT); } } void WebServer::broadcast(const std::string& text) { // 只投递到队列,实际发送在 Web 线程 flushPending() 中完成 std::lock_guard lock(m_pendingMutex); m_pending = text; } } // namespace ccu