web_server.cpp 26 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597
  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. while (!stopping.load() && mjpeg_enabled.load() && sink.is_writable()) {
  232. uint64_t seq = seqfn ? seqfn() : 0;
  233. if (seq != 0 && seq == last_seq) {
  234. // 帧未更新, 跳过重复JPEG编码
  235. std::this_thread::sleep_for(std::chrono::milliseconds(15));
  236. continue;
  237. }
  238. cv::Mat frame = fp->second();
  239. if (!frame.empty()) {
  240. std::vector<uchar> buf;
  241. std::vector<int> params = {cv::IMWRITE_JPEG_QUALITY, 60};
  242. cv::imencode(".jpg", frame, buf, params);
  243. std::string header = "--mjpegboundary\r\n"
  244. "Content-Type: image/jpeg\r\n"
  245. "Content-Length: " + std::to_string(buf.size()) + "\r\n\r\n";
  246. if (!sink.write(header.data(), header.size())) return false;
  247. if (!sink.write(reinterpret_cast<char*>(buf.data()), buf.size())) return false;
  248. if (!sink.write("\r\n", 2)) return false;
  249. last_seq = seq;
  250. }
  251. std::this_thread::sleep_for(std::chrono::milliseconds(33));
  252. }
  253. return true;
  254. });
  255. });
  256. svr.Get("/data/screenshots/:file", [](const httplib::Request& req, httplib::Response& res) {
  257. string filename = req.path_params.at("file");
  258. if (filename.find("..") != string::npos) {
  259. res.status = 403;
  260. return;
  261. }
  262. string path = "data/screenshots/" + filename;
  263. ifstream f(path, ios::binary);
  264. if (!f.is_open()) {
  265. res.status = 404;
  266. return;
  267. }
  268. string content((istreambuf_iterator<char>(f)), istreambuf_iterator<char>());
  269. res.set_content(content, "image/jpeg");
  270. });
  271. svr.Get("/api/status", [this](const httplib::Request& req, httplib::Response& res) {
  272. if (!authed(req)) { denyJson(res); return; }
  273. auto it = get_handlers.find("/api/status");
  274. if (it != get_handlers.end())
  275. res.set_content(it->second(), "application/json");
  276. });
  277. svr.Get("/api/fences", [this](const httplib::Request& req, httplib::Response& res) {
  278. if (!authed(req)) { denyJson(res); return; }
  279. auto it = get_handlers.find("/api/fences");
  280. if (it != get_handlers.end())
  281. res.set_content(it->second(), "application/json");
  282. });
  283. svr.Post("/api/fences", [this](const httplib::Request& req, httplib::Response& res) {
  284. if (!authed(req)) { denyJson(res); return; }
  285. auto it = post_handlers.find("/api/fences");
  286. if (it != post_handlers.end())
  287. res.set_content(it->second(req.body), "application/json");
  288. });
  289. svr.Post("/api/config", [this](const httplib::Request& req, httplib::Response& res) {
  290. if (!authed(req)) { denyJson(res); return; }
  291. auto it = post_handlers.find("/api/config");
  292. if (it != post_handlers.end())
  293. res.set_content(it->second(req.body), "application/json");
  294. });
  295. svr.Post("/api/relay", [this](const httplib::Request& req, httplib::Response& res) {
  296. if (!authed(req)) { denyJson(res); return; }
  297. auto it = post_handlers.find("/api/relay");
  298. if (it != post_handlers.end())
  299. res.set_content(it->second(req.body), "application/json");
  300. });
  301. svr.Get("/api/relay/status", [this](const httplib::Request& req, httplib::Response& res) {
  302. if (!authed(req)) { denyJson(res); return; }
  303. auto it = get_handlers.find("/api/relay/status");
  304. if (it != get_handlers.end())
  305. res.set_content(it->second(), "application/json");
  306. });
  307. svr.Post("/api/relay/config", [this](const httplib::Request& req, httplib::Response& res) {
  308. if (!authed(req)) { denyJson(res); return; }
  309. auto it = post_handlers.find("/api/relay/config");
  310. if (it != post_handlers.end())
  311. res.set_content(it->second(req.body), "application/json");
  312. });
  313. svr.Post("/api/relay/mode", [this](const httplib::Request& req, httplib::Response& res) {
  314. if (!authed(req)) { denyJson(res); return; }
  315. auto it = post_handlers.find("/api/relay/mode");
  316. if (it != post_handlers.end())
  317. res.set_content(it->second(req.body), "application/json");
  318. });
  319. svr.Get("/api/relay/bindings", [this](const httplib::Request& req, httplib::Response& res) {
  320. if (!authed(req)) { denyJson(res); return; }
  321. auto it = get_handlers.find("/api/relay/bindings");
  322. if (it != get_handlers.end())
  323. res.set_content(it->second(), "application/json");
  324. });
  325. svr.Post("/api/relay/bindings", [this](const httplib::Request& req, httplib::Response& res) {
  326. if (!authed(req)) { denyJson(res); return; }
  327. auto it = post_handlers.find("/api/relay/bindings");
  328. if (it != post_handlers.end())
  329. res.set_content(it->second(req.body), "application/json");
  330. });
  331. svr.Get("/api/system", [this](const httplib::Request& req, httplib::Response& res) {
  332. if (!authed(req)) { denyJson(res); return; }
  333. auto it = get_handlers.find("/api/system");
  334. if (it != get_handlers.end())
  335. res.set_content(it->second(), "application/json");
  336. });
  337. svr.Post("/api/reboot", [this](const httplib::Request& req, httplib::Response& res) {
  338. if (!authed(req)) { denyJson(res); return; }
  339. auto it = post_handlers.find("/api/reboot");
  340. if (it != post_handlers.end())
  341. res.set_content(it->second(req.body), "application/json");
  342. });
  343. svr.Post("/api/channels/save", [this](const httplib::Request& req, httplib::Response& res) {
  344. if (!authed(req)) { denyJson(res); return; }
  345. auto it = post_handlers.find("/api/channels/save");
  346. if (it != post_handlers.end())
  347. res.set_content(it->second(req.body), "application/json");
  348. });
  349. svr.Post("/api/channels/delete", [this](const httplib::Request& req, httplib::Response& res) {
  350. if (!authed(req)) { denyJson(res); return; }
  351. auto it = post_handlers.find("/api/channels/delete");
  352. if (it != post_handlers.end())
  353. res.set_content(it->second(req.body), "application/json");
  354. });
  355. svr.Post("/api/channels/test", [this](const httplib::Request& req, httplib::Response& res) {
  356. if (!authed(req)) { denyJson(res); return; }
  357. auto it = post_handlers.find("/api/channels/test");
  358. if (it != post_handlers.end())
  359. res.set_content(it->second(req.body), "application/json");
  360. });
  361. svr.Get("/api/buckets", [this](const httplib::Request& req, httplib::Response& res) {
  362. if (!authed(req)) { denyJson(res); return; }
  363. auto it = get_handlers.find("/api/buckets");
  364. if (it != get_handlers.end())
  365. res.set_content(it->second(), "application/json");
  366. });
  367. svr.Post("/api/buckets", [this](const httplib::Request& req, httplib::Response& res) {
  368. if (!authed(req)) { denyJson(res); return; }
  369. auto it = post_handlers.find("/api/buckets");
  370. if (it != post_handlers.end())
  371. res.set_content(it->second(req.body), "application/json");
  372. });
  373. svr.Get("/api/buckets/status", [this](const httplib::Request& req, httplib::Response& res) {
  374. if (!authed(req)) { denyJson(res); return; }
  375. auto it = get_handlers.find("/api/buckets/status");
  376. if (it != get_handlers.end())
  377. res.set_content(it->second(), "application/json");
  378. });
  379. svr.Post("/api/buckets/activate", [this](const httplib::Request& req, httplib::Response& res) {
  380. if (!authed(req)) { denyJson(res); return; }
  381. auto it = post_handlers.find("/api/buckets/activate");
  382. if (it != post_handlers.end())
  383. res.set_content(it->second(req.body), "application/json");
  384. });
  385. svr.Post("/api/buckets/mode", [this](const httplib::Request& req, httplib::Response& res) {
  386. if (!authed(req)) { denyJson(res); return; }
  387. auto it = post_handlers.find("/api/buckets/mode");
  388. if (it != post_handlers.end())
  389. res.set_content(it->second(req.body), "application/json");
  390. });
  391. svr.Get("/api/module/config", [this](const httplib::Request& req, httplib::Response& res) {
  392. if (!authed(req)) { denyJson(res); return; }
  393. auto it = get_handlers.find("/api/module/config");
  394. if (it != get_handlers.end())
  395. res.set_content(it->second(), "application/json");
  396. });
  397. svr.Post("/api/module/config", [this](const httplib::Request& req, httplib::Response& res) {
  398. if (!authed(req)) { denyJson(res); return; }
  399. auto it = post_handlers.find("/api/module/config");
  400. if (it != post_handlers.end())
  401. res.set_content(it->second(req.body), "application/json");
  402. });
  403. svr.Get("/api/alarms", [this](const httplib::Request& req, httplib::Response& res) {
  404. if (!authed(req)) { denyJson(res); return; }
  405. auto it = getq_handlers.find("/api/alarms");
  406. if (it != getq_handlers.end()) {
  407. std::map<std::string, std::string> ps;
  408. for (auto& p : req.params) ps[p.first] = p.second;
  409. res.set_content(it->second(ps), "application/json");
  410. }
  411. });
  412. svr.Get("/api/relay/aliases", [this](const httplib::Request& req, httplib::Response& res) {
  413. if (!authed(req)) { denyJson(res); return; }
  414. auto it = get_handlers.find("/api/relay/aliases");
  415. if (it != get_handlers.end())
  416. res.set_content(it->second(), "application/json");
  417. });
  418. svr.Post("/api/relay/aliases", [this](const httplib::Request& req, httplib::Response& res) {
  419. if (!authed(req)) { denyJson(res); return; }
  420. auto it = post_handlers.find("/api/relay/aliases");
  421. if (it != post_handlers.end())
  422. res.set_content(it->second(req.body), "application/json");
  423. });
  424. svr.Get("/api/relay/actions", [this](const httplib::Request& req, httplib::Response& res) {
  425. if (!authed(req)) { denyJson(res); return; }
  426. auto it = get_handlers.find("/api/relay/actions");
  427. if (it != get_handlers.end())
  428. res.set_content(it->second(), "application/json");
  429. });
  430. svr.Post("/api/relay/actions/clear", [this](const httplib::Request& req, httplib::Response& res) {
  431. if (!authed(req)) { denyJson(res); return; }
  432. auto it = post_handlers.find("/api/relay/actions/clear");
  433. if (it != post_handlers.end())
  434. res.set_content(it->second(req.body), "application/json");
  435. });
  436. svr.Get("/api/alarms/count", [this](const httplib::Request& req, httplib::Response& res) {
  437. if (!authed(req)) { denyJson(res); return; }
  438. auto it = get_handlers.find("/api/alarms/count");
  439. if (it != get_handlers.end())
  440. res.set_content(it->second(), "application/json");
  441. });
  442. svr.Post("/api/alarms/delete", [this](const httplib::Request& req, httplib::Response& res) {
  443. if (!authed(req)) { denyJson(res); return; }
  444. auto it = post_handlers.find("/api/alarms/delete");
  445. if (it != post_handlers.end())
  446. res.set_content(it->second(req.body), "application/json");
  447. });
  448. svr.Post("/api/alarms/clear", [this](const httplib::Request& req, httplib::Response& res) {
  449. if (!authed(req)) { denyJson(res); return; }
  450. auto it = post_handlers.find("/api/alarms/clear");
  451. if (it != post_handlers.end())
  452. res.set_content(it->second(req.body), "application/json");
  453. });
  454. svr.Get("/api/pose/status", [this](const httplib::Request& req, httplib::Response& res) {
  455. if (!authed(req)) { denyJson(res); return; }
  456. auto it = get_handlers.find("/api/pose/status");
  457. if (it != get_handlers.end())
  458. res.set_content(it->second(), "application/json");
  459. });
  460. svr.Post("/api/pose/config", [this](const httplib::Request& req, httplib::Response& res) {
  461. if (!authed(req)) { denyJson(res); return; }
  462. auto it = post_handlers.find("/api/pose/config");
  463. if (it != post_handlers.end())
  464. res.set_content(it->second(req.body), "application/json");
  465. });
  466. svr.Get("/api/mjpeg/status", [this](const httplib::Request& req, httplib::Response& res) {
  467. if (!authed(req)) { denyJson(res); return; }
  468. auto it = get_handlers.find("/api/mjpeg/status");
  469. if (it != get_handlers.end())
  470. res.set_content(it->second(), "application/json");
  471. });
  472. svr.Post("/api/mjpeg/toggle", [this](const httplib::Request& req, httplib::Response& res) {
  473. if (!authed(req)) { denyJson(res); return; }
  474. auto it = post_handlers.find("/api/mjpeg/toggle");
  475. if (it != post_handlers.end())
  476. res.set_content(it->second(req.body), "application/json");
  477. });
  478. }
  479. };
  480. WebServer::WebServer() : impl_(new Impl()) {}
  481. void WebServer::start() {
  482. impl_->host = host_;
  483. impl_->port = port_;
  484. impl_->setupRoutes();
  485. running_ = true;
  486. server_thread_ = thread([this]() {
  487. cout << "[Web] 服务启动: http://" << host_ << ":" << port_ << endl;
  488. impl_->svr.listen(host_, port_);
  489. running_ = false;
  490. });
  491. }
  492. void WebServer::stop() {
  493. if (impl_) {
  494. impl_->stopping = true; // 先通知推流线程退出, 避免join卡死
  495. impl_->svr.stop();
  496. }
  497. if (server_thread_.joinable()) server_thread_.join();
  498. running_ = false;
  499. }
  500. void WebServer::setMjpegEnabled(bool en) {
  501. if (impl_) impl_->mjpeg_enabled.store(en);
  502. }
  503. bool WebServer::mjpegEnabled() const {
  504. return impl_ ? impl_->mjpeg_enabled.load() : true;
  505. }
  506. void WebServer::setGetHandler(const string& path, function<string()> handler) {
  507. impl_->get_handlers[path] = handler;
  508. }
  509. void WebServer::setPostHandler(const string& path, function<string(const string&)> handler) {
  510. impl_->post_handlers[path] = handler;
  511. }
  512. void WebServer::setFrameProvider(int channel, function<cv::Mat()> provider) {
  513. if (channel >= 0)
  514. impl_->frame_providers[channel] = std::move(provider);
  515. }
  516. void WebServer::setFrameSeqProvider(int channel, function<uint64_t()> provider) {
  517. if (channel >= 0)
  518. impl_->seq_providers[channel] = std::move(provider);
  519. }
  520. void WebServer::removeFrameProvider(int channel) {
  521. impl_->frame_providers.erase(channel);
  522. impl_->seq_providers.erase(channel);
  523. }
  524. void WebServer::setGetHandlerQ(const std::string& path,
  525. std::function<std::string(const std::map<std::string, std::string>&)> handler) {
  526. impl_->getq_handlers[path] = std::move(handler);
  527. }
  528. void WebServer::setAuthHooks(AuthHooks hooks) {
  529. impl_->hooks_ = std::move(hooks);
  530. impl_->hooks_set_ = true;
  531. }