web_server.cpp 27 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630
  1. #include "web_server.h"
  2. #include "httplib.h"
  3. #include "json.hpp"
  4. #include <iostream>
  5. #include <fstream>
  6. #include <sstream>
  7. #include <thread>
  8. #include <chrono>
  9. #include <set>
  10. #include <random>
  11. using namespace std;
  12. using json = nlohmann::json;
  13. class WebServer::Impl {
  14. public:
  15. httplib::Server svr;
  16. string host;
  17. int port;
  18. std::atomic<bool> stopping{false}; // 退出标记: 让MJPEG推流循环及时结束
  19. std::atomic<bool> mjpeg_enabled{true}; // MJPEG推流开关
  20. unordered_map<string, function<string()>> get_handlers;
  21. unordered_map<string, function<string(const map<string,string>&)>> getq_handlers;
  22. unordered_map<string, function<string(const string&)>> post_handlers;
  23. unordered_map<int, function<cv::Mat()>> frame_providers;
  24. unordered_map<int, function<uint64_t()>> seq_providers;
  25. AuthHooks hooks_;
  26. bool hooks_set_ = false;
  27. // ---- 登录会话 ----
  28. std::set<std::string> sessions;
  29. std::mutex sess_mtx;
  30. std::string makeSession() {
  31. static std::random_device rd;
  32. static std::mt19937 gen(rd());
  33. static std::uniform_int_distribution<int> d(0, 15);
  34. static const char* hx = "0123456789abcdef";
  35. std::string t;
  36. for (int i = 0; i < 40; i++) t += hx[d(gen)];
  37. std::lock_guard<std::mutex> lk(sess_mtx);
  38. sessions.insert(t);
  39. return t;
  40. }
  41. bool sessionOk(const httplib::Request& req) {
  42. std::string c = req.get_header_value("Cookie");
  43. size_t p = c.find("za_session=");
  44. if (p == std::string::npos) return false;
  45. p += 11;
  46. size_t e = c.find(';', p);
  47. std::string tok = c.substr(p, (e == std::string::npos ? c.size() : e) - p);
  48. std::lock_guard<std::mutex> lk(sess_mtx);
  49. return sessions.count(tok) > 0;
  50. }
  51. void dropSession(const httplib::Request& req) {
  52. std::string c = req.get_header_value("Cookie");
  53. size_t p = c.find("za_session=");
  54. if (p == std::string::npos) return;
  55. p += 11;
  56. size_t e = c.find(';', p);
  57. std::string tok = c.substr(p, (e == std::string::npos ? c.size() : e) - p);
  58. std::lock_guard<std::mutex> lk(sess_mtx);
  59. sessions.erase(tok);
  60. }
  61. bool authed(const httplib::Request& req) {
  62. if (!hooks_set_) return true; // 未配置凭据时不启用登录
  63. std::string p = req.path;
  64. if (p == "/login" || p == "/api/login" || p.rfind("/logo.", 0) == 0 ||
  65. p == "/favicon.ico")
  66. return true;
  67. return sessionOk(req);
  68. }
  69. void denyJson(httplib::Response& res) {
  70. res.status = 401;
  71. res.set_content(R"({"error":"unauthorized"})", "application/json");
  72. }
  73. void setupRoutes() {
  74. svr.Get("/", [this](const httplib::Request& req, httplib::Response& res) {
  75. if (hooks_set_ && !authed(req)) {
  76. res.status = 302;
  77. res.set_header("Location", "/login");
  78. return;
  79. }
  80. ifstream f("web/index.html");
  81. if (f.is_open()) {
  82. string content((istreambuf_iterator<char>(f)), istreambuf_iterator<char>());
  83. res.set_content(content, "text/html");
  84. } else {
  85. res.set_content("<h1>zone_alarm</h1><p>web/index.html not found</p>", "text/html");
  86. }
  87. });
  88. svr.Get("/login", [](const httplib::Request& req, httplib::Response& res) {
  89. ifstream f("web/login.html");
  90. if (f.is_open()) {
  91. string content((istreambuf_iterator<char>(f)), istreambuf_iterator<char>());
  92. res.set_content(content, "text/html");
  93. } else {
  94. res.status = 404;
  95. res.set_content("login page missing", "text/plain");
  96. }
  97. });
  98. svr.Post("/api/login", [this](const httplib::Request& req, httplib::Response& res) {
  99. try {
  100. auto j = json::parse(req.body);
  101. string u = j.value("username", "");
  102. string p = j.value("password", "");
  103. if (hooks_set_ && hooks_.check(u, p)) {
  104. std::string tok = makeSession();
  105. res.set_header("Set-Cookie",
  106. "za_session=" + tok + "; Path=/; HttpOnly");
  107. res.set_content(R"({"ok":true})", "application/json");
  108. cout << "[Auth] 登录成功: " << u << endl;
  109. } else {
  110. res.set_content(R"({"ok":false,"error":"用户名或密码错误"})",
  111. "application/json");
  112. }
  113. } catch (...) {
  114. res.set_content(R"({"ok":false})", "application/json");
  115. }
  116. });
  117. svr.Post("/api/logout", [this](const httplib::Request& req, httplib::Response& res) {
  118. if (!authed(req)) { denyJson(res); return; }
  119. dropSession(req);
  120. res.set_header("Set-Cookie",
  121. "za_session=deleted; Path=/; Max-Age=0");
  122. res.set_content(R"({"ok":true})", "application/json");
  123. });
  124. svr.Get("/api/auth/info", [this](const httplib::Request& req, httplib::Response& res) {
  125. if (!authed(req)) { denyJson(res); return; }
  126. string u = (hooks_set_ && hooks_.currentUser) ? hooks_.currentUser() : "";
  127. json j; j["username"] = u;
  128. res.set_content(j.dump(), "application/json");
  129. });
  130. svr.Post("/api/auth/changepwd", [this](
  131. const httplib::Request& req,
  132. httplib::Response& res) {
  133. if (!hooks_set_) { denyJson(res); return; }
  134. if (!authed(req)) { denyJson(res); return; }
  135. try {
  136. auto j = json::parse(req.body);
  137. string oldp = j.value("old_password", "");
  138. string u = j.value("username", "");
  139. string newp = j.value("new_password", "");
  140. if (!hooks_.update(oldp, u, newp)) {
  141. res.set_content(R"({"ok":false,"error":"原密码错误或新密码为空"})",
  142. "application/json");
  143. return;
  144. }
  145. {
  146. std::lock_guard<std::mutex> lk(sess_mtx);
  147. sessions.clear(); // 凭据变更后作废所有旧会话
  148. }
  149. std::string tok = makeSession(); // 给当前浏览器签发新会话
  150. res.set_header("Set-Cookie",
  151. "za_session=" + tok + "; Path=/; HttpOnly");
  152. res.set_content(R"({"ok":true})", "application/json");
  153. } catch (...) {
  154. res.set_content(R"({"ok":false})", "application/json");
  155. }
  156. });
  157. svr.Get("/logo.ico", [](const httplib::Request& req, httplib::Response& res) {
  158. ifstream f("logo.ico", ios::binary);
  159. if (!f.is_open()) { res.status = 404; return; }
  160. string content((istreambuf_iterator<char>(f)), istreambuf_iterator<char>());
  161. res.set_content(content, "image/x-icon");
  162. });
  163. svr.Get("/logo.png", [](const httplib::Request& req, httplib::Response& res) {
  164. ifstream f("logo.png", ios::binary);
  165. if (!f.is_open()) { res.status = 404; return; }
  166. string content((istreambuf_iterator<char>(f)), istreambuf_iterator<char>());
  167. res.set_content(content, "image/png");
  168. });
  169. svr.Get("/preview.html", [this](const httplib::Request& req, httplib::Response& res) {
  170. if (!authed(req)) { res.status = 302; res.set_header("Location", "/login"); return; }
  171. ifstream f("web/preview.html");
  172. if (f.is_open()) {
  173. string content((istreambuf_iterator<char>(f)), istreambuf_iterator<char>());
  174. res.set_content(content, "text/html");
  175. } else {
  176. res.status = 404;
  177. res.set_content("preview.html not found", "text/plain");
  178. }
  179. });
  180. svr.Get("/config.html", [this](const httplib::Request& req, httplib::Response& res) {
  181. if (!authed(req)) { res.status = 302; res.set_header("Location", "/login"); return; }
  182. ifstream f("web/config.html");
  183. if (f.is_open()) {
  184. string content((istreambuf_iterator<char>(f)), istreambuf_iterator<char>());
  185. res.set_content(content, "text/html");
  186. } else {
  187. res.status = 404;
  188. res.set_content("config.html not found", "text/plain");
  189. }
  190. });
  191. svr.Get("/stream/:ch", [this](const httplib::Request& req, httplib::Response& res) {
  192. if (!authed(req)) { denyJson(res); return; }
  193. int ch = 0;
  194. try { ch = stoi(req.path_params.at("ch")); } catch (...) {}
  195. auto fp = frame_providers.find(ch);
  196. if (fp == frame_providers.end() || !fp->second) {
  197. res.set_content("no stream", "text/plain");
  198. return;
  199. }
  200. cv::Mat frame = fp->second();
  201. if (frame.empty()) {
  202. res.set_content("no frame", "text/plain");
  203. return;
  204. }
  205. vector<uchar> buf;
  206. vector<int> params = {cv::IMWRITE_JPEG_QUALITY, 70};
  207. cv::imencode(".jpg", frame, buf, params);
  208. res.set_content(string(reinterpret_cast<char*>(buf.data()), buf.size()),
  209. "image/jpeg");
  210. });
  211. svr.Get("/mjpeg/:ch", [this](const httplib::Request& req, httplib::Response& res) {
  212. if (!authed(req)) { denyJson(res); return; }
  213. if (!mjpeg_enabled.load()) {
  214. res.set_content("stream disabled", "text/plain");
  215. return;
  216. }
  217. int ch = 0;
  218. try { ch = stoi(req.path_params.at("ch")); } catch (...) {}
  219. auto fp = frame_providers.find(ch);
  220. if (fp == frame_providers.end() || !fp->second) {
  221. res.set_content("no stream", "text/plain");
  222. return;
  223. }
  224. auto seqf = seq_providers.find(ch);
  225. function<uint64_t()> seqfn =
  226. (seqf != seq_providers.end()) ? seqf->second : nullptr;
  227. res.set_chunked_content_provider(
  228. "multipart/x-mixed-replace; boundary=mjpegboundary",
  229. [this, fp, seqfn](size_t, httplib::DataSink& sink) -> bool {
  230. uint64_t last_seq = 0;
  231. // MJPEG推流节流: 约10fps, 避免并发编码吃满CPU
  232. const int interval_ms = 100;
  233. while (!stopping.load() && mjpeg_enabled.load() && sink.is_writable()) {
  234. uint64_t seq = seqfn ? seqfn() : 0;
  235. if (seq != 0 && seq == last_seq) {
  236. // 帧未更新, 跳过重复JPEG编码
  237. std::this_thread::sleep_for(std::chrono::milliseconds(20));
  238. continue;
  239. }
  240. cv::Mat frame = fp->second();
  241. if (!frame.empty()) {
  242. std::vector<uchar> buf;
  243. std::vector<int> params = {cv::IMWRITE_JPEG_QUALITY, 50};
  244. cv::imencode(".jpg", frame, buf, params);
  245. std::string header = "--mjpegboundary\r\n"
  246. "Content-Type: image/jpeg\r\n"
  247. "Content-Length: " + std::to_string(buf.size()) + "\r\n\r\n";
  248. if (!sink.write(header.data(), header.size())) return false;
  249. if (!sink.write(reinterpret_cast<char*>(buf.data()), buf.size())) return false;
  250. if (!sink.write("\r\n", 2)) return false;
  251. last_seq = seq;
  252. }
  253. std::this_thread::sleep_for(std::chrono::milliseconds(interval_ms));
  254. }
  255. return true;
  256. });
  257. });
  258. svr.Get("/data/screenshots/:file", [](const httplib::Request& req, httplib::Response& res) {
  259. string filename = req.path_params.at("file");
  260. if (filename.find("..") != string::npos) {
  261. res.status = 403;
  262. return;
  263. }
  264. string path = "data/screenshots/" + filename;
  265. ifstream f(path, ios::binary);
  266. if (!f.is_open()) {
  267. res.status = 404;
  268. return;
  269. }
  270. string content((istreambuf_iterator<char>(f)), istreambuf_iterator<char>());
  271. res.set_content(content, "image/jpeg");
  272. });
  273. svr.Get("/api/status", [this](const httplib::Request& req, httplib::Response& res) {
  274. if (!authed(req)) { denyJson(res); return; }
  275. auto it = get_handlers.find("/api/status");
  276. if (it != get_handlers.end())
  277. res.set_content(it->second(), "application/json");
  278. });
  279. svr.Get("/api/fences", [this](const httplib::Request& req, httplib::Response& res) {
  280. if (!authed(req)) { denyJson(res); return; }
  281. auto it = get_handlers.find("/api/fences");
  282. if (it != get_handlers.end())
  283. res.set_content(it->second(), "application/json");
  284. });
  285. svr.Post("/api/fences", [this](const httplib::Request& req, httplib::Response& res) {
  286. if (!authed(req)) { denyJson(res); return; }
  287. auto it = post_handlers.find("/api/fences");
  288. if (it != post_handlers.end())
  289. res.set_content(it->second(req.body), "application/json");
  290. });
  291. svr.Post("/api/config", [this](const httplib::Request& req, httplib::Response& res) {
  292. if (!authed(req)) { denyJson(res); return; }
  293. auto it = post_handlers.find("/api/config");
  294. if (it != post_handlers.end())
  295. res.set_content(it->second(req.body), "application/json");
  296. });
  297. svr.Post("/api/relay", [this](const httplib::Request& req, httplib::Response& res) {
  298. if (!authed(req)) { denyJson(res); return; }
  299. auto it = post_handlers.find("/api/relay");
  300. if (it != post_handlers.end())
  301. res.set_content(it->second(req.body), "application/json");
  302. });
  303. svr.Get("/api/relay/status", [this](const httplib::Request& req, httplib::Response& res) {
  304. if (!authed(req)) { denyJson(res); return; }
  305. auto it = get_handlers.find("/api/relay/status");
  306. if (it != get_handlers.end())
  307. res.set_content(it->second(), "application/json");
  308. });
  309. svr.Post("/api/relay/config", [this](const httplib::Request& req, httplib::Response& res) {
  310. if (!authed(req)) { denyJson(res); return; }
  311. auto it = post_handlers.find("/api/relay/config");
  312. if (it != post_handlers.end())
  313. res.set_content(it->second(req.body), "application/json");
  314. });
  315. svr.Post("/api/relay/mode", [this](const httplib::Request& req, httplib::Response& res) {
  316. if (!authed(req)) { denyJson(res); return; }
  317. auto it = post_handlers.find("/api/relay/mode");
  318. if (it != post_handlers.end())
  319. res.set_content(it->second(req.body), "application/json");
  320. });
  321. svr.Get("/api/relay/bindings", [this](const httplib::Request& req, httplib::Response& res) {
  322. if (!authed(req)) { denyJson(res); return; }
  323. auto it = get_handlers.find("/api/relay/bindings");
  324. if (it != get_handlers.end())
  325. res.set_content(it->second(), "application/json");
  326. });
  327. svr.Post("/api/relay/bindings", [this](const httplib::Request& req, httplib::Response& res) {
  328. if (!authed(req)) { denyJson(res); return; }
  329. auto it = post_handlers.find("/api/relay/bindings");
  330. if (it != post_handlers.end())
  331. res.set_content(it->second(req.body), "application/json");
  332. });
  333. svr.Get("/api/system", [this](const httplib::Request& req, httplib::Response& res) {
  334. if (!authed(req)) { denyJson(res); return; }
  335. auto it = get_handlers.find("/api/system");
  336. if (it != get_handlers.end())
  337. res.set_content(it->second(), "application/json");
  338. });
  339. svr.Post("/api/reboot", [this](const httplib::Request& req, httplib::Response& res) {
  340. if (!authed(req)) { denyJson(res); return; }
  341. auto it = post_handlers.find("/api/reboot");
  342. if (it != post_handlers.end())
  343. res.set_content(it->second(req.body), "application/json");
  344. });
  345. svr.Post("/api/channels/save", [this](const httplib::Request& req, httplib::Response& res) {
  346. if (!authed(req)) { denyJson(res); return; }
  347. auto it = post_handlers.find("/api/channels/save");
  348. if (it != post_handlers.end())
  349. res.set_content(it->second(req.body), "application/json");
  350. });
  351. svr.Post("/api/channels/delete", [this](const httplib::Request& req, httplib::Response& res) {
  352. if (!authed(req)) { denyJson(res); return; }
  353. auto it = post_handlers.find("/api/channels/delete");
  354. if (it != post_handlers.end())
  355. res.set_content(it->second(req.body), "application/json");
  356. });
  357. svr.Post("/api/channels/test", [this](const httplib::Request& req, httplib::Response& res) {
  358. if (!authed(req)) { denyJson(res); return; }
  359. auto it = post_handlers.find("/api/channels/test");
  360. if (it != post_handlers.end())
  361. res.set_content(it->second(req.body), "application/json");
  362. });
  363. svr.Get("/api/buckets", [this](const httplib::Request& req, httplib::Response& res) {
  364. if (!authed(req)) { denyJson(res); return; }
  365. auto it = get_handlers.find("/api/buckets");
  366. if (it != get_handlers.end())
  367. res.set_content(it->second(), "application/json");
  368. });
  369. svr.Post("/api/buckets", [this](const httplib::Request& req, httplib::Response& res) {
  370. if (!authed(req)) { denyJson(res); return; }
  371. auto it = post_handlers.find("/api/buckets");
  372. if (it != post_handlers.end())
  373. res.set_content(it->second(req.body), "application/json");
  374. });
  375. svr.Get("/api/buckets/status", [this](const httplib::Request& req, httplib::Response& res) {
  376. if (!authed(req)) { denyJson(res); return; }
  377. auto it = get_handlers.find("/api/buckets/status");
  378. if (it != get_handlers.end())
  379. res.set_content(it->second(), "application/json");
  380. });
  381. svr.Post("/api/buckets/activate", [this](const httplib::Request& req, httplib::Response& res) {
  382. if (!authed(req)) { denyJson(res); return; }
  383. auto it = post_handlers.find("/api/buckets/activate");
  384. if (it != post_handlers.end())
  385. res.set_content(it->second(req.body), "application/json");
  386. });
  387. svr.Post("/api/buckets/mode", [this](const httplib::Request& req, httplib::Response& res) {
  388. if (!authed(req)) { denyJson(res); return; }
  389. auto it = post_handlers.find("/api/buckets/mode");
  390. if (it != post_handlers.end())
  391. res.set_content(it->second(req.body), "application/json");
  392. });
  393. svr.Get("/api/module/config", [this](const httplib::Request& req, httplib::Response& res) {
  394. if (!authed(req)) { denyJson(res); return; }
  395. auto it = get_handlers.find("/api/module/config");
  396. if (it != get_handlers.end())
  397. res.set_content(it->second(), "application/json");
  398. });
  399. svr.Post("/api/module/config", [this](const httplib::Request& req, httplib::Response& res) {
  400. if (!authed(req)) { denyJson(res); return; }
  401. auto it = post_handlers.find("/api/module/config");
  402. if (it != post_handlers.end())
  403. res.set_content(it->second(req.body), "application/json");
  404. });
  405. svr.Get("/api/alarms", [this](const httplib::Request& req, httplib::Response& res) {
  406. if (!authed(req)) { denyJson(res); return; }
  407. auto it = getq_handlers.find("/api/alarms");
  408. if (it != getq_handlers.end()) {
  409. std::map<std::string, std::string> ps;
  410. for (auto& p : req.params) ps[p.first] = p.second;
  411. res.set_content(it->second(ps), "application/json");
  412. }
  413. });
  414. svr.Get("/api/pose/alarms", [this](const httplib::Request& req, httplib::Response& res) {
  415. if (!authed(req)) { denyJson(res); return; }
  416. auto it = getq_handlers.find("/api/pose/alarms");
  417. if (it != getq_handlers.end()) {
  418. std::map<std::string, std::string> ps;
  419. for (auto& p : req.params) ps[p.first] = p.second;
  420. res.set_content(it->second(ps), "application/json");
  421. }
  422. });
  423. svr.Post("/api/pose/alarms/delete", [this](const httplib::Request& req, httplib::Response& res) {
  424. if (!authed(req)) { denyJson(res); return; }
  425. auto it = post_handlers.find("/api/pose/alarms/delete");
  426. if (it != post_handlers.end())
  427. res.set_content(it->second(req.body), "application/json");
  428. });
  429. svr.Post("/api/pose/alarms/clear", [this](const httplib::Request& req, httplib::Response& res) {
  430. if (!authed(req)) { denyJson(res); return; }
  431. auto it = post_handlers.find("/api/pose/alarms/clear");
  432. if (it != post_handlers.end())
  433. res.set_content(it->second(req.body), "application/json");
  434. });
  435. svr.Get("/api/pose/alarms/count", [this](const httplib::Request& req, httplib::Response& res) {
  436. if (!authed(req)) { denyJson(res); return; }
  437. auto it = get_handlers.find("/api/pose/alarms/count");
  438. if (it != get_handlers.end())
  439. res.set_content(it->second(), "application/json");
  440. });
  441. svr.Get("/api/relay/aliases", [this](const httplib::Request& req, httplib::Response& res) {
  442. if (!authed(req)) { denyJson(res); return; }
  443. auto it = get_handlers.find("/api/relay/aliases");
  444. if (it != get_handlers.end())
  445. res.set_content(it->second(), "application/json");
  446. });
  447. svr.Post("/api/relay/aliases", [this](const httplib::Request& req, httplib::Response& res) {
  448. if (!authed(req)) { denyJson(res); return; }
  449. auto it = post_handlers.find("/api/relay/aliases");
  450. if (it != post_handlers.end())
  451. res.set_content(it->second(req.body), "application/json");
  452. });
  453. svr.Get("/api/relay/actions", [this](const httplib::Request& req, httplib::Response& res) {
  454. if (!authed(req)) { denyJson(res); return; }
  455. auto it = get_handlers.find("/api/relay/actions");
  456. if (it != get_handlers.end())
  457. res.set_content(it->second(), "application/json");
  458. });
  459. svr.Post("/api/relay/actions/clear", [this](const httplib::Request& req, httplib::Response& res) {
  460. if (!authed(req)) { denyJson(res); return; }
  461. auto it = post_handlers.find("/api/relay/actions/clear");
  462. if (it != post_handlers.end())
  463. res.set_content(it->second(req.body), "application/json");
  464. });
  465. svr.Get("/api/alarms/count", [this](const httplib::Request& req, httplib::Response& res) {
  466. if (!authed(req)) { denyJson(res); return; }
  467. auto it = get_handlers.find("/api/alarms/count");
  468. if (it != get_handlers.end())
  469. res.set_content(it->second(), "application/json");
  470. });
  471. svr.Post("/api/alarms/delete", [this](const httplib::Request& req, httplib::Response& res) {
  472. if (!authed(req)) { denyJson(res); return; }
  473. auto it = post_handlers.find("/api/alarms/delete");
  474. if (it != post_handlers.end())
  475. res.set_content(it->second(req.body), "application/json");
  476. });
  477. svr.Post("/api/alarms/clear", [this](const httplib::Request& req, httplib::Response& res) {
  478. if (!authed(req)) { denyJson(res); return; }
  479. auto it = post_handlers.find("/api/alarms/clear");
  480. if (it != post_handlers.end())
  481. res.set_content(it->second(req.body), "application/json");
  482. });
  483. svr.Get("/api/pose/status", [this](const httplib::Request& req, httplib::Response& res) {
  484. if (!authed(req)) { denyJson(res); return; }
  485. auto it = get_handlers.find("/api/pose/status");
  486. if (it != get_handlers.end())
  487. res.set_content(it->second(), "application/json");
  488. });
  489. svr.Post("/api/pose/config", [this](const httplib::Request& req, httplib::Response& res) {
  490. if (!authed(req)) { denyJson(res); return; }
  491. auto it = post_handlers.find("/api/pose/config");
  492. if (it != post_handlers.end())
  493. res.set_content(it->second(req.body), "application/json");
  494. });
  495. svr.Get("/api/mjpeg/status", [this](const httplib::Request& req, httplib::Response& res) {
  496. if (!authed(req)) { denyJson(res); return; }
  497. auto it = get_handlers.find("/api/mjpeg/status");
  498. if (it != get_handlers.end())
  499. res.set_content(it->second(), "application/json");
  500. });
  501. svr.Post("/api/mjpeg/toggle", [this](const httplib::Request& req, httplib::Response& res) {
  502. if (!authed(req)) { denyJson(res); return; }
  503. auto it = post_handlers.find("/api/mjpeg/toggle");
  504. if (it != post_handlers.end())
  505. res.set_content(it->second(req.body), "application/json");
  506. });
  507. }
  508. };
  509. WebServer::WebServer() : impl_(new Impl()) {}
  510. void WebServer::start() {
  511. impl_->host = host_;
  512. impl_->port = port_;
  513. impl_->setupRoutes();
  514. running_ = true;
  515. server_thread_ = thread([this]() {
  516. cout << "[Web] 服务启动: http://" << host_ << ":" << port_ << endl;
  517. impl_->svr.listen(host_, port_);
  518. running_ = false;
  519. });
  520. }
  521. void WebServer::stop() {
  522. if (impl_) {
  523. impl_->stopping = true; // 先通知推流线程退出, 避免join卡死
  524. impl_->svr.stop();
  525. }
  526. if (server_thread_.joinable()) server_thread_.join();
  527. running_ = false;
  528. }
  529. void WebServer::setMjpegEnabled(bool en) {
  530. if (impl_) impl_->mjpeg_enabled.store(en);
  531. }
  532. bool WebServer::mjpegEnabled() const {
  533. return impl_ ? impl_->mjpeg_enabled.load() : true;
  534. }
  535. void WebServer::setGetHandler(const string& path, function<string()> handler) {
  536. impl_->get_handlers[path] = handler;
  537. }
  538. void WebServer::setPostHandler(const string& path, function<string(const string&)> handler) {
  539. impl_->post_handlers[path] = handler;
  540. }
  541. void WebServer::setFrameProvider(int channel, function<cv::Mat()> provider) {
  542. if (channel >= 0)
  543. impl_->frame_providers[channel] = std::move(provider);
  544. }
  545. void WebServer::setFrameSeqProvider(int channel, function<uint64_t()> provider) {
  546. if (channel >= 0)
  547. impl_->seq_providers[channel] = std::move(provider);
  548. }
  549. void WebServer::removeFrameProvider(int channel) {
  550. impl_->frame_providers.erase(channel);
  551. impl_->seq_providers.erase(channel);
  552. }
  553. void WebServer::setGetHandlerQ(const std::string& path,
  554. std::function<std::string(const std::map<std::string, std::string>&)> handler) {
  555. impl_->getq_handlers[path] = std::move(handler);
  556. }
  557. void WebServer::setAuthHooks(AuthHooks hooks) {
  558. impl_->hooks_ = std::move(hooks);
  559. impl_->hooks_set_ = true;
  560. }