http_server.cpp 36 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996
  1. #include "http_server.h"
  2. #include <iostream>
  3. #include <fstream>
  4. #include <sstream>
  5. #include <filesystem>
  6. #include <algorithm>
  7. #include <iomanip>
  8. #include <ctime>
  9. namespace jtjai_media {
  10. HttpServer::HttpServer(const std::string& output_directory, int port)
  11. : output_directory_(output_directory)
  12. , port_(port)
  13. , is_running_(false) {
  14. }
  15. HttpServer::~HttpServer() {
  16. stop();
  17. }
  18. bool HttpServer::start() {
  19. if (is_running_.load()) {
  20. std::cerr << "HTTP服务器已经在运行中" << std::endl;
  21. return false;
  22. }
  23. is_running_.store(true);
  24. server_thread_ = std::make_unique<std::thread>(&HttpServer::server_main_loop, this);
  25. std::cout << "HTTP服务器已启动,监听端口: " << port_ << std::endl;
  26. std::cout << "API端点:" << std::endl;
  27. std::cout << " GET /api/videos - 列出所有视频" << std::endl;
  28. std::cout << " GET /api/timestamps - 列出所有时间戳目录" << std::endl;
  29. std::cout << " GET /api/videos/{timestamp} - 列出指定时间戳目录的视频" << std::endl;
  30. std::cout << " GET /api/reports - 列出所有轮询报告" << std::endl;
  31. std::cout << " GET /api/report/{timestamp} - 获取指定时间戳的轮询报告" << std::endl;
  32. std::cout << " DELETE /api/video?path={path} - 删除指定视频文件" << std::endl;
  33. std::cout << " DELETE /api/timestamp/{timestamp} - 删除指定时间戳目录及其所有文件" << std::endl;
  34. std::cout << "Web界面:" << std::endl;
  35. std::cout << " GET / - 主页" << std::endl;
  36. std::cout << " GET /manager - 视频管理界面" << std::endl;
  37. return true;
  38. }
  39. void HttpServer::stop() {
  40. if (!is_running_.load()) {
  41. return;
  42. }
  43. std::cout << "正在停止HTTP服务器..." << std::endl;
  44. is_running_.store(false);
  45. if (server_thread_ && server_thread_->joinable()) {
  46. server_thread_->join();
  47. }
  48. std::cout << "HTTP服务器已停止" << std::endl;
  49. }
  50. void HttpServer::server_main_loop() {
  51. try {
  52. boost::asio::io_context io_context;
  53. boost::asio::ip::tcp::acceptor acceptor(
  54. io_context,
  55. boost::asio::ip::tcp::endpoint(boost::asio::ip::tcp::v4(), port_)
  56. );
  57. acceptor.listen();
  58. while (is_running_.load()) {
  59. boost::asio::ip::tcp::socket socket(io_context);
  60. // 设置接受超时
  61. acceptor.non_blocking(true);
  62. boost::system::error_code ec;
  63. acceptor.accept(socket, ec);
  64. if (!ec) {
  65. // 在新线程中处理请求
  66. std::thread([this](boost::asio::ip::tcp::socket sock) {
  67. handle_client(std::move(sock));
  68. }, std::move(socket)).detach();
  69. } else if (ec != boost::asio::error::would_block) {
  70. std::cerr << "接受连接错误: " << ec.message() << std::endl;
  71. }
  72. // 短暂休眠避免CPU占用过高
  73. std::this_thread::sleep_for(std::chrono::milliseconds(10));
  74. }
  75. } catch (const std::exception& e) {
  76. std::cerr << "HTTP服务器异常: " << e.what() << std::endl;
  77. }
  78. }
  79. void HttpServer::handle_client(boost::asio::ip::tcp::socket socket) {
  80. try {
  81. // 读取请求
  82. boost::asio::streambuf buffer;
  83. boost::system::error_code ec;
  84. // 读取直到双换行(HTTP请求结束标志)
  85. boost::asio::read_until(socket, buffer, "\r\n\r\n", ec);
  86. if (ec && ec != boost::asio::error::eof) {
  87. std::cerr << "读取请求错误: " << ec.message() << std::endl;
  88. return;
  89. }
  90. // 转换为字符串
  91. std::string request_data(
  92. boost::asio::buffers_begin(buffer.data()),
  93. boost::asio::buffers_begin(buffer.data()) + buffer.size()
  94. );
  95. // 解析请求
  96. HttpRequest request = parse_request(request_data);
  97. // 路由处理
  98. HttpResponse response = route_request(request);
  99. // 构建响应
  100. std::string response_data = build_response(response);
  101. // 发送响应
  102. boost::asio::write(socket, boost::asio::buffer(response_data), ec);
  103. if (ec) {
  104. std::cerr << "发送响应错误: " << ec.message() << std::endl;
  105. }
  106. // 关闭连接
  107. socket.close();
  108. } catch (const std::exception& e) {
  109. std::cerr << "处理客户端请求异常: " << e.what() << std::endl;
  110. }
  111. }
  112. HttpRequest HttpServer::parse_request(const std::string& request_data) {
  113. HttpRequest request;
  114. std::istringstream stream(request_data);
  115. std::string line;
  116. // 解析请求行
  117. if (std::getline(stream, line)) {
  118. std::istringstream request_line(line);
  119. std::string uri;
  120. request_line >> request.method >> uri;
  121. // 分离路径和查询字符串
  122. size_t query_pos = uri.find('?');
  123. if (query_pos != std::string::npos) {
  124. request.path = uri.substr(0, query_pos);
  125. request.query_string = uri.substr(query_pos + 1);
  126. } else {
  127. request.path = uri;
  128. }
  129. }
  130. // 解析请求头
  131. while (std::getline(stream, line) && line != "\r") {
  132. size_t colon_pos = line.find(':');
  133. if (colon_pos != std::string::npos) {
  134. std::string key = line.substr(0, colon_pos);
  135. std::string value = line.substr(colon_pos + 1);
  136. // 去除首尾空格
  137. value.erase(0, value.find_first_not_of(" \t\r\n"));
  138. value.erase(value.find_last_not_of(" \t\r\n") + 1);
  139. request.headers[key] = value;
  140. }
  141. }
  142. return request;
  143. }
  144. std::string HttpServer::build_response(const HttpResponse& response) {
  145. std::ostringstream oss;
  146. // 状态行
  147. oss << "HTTP/1.1 " << response.status_code << " " << response.status_message << "\r\n";
  148. // 响应头
  149. oss << "Content-Type: " << response.content_type << "\r\n";
  150. oss << "Content-Length: " << response.body.length() << "\r\n";
  151. oss << "Access-Control-Allow-Origin: *\r\n";
  152. oss << "Access-Control-Allow-Methods: GET, POST, DELETE, OPTIONS\r\n";
  153. oss << "Accept-Ranges: bytes\r\n"; // 支持Range请求
  154. oss << "Connection: close\r\n";
  155. oss << "\r\n";
  156. // 响应体
  157. oss << response.body;
  158. return oss.str();
  159. }
  160. HttpResponse HttpServer::route_request(const HttpRequest& request) {
  161. std::cout << request.method << " " << request.path;
  162. if (!request.query_string.empty()) {
  163. std::cout << "?" << request.query_string;
  164. }
  165. std::cout << std::endl;
  166. // OPTIONS请求处理(CORS预检)
  167. if (request.method == "OPTIONS") {
  168. return HttpResponse(200, "OK", "");
  169. }
  170. // 路由分发
  171. if (request.method == "GET" || request.method == "HEAD") {
  172. if (request.path == "/api/videos") {
  173. return handle_list_videos(request);
  174. } else if (request.path == "/api/timestamps") {
  175. return handle_list_timestamps(request);
  176. } else if (request.path == "/api/reports") {
  177. return handle_list_reports(request);
  178. } else if (request.path.find("/api/report/") == 0) {
  179. return handle_get_report(request);
  180. } else if (request.path.find("/api/videos/") == 0) {
  181. return handle_get_video_info(request);
  182. } else if (request.path.find("/videos/") == 0) {
  183. // 处理视频文件访问
  184. auto response = handle_video_file(request);
  185. // 如果是HEAD请求,只返回头部,不返回体
  186. if (request.method == "HEAD") {
  187. response.body.clear();
  188. }
  189. return response;
  190. } else {
  191. // 处理静态文件访问(HTML、CSS、JS等)
  192. return handle_static_file(request);
  193. }
  194. } else if (request.method == "DELETE") {
  195. if (request.path == "/api/video") {
  196. return handle_delete_video(request);
  197. } else if (request.path.find("/api/timestamp/") == 0) {
  198. return handle_delete_timestamp(request);
  199. }
  200. }
  201. // 404未找到
  202. boost::json::object error_obj;
  203. error_obj["error"] = "Not Found";
  204. error_obj["message"] = "请求的资源不存在";
  205. return HttpResponse(404, "Not Found", boost::json::serialize(error_obj));
  206. }
  207. HttpResponse HttpServer::handle_list_videos(const HttpRequest& request) {
  208. try {
  209. auto query_params = parse_query_string(request.query_string);
  210. std::vector<VideoFileInfo> videos;
  211. // 检查是否指定了时间戳
  212. if (query_params.count("timestamp")) {
  213. std::string timestamp = query_params["timestamp"];
  214. std::string dir_path = output_directory_ + "/" + timestamp;
  215. videos = list_videos_in_directory(dir_path);
  216. } else {
  217. videos = list_all_videos();
  218. }
  219. // 构建JSON响应
  220. boost::json::array videos_array;
  221. for (const auto& video : videos) {
  222. boost::json::object video_obj;
  223. video_obj["filename"] = video.filename;
  224. video_obj["full_path"] = video.full_path;
  225. video_obj["timestamp_dir"] = video.timestamp_dir;
  226. video_obj["file_size"] = video.file_size;
  227. video_obj["created_time"] = video.created_time;
  228. video_obj["stream_index"] = video.stream_index;
  229. videos_array.push_back(video_obj);
  230. }
  231. boost::json::object result;
  232. result["count"] = videos.size();
  233. result["videos"] = videos_array;
  234. return HttpResponse(200, "OK", boost::json::serialize(result));
  235. } catch (const std::exception& e) {
  236. boost::json::object error_obj;
  237. error_obj["error"] = "Internal Server Error";
  238. error_obj["message"] = e.what();
  239. return HttpResponse(500, "Internal Server Error", boost::json::serialize(error_obj));
  240. }
  241. }
  242. HttpResponse HttpServer::handle_list_timestamps(const HttpRequest& request) {
  243. try {
  244. auto timestamps = list_timestamp_directories();
  245. // 构建JSON响应
  246. boost::json::array timestamps_array;
  247. for (const auto& timestamp : timestamps) {
  248. boost::json::object ts_obj;
  249. ts_obj["timestamp"] = timestamp;
  250. // 获取该目录下的视频数量
  251. std::string dir_path = output_directory_ + "/" + timestamp;
  252. auto videos = list_videos_in_directory(dir_path);
  253. ts_obj["video_count"] = videos.size();
  254. // 计算总大小
  255. int64_t total_size = 0;
  256. for (const auto& video : videos) {
  257. total_size += video.file_size;
  258. }
  259. ts_obj["total_size"] = total_size;
  260. timestamps_array.push_back(ts_obj);
  261. }
  262. boost::json::object result;
  263. result["count"] = timestamps.size();
  264. result["timestamps"] = timestamps_array;
  265. return HttpResponse(200, "OK", boost::json::serialize(result));
  266. } catch (const std::exception& e) {
  267. boost::json::object error_obj;
  268. error_obj["error"] = "Internal Server Error";
  269. error_obj["message"] = e.what();
  270. return HttpResponse(500, "Internal Server Error", boost::json::serialize(error_obj));
  271. }
  272. }
  273. HttpResponse HttpServer::handle_delete_video(const HttpRequest& request) {
  274. try {
  275. auto query_params = parse_query_string(request.query_string);
  276. if (!query_params.count("path")) {
  277. boost::json::object error_obj;
  278. error_obj["error"] = "Bad Request";
  279. error_obj["message"] = "缺少path参数";
  280. return HttpResponse(400, "Bad Request", boost::json::serialize(error_obj));
  281. }
  282. std::string file_path = query_params["path"];
  283. // 安全检查:确保文件路径在输出目录内
  284. std::filesystem::path abs_path = std::filesystem::absolute(file_path);
  285. std::filesystem::path abs_output = std::filesystem::absolute(output_directory_);
  286. if (abs_path.string().find(abs_output.string()) != 0) {
  287. boost::json::object error_obj;
  288. error_obj["error"] = "Forbidden";
  289. error_obj["message"] = "无权删除该文件";
  290. return HttpResponse(403, "Forbidden", boost::json::serialize(error_obj));
  291. }
  292. bool success = delete_file(file_path);
  293. boost::json::object result;
  294. result["success"] = success;
  295. result["path"] = file_path;
  296. if (success) {
  297. result["message"] = "文件删除成功";
  298. return HttpResponse(200, "OK", boost::json::serialize(result));
  299. } else {
  300. result["message"] = "文件删除失败";
  301. return HttpResponse(500, "Internal Server Error", boost::json::serialize(result));
  302. }
  303. } catch (const std::exception& e) {
  304. boost::json::object error_obj;
  305. error_obj["error"] = "Internal Server Error";
  306. error_obj["message"] = e.what();
  307. return HttpResponse(500, "Internal Server Error", boost::json::serialize(error_obj));
  308. }
  309. }
  310. HttpResponse HttpServer::handle_delete_timestamp(const HttpRequest& request) {
  311. try {
  312. // 从路径中提取时间戳 /api/timestamp/{timestamp}
  313. std::string path = request.path;
  314. size_t pos = path.find_last_of('/');
  315. if (pos == std::string::npos) {
  316. boost::json::object error_obj;
  317. error_obj["error"] = "Bad Request";
  318. error_obj["message"] = "无效的请求路径";
  319. return HttpResponse(400, "Bad Request", boost::json::serialize(error_obj));
  320. }
  321. std::string timestamp = path.substr(pos + 1);
  322. std::string dir_path = output_directory_ + "/" + timestamp;
  323. // 安全检查
  324. std::filesystem::path abs_path = std::filesystem::absolute(dir_path);
  325. std::filesystem::path abs_output = std::filesystem::absolute(output_directory_);
  326. if (abs_path.string().find(abs_output.string()) != 0) {
  327. boost::json::object error_obj;
  328. error_obj["error"] = "Forbidden";
  329. error_obj["message"] = "无权删除该目录";
  330. return HttpResponse(403, "Forbidden", boost::json::serialize(error_obj));
  331. }
  332. bool success = delete_directory(dir_path);
  333. boost::json::object result;
  334. result["success"] = success;
  335. result["timestamp"] = timestamp;
  336. result["path"] = dir_path;
  337. if (success) {
  338. result["message"] = "目录删除成功";
  339. return HttpResponse(200, "OK", boost::json::serialize(result));
  340. } else {
  341. result["message"] = "目录删除失败";
  342. return HttpResponse(500, "Internal Server Error", boost::json::serialize(result));
  343. }
  344. } catch (const std::exception& e) {
  345. boost::json::object error_obj;
  346. error_obj["error"] = "Internal Server Error";
  347. error_obj["message"] = e.what();
  348. return HttpResponse(500, "Internal Server Error", boost::json::serialize(error_obj));
  349. }
  350. }
  351. HttpResponse HttpServer::handle_get_video_info(const HttpRequest& request) {
  352. // 从路径中提取时间戳 /api/videos/{timestamp}
  353. std::string path = request.path;
  354. size_t pos = path.find_last_of('/');
  355. if (pos == std::string::npos) {
  356. boost::json::object error_obj;
  357. error_obj["error"] = "Bad Request";
  358. error_obj["message"] = "无效的请求路径";
  359. return HttpResponse(400, "Bad Request", boost::json::serialize(error_obj));
  360. }
  361. std::string timestamp = path.substr(pos + 1);
  362. std::string dir_path = output_directory_ + "/" + timestamp;
  363. auto videos = list_videos_in_directory(dir_path);
  364. boost::json::array videos_array;
  365. for (const auto& video : videos) {
  366. boost::json::object video_obj;
  367. video_obj["filename"] = video.filename;
  368. video_obj["full_path"] = video.full_path;
  369. video_obj["timestamp_dir"] = video.timestamp_dir;
  370. video_obj["file_size"] = video.file_size;
  371. video_obj["created_time"] = video.created_time;
  372. video_obj["stream_index"] = video.stream_index;
  373. videos_array.push_back(video_obj);
  374. }
  375. boost::json::object result;
  376. result["timestamp"] = timestamp;
  377. result["count"] = videos.size();
  378. result["videos"] = videos_array;
  379. return HttpResponse(200, "OK", boost::json::serialize(result));
  380. }
  381. HttpResponse HttpServer::handle_video_file(const HttpRequest& request) {
  382. try {
  383. // 从路径中提取文件路径 /videos/{timestamp}/{filename}
  384. std::string path = request.path;
  385. size_t pos = path.find("/videos/");
  386. if (pos == std::string::npos) {
  387. boost::json::object error_obj;
  388. error_obj["error"] = "Bad Request";
  389. error_obj["message"] = "无效的请求路径";
  390. return HttpResponse(400, "Bad Request", boost::json::serialize(error_obj));
  391. }
  392. // 获取相对路径(URL解码)
  393. std::string encoded_path = path.substr(pos + 8); // 跳过 "/videos/"
  394. std::string relative_path = url_decode(encoded_path);
  395. // 构建完整文件路径
  396. std::string file_path = output_directory_ + "/" + relative_path;
  397. std::cout << "请求视频文件: " << file_path << std::endl;
  398. // 安全检查:确保文件路径在输出目录内
  399. std::filesystem::path abs_path = std::filesystem::absolute(file_path);
  400. std::filesystem::path abs_output = std::filesystem::absolute(output_directory_);
  401. if (abs_path.string().find(abs_output.string()) != 0) {
  402. boost::json::object error_obj;
  403. error_obj["error"] = "Forbidden";
  404. error_obj["message"] = "无权访问该文件";
  405. return HttpResponse(403, "Forbidden", boost::json::serialize(error_obj));
  406. }
  407. // 检查文件是否存在
  408. if (!std::filesystem::exists(file_path)) {
  409. boost::json::object error_obj;
  410. error_obj["error"] = "Not Found";
  411. error_obj["message"] = "文件不存在: " + file_path;
  412. return HttpResponse(404, "Not Found", boost::json::serialize(error_obj));
  413. }
  414. // 获取文件大小
  415. size_t file_size = std::filesystem::file_size(file_path);
  416. // 读取文件内容
  417. std::ifstream file(file_path, std::ios::binary);
  418. if (!file.is_open()) {
  419. boost::json::object error_obj;
  420. error_obj["error"] = "Internal Server Error";
  421. error_obj["message"] = "无法打开文件";
  422. return HttpResponse(500, "Internal Server Error", boost::json::serialize(error_obj));
  423. }
  424. // 读取全部文件内容
  425. std::string content(file_size, '\0');
  426. file.read(&content[0], file_size);
  427. file.close();
  428. std::cout << "发送视频文件: " << file_path << " (" << file_size << " bytes)" << std::endl;
  429. // 构建响应
  430. HttpResponse response;
  431. response.status_code = 200;
  432. response.status_message = "OK";
  433. // 根据文件扩展名设置Content-Type
  434. std::string ext = std::filesystem::path(file_path).extension().string();
  435. if (ext == ".mp4") {
  436. response.content_type = "video/mp4";
  437. } else if (ext == ".avi") {
  438. response.content_type = "video/x-msvideo";
  439. } else if (ext == ".mkv") {
  440. response.content_type = "video/x-matroska";
  441. } else if (ext == ".flv") {
  442. response.content_type = "video/x-flv";
  443. } else {
  444. response.content_type = "application/octet-stream";
  445. }
  446. response.body = content;
  447. return response;
  448. } catch (const std::exception& e) {
  449. boost::json::object error_obj;
  450. error_obj["error"] = "Internal Server Error";
  451. error_obj["message"] = e.what();
  452. return HttpResponse(500, "Internal Server Error", boost::json::serialize(error_obj));
  453. }
  454. }
  455. std::vector<std::string> HttpServer::list_timestamp_directories() {
  456. std::vector<std::string> timestamps;
  457. try {
  458. if (!std::filesystem::exists(output_directory_)) {
  459. return timestamps;
  460. }
  461. for (const auto& entry : std::filesystem::directory_iterator(output_directory_)) {
  462. if (entry.is_directory()) {
  463. timestamps.push_back(entry.path().filename().string());
  464. }
  465. }
  466. // 按时间戳排序(最新的在前)
  467. std::sort(timestamps.begin(), timestamps.end(), std::greater<std::string>());
  468. } catch (const std::exception& e) {
  469. std::cerr << "列出时间戳目录错误: " << e.what() << std::endl;
  470. }
  471. return timestamps;
  472. }
  473. std::vector<VideoFileInfo> HttpServer::list_videos_in_directory(const std::string& dir_path) {
  474. std::vector<VideoFileInfo> videos;
  475. try {
  476. if (!std::filesystem::exists(dir_path)) {
  477. return videos;
  478. }
  479. for (const auto& entry : std::filesystem::directory_iterator(dir_path)) {
  480. if (entry.is_regular_file()) {
  481. std::string filename = entry.path().filename().string();
  482. std::string ext = entry.path().extension().string();
  483. // 只列出视频文件
  484. if (ext == ".mp4" || ext == ".avi" || ext == ".mkv" || ext == ".flv") {
  485. VideoFileInfo info;
  486. info.filename = filename;
  487. info.full_path = entry.path().string();
  488. info.timestamp_dir = std::filesystem::path(dir_path).filename().string();
  489. info.file_size = get_file_size(info.full_path);
  490. info.created_time = get_file_created_time(info.full_path);
  491. // 从文件名提取流索引
  492. size_t stream_pos = filename.find("stream");
  493. if (stream_pos != std::string::npos) {
  494. size_t num_start = stream_pos + 6;
  495. size_t num_end = filename.find_first_not_of("0123456789", num_start);
  496. if (num_end != std::string::npos) {
  497. info.stream_index = filename.substr(num_start, num_end - num_start);
  498. }
  499. }
  500. videos.push_back(info);
  501. }
  502. }
  503. }
  504. // 按文件名排序
  505. std::sort(videos.begin(), videos.end(),
  506. [](const VideoFileInfo& a, const VideoFileInfo& b) {
  507. return a.filename < b.filename;
  508. });
  509. } catch (const std::exception& e) {
  510. std::cerr << "列出视频文件错误: " << e.what() << std::endl;
  511. }
  512. return videos;
  513. }
  514. std::vector<VideoFileInfo> HttpServer::list_all_videos() {
  515. std::vector<VideoFileInfo> all_videos;
  516. auto timestamps = list_timestamp_directories();
  517. for (const auto& timestamp : timestamps) {
  518. std::string dir_path = output_directory_ + "/" + timestamp;
  519. auto videos = list_videos_in_directory(dir_path);
  520. all_videos.insert(all_videos.end(), videos.begin(), videos.end());
  521. }
  522. return all_videos;
  523. }
  524. bool HttpServer::delete_file(const std::string& file_path) {
  525. try {
  526. if (std::filesystem::exists(file_path)) {
  527. std::filesystem::remove(file_path);
  528. std::cout << "已删除文件: " << file_path << std::endl;
  529. return true;
  530. }
  531. return false;
  532. } catch (const std::exception& e) {
  533. std::cerr << "删除文件错误: " << e.what() << std::endl;
  534. return false;
  535. }
  536. }
  537. bool HttpServer::delete_directory(const std::string& dir_path) {
  538. try {
  539. if (std::filesystem::exists(dir_path)) {
  540. std::filesystem::remove_all(dir_path);
  541. std::cout << "已删除目录: " << dir_path << std::endl;
  542. return true;
  543. }
  544. return false;
  545. } catch (const std::exception& e) {
  546. std::cerr << "删除目录错误: " << e.what() << std::endl;
  547. return false;
  548. }
  549. }
  550. std::string HttpServer::get_file_created_time(const std::string& file_path) {
  551. try {
  552. auto ftime = std::filesystem::last_write_time(file_path);
  553. auto sctp = std::chrono::time_point_cast<std::chrono::system_clock::duration>(
  554. ftime - std::filesystem::file_time_type::clock::now() + std::chrono::system_clock::now()
  555. );
  556. auto time_t = std::chrono::system_clock::to_time_t(sctp);
  557. auto tm = *std::localtime(&time_t);
  558. std::ostringstream oss;
  559. oss << std::put_time(&tm, "%Y-%m-%d %H:%M:%S");
  560. return oss.str();
  561. } catch (const std::exception& e) {
  562. return "Unknown";
  563. }
  564. }
  565. int64_t HttpServer::get_file_size(const std::string& file_path) {
  566. try {
  567. return std::filesystem::file_size(file_path);
  568. } catch (const std::exception& e) {
  569. return 0;
  570. }
  571. }
  572. std::map<std::string, std::string> HttpServer::parse_query_string(const std::string& query) {
  573. std::map<std::string, std::string> params;
  574. if (query.empty()) {
  575. return params;
  576. }
  577. std::istringstream stream(query);
  578. std::string pair;
  579. while (std::getline(stream, pair, '&')) {
  580. size_t eq_pos = pair.find('=');
  581. if (eq_pos != std::string::npos) {
  582. std::string key = pair.substr(0, eq_pos);
  583. std::string value = pair.substr(eq_pos + 1);
  584. params[key] = value;
  585. }
  586. }
  587. return params;
  588. }
  589. std::string HttpServer::url_decode(const std::string& str) {
  590. std::string result;
  591. result.reserve(str.size());
  592. for (size_t i = 0; i < str.size(); ++i) {
  593. if (str[i] == '%') {
  594. if (i + 2 < str.size()) {
  595. int value;
  596. std::istringstream is(str.substr(i + 1, 2));
  597. if (is >> std::hex >> value) {
  598. result += static_cast<char>(value);
  599. i += 2;
  600. } else {
  601. result += str[i];
  602. }
  603. } else {
  604. result += str[i];
  605. }
  606. } else if (str[i] == '+') {
  607. result += ' ';
  608. } else {
  609. result += str[i];
  610. }
  611. }
  612. return result;
  613. }
  614. HttpResponse HttpServer::handle_static_file(const HttpRequest& request) {
  615. try {
  616. std::string file_path;
  617. std::string content_type = "text/html";
  618. // 路由映射
  619. if (request.path == "/" || request.path == "/index.html") {
  620. // 主页 - 显示系统信息
  621. return generate_index_page();
  622. } else if (request.path == "/manager" || request.path == "/manager.html") {
  623. // 视频管理界面
  624. file_path = "video_manager.html";
  625. content_type = "text/html";
  626. } else if (request.path == "/api" || request.path == "/api.html") {
  627. // API文档
  628. return generate_api_doc();
  629. } else {
  630. // 404
  631. boost::json::object error_obj;
  632. error_obj["error"] = "Not Found";
  633. error_obj["message"] = "请求的资源不存在";
  634. return HttpResponse(404, "Not Found", boost::json::serialize(error_obj));
  635. }
  636. // 读取静态文件
  637. if (!std::filesystem::exists(file_path)) {
  638. boost::json::object error_obj;
  639. error_obj["error"] = "Not Found";
  640. error_obj["message"] = "文件不存在: " + file_path;
  641. return HttpResponse(404, "Not Found", boost::json::serialize(error_obj));
  642. }
  643. std::ifstream file(file_path);
  644. if (!file.is_open()) {
  645. boost::json::object error_obj;
  646. error_obj["error"] = "Internal Server Error";
  647. error_obj["message"] = "无法打开文件";
  648. return HttpResponse(500, "Internal Server Error", boost::json::serialize(error_obj));
  649. }
  650. std::string content((std::istreambuf_iterator<char>(file)),
  651. std::istreambuf_iterator<char>());
  652. file.close();
  653. HttpResponse response;
  654. response.status_code = 200;
  655. response.status_message = "OK";
  656. response.content_type = content_type;
  657. response.body = content;
  658. return response;
  659. } catch (const std::exception& e) {
  660. boost::json::object error_obj;
  661. error_obj["error"] = "Internal Server Error";
  662. error_obj["message"] = e.what();
  663. return HttpResponse(500, "Internal Server Error", boost::json::serialize(error_obj));
  664. }
  665. }
  666. HttpResponse HttpServer::handle_list_reports(const HttpRequest& request) {
  667. try {
  668. auto timestamps = list_timestamp_directories();
  669. // 构建JSON响应
  670. boost::json::array reports_array;
  671. for (const auto& timestamp : timestamps) {
  672. std::string report_path = output_directory_ + "/" + timestamp + "/rtsp_report.json";
  673. // 检查报告文件是否存在
  674. if (std::filesystem::exists(report_path)) {
  675. // 读取报告文件
  676. std::ifstream file(report_path);
  677. if (file.is_open()) {
  678. std::string content((std::istreambuf_iterator<char>(file)),
  679. std::istreambuf_iterator<char>());
  680. file.close();
  681. try {
  682. // 解析JSON
  683. auto report = boost::json::parse(content);
  684. boost::json::object report_info;
  685. report_info["timestamp"] = timestamp;
  686. report_info["has_report"] = true;
  687. // 提取摘要信息
  688. if (report.is_object()) {
  689. auto& obj = report.as_object();
  690. if (obj.contains("summary")) {
  691. report_info["summary"] = obj.at("summary");
  692. }
  693. if (obj.contains("report_generated_at")) {
  694. report_info["generated_at"] = obj.at("report_generated_at");
  695. }
  696. }
  697. reports_array.push_back(report_info);
  698. } catch (...) {
  699. // JSON解析失败,跳过
  700. }
  701. }
  702. }
  703. }
  704. boost::json::object result;
  705. result["count"] = reports_array.size();
  706. result["reports"] = reports_array;
  707. return HttpResponse(200, "OK", boost::json::serialize(result));
  708. } catch (const std::exception& e) {
  709. boost::json::object error_obj;
  710. error_obj["error"] = "Internal Server Error";
  711. error_obj["message"] = e.what();
  712. return HttpResponse(500, "Internal Server Error", boost::json::serialize(error_obj));
  713. }
  714. }
  715. HttpResponse HttpServer::handle_get_report(const HttpRequest& request) {
  716. try {
  717. // 从路径中提取时间戳 /api/report/{timestamp}
  718. std::string path = request.path;
  719. size_t pos = path.find_last_of('/');
  720. if (pos == std::string::npos) {
  721. boost::json::object error_obj;
  722. error_obj["error"] = "Bad Request";
  723. error_obj["message"] = "无效的请求路径";
  724. return HttpResponse(400, "Bad Request", boost::json::serialize(error_obj));
  725. }
  726. std::string timestamp = path.substr(pos + 1);
  727. std::string report_path = output_directory_ + "/" + timestamp + "/rtsp_report.json";
  728. // 检查文件是否存在
  729. if (!std::filesystem::exists(report_path)) {
  730. boost::json::object error_obj;
  731. error_obj["error"] = "Not Found";
  732. error_obj["message"] = "报告文件不存在";
  733. error_obj["timestamp"] = timestamp;
  734. return HttpResponse(404, "Not Found", boost::json::serialize(error_obj));
  735. }
  736. // 读取报告文件
  737. std::ifstream file(report_path);
  738. if (!file.is_open()) {
  739. boost::json::object error_obj;
  740. error_obj["error"] = "Internal Server Error";
  741. error_obj["message"] = "无法打开报告文件";
  742. return HttpResponse(500, "Internal Server Error", boost::json::serialize(error_obj));
  743. }
  744. std::string content((std::istreambuf_iterator<char>(file)),
  745. std::istreambuf_iterator<char>());
  746. file.close();
  747. // 验证JSON格式
  748. try {
  749. boost::json::parse(content);
  750. } catch (const std::exception& e) {
  751. boost::json::object error_obj;
  752. error_obj["error"] = "Internal Server Error";
  753. error_obj["message"] = "报告文件格式错误";
  754. return HttpResponse(500, "Internal Server Error", boost::json::serialize(error_obj));
  755. }
  756. return HttpResponse(200, "OK", content);
  757. } catch (const std::exception& e) {
  758. boost::json::object error_obj;
  759. error_obj["error"] = "Internal Server Error";
  760. error_obj["message"] = e.what();
  761. return HttpResponse(500, "Internal Server Error", boost::json::serialize(error_obj));
  762. }
  763. }
  764. HttpResponse HttpServer::generate_index_page() {
  765. std::string html = R"(
  766. <!DOCTYPE html>
  767. <html lang="zh-CN">
  768. <head>
  769. <meta charset="UTF-8">
  770. <title>RTSP视频流管理系统</title>
  771. <style>
  772. body { font-family: -apple-system, BlinkMacSystemFont, sans-serif; margin: 0; padding: 40px; background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); min-height: 100vh; }
  773. .container { max-width: 800px; margin: 0 auto; background: white; padding: 40px; border-radius: 10px; box-shadow: 0 10px 30px rgba(0,0,0,0.2); }
  774. h1 { color: #333; text-align: center; }
  775. .card { background: #f8f9fa; padding: 20px; margin: 20px 0; border-radius: 8px; }
  776. .btn { display: inline-block; padding: 12px 24px; background: #667eea; color: white; text-decoration: none; border-radius: 5px; margin: 10px 10px 10px 0; }
  777. .btn:hover { background: #5568d3; }
  778. </style>
  779. </head>
  780. <body>
  781. <div class="container">
  782. <h1>🌅 RTSP视频流管理系统</h1>
  783. <div class="card">
  784. <h2>🚀 快速开始</h2>
  785. <p>欢迎使用RTSP视频流管理系统!</p>
  786. <a href="/manager" class="btn">📹 视频管理</a>
  787. <a href="/api" class="btn">📊 API文档</a>
  788. </div>
  789. </div>
  790. </body>
  791. </html>
  792. )";
  793. HttpResponse response;
  794. response.status_code = 200;
  795. response.status_message = "OK";
  796. response.content_type = "text/html";
  797. response.body = html;
  798. return response;
  799. }
  800. HttpResponse HttpServer::generate_api_doc() {
  801. std::string html = R"(
  802. <!DOCTYPE html>
  803. <html lang="zh-CN">
  804. <head>
  805. <meta charset="UTF-8">
  806. <title>API文档</title>
  807. <style>
  808. body { font-family: sans-serif; margin: 0; padding: 20px; background: #f5f5f5; }
  809. .container { max-width: 1000px; margin: 0 auto; background: white; padding: 40px; border-radius: 10px; }
  810. .endpoint { background: #f8f9fa; padding: 20px; margin: 20px 0; border-radius: 8px; }
  811. .method { display: inline-block; padding: 4px 8px; border-radius: 4px; color: white; font-weight: bold; margin-right: 10px; }
  812. .get { background: #28a745; }
  813. .delete { background: #dc3545; }
  814. </style>
  815. </head>
  816. <body>
  817. <div class="container">
  818. <h1>📊 API文档</h1>
  819. <div class="endpoint">
  820. <h3><span class="method get">GET</span>/api/videos</h3>
  821. <p>列出所有视频文件</p>
  822. </div>
  823. <div class="endpoint">
  824. <h3><span class="method delete">DELETE</span>/api/video</h3>
  825. <p>删除指定视频文件</p>
  826. </div>
  827. </div>
  828. </body>
  829. </html>
  830. )";
  831. HttpResponse response;
  832. response.status_code = 200;
  833. response.status_message = "OK";
  834. response.content_type = "text/html";
  835. response.body = html;
  836. return response;
  837. }
  838. } // namespace jtjai_media