123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996 |
- #include "http_server.h"
- #include <iostream>
- #include <fstream>
- #include <sstream>
- #include <filesystem>
- #include <algorithm>
- #include <iomanip>
- #include <ctime>
- namespace jtjai_media {
- HttpServer::HttpServer(const std::string& output_directory, int port)
- : output_directory_(output_directory)
- , port_(port)
- , is_running_(false) {
- }
- HttpServer::~HttpServer() {
- stop();
- }
- bool HttpServer::start() {
- if (is_running_.load()) {
- std::cerr << "HTTP服务器已经在运行中" << std::endl;
- return false;
- }
-
- is_running_.store(true);
- server_thread_ = std::make_unique<std::thread>(&HttpServer::server_main_loop, this);
-
- std::cout << "HTTP服务器已启动,监听端口: " << port_ << std::endl;
- std::cout << "API端点:" << std::endl;
- std::cout << " GET /api/videos - 列出所有视频" << std::endl;
- std::cout << " GET /api/timestamps - 列出所有时间戳目录" << std::endl;
- std::cout << " GET /api/videos/{timestamp} - 列出指定时间戳目录的视频" << std::endl;
- std::cout << " GET /api/reports - 列出所有轮询报告" << std::endl;
- std::cout << " GET /api/report/{timestamp} - 获取指定时间戳的轮询报告" << std::endl;
- std::cout << " DELETE /api/video?path={path} - 删除指定视频文件" << std::endl;
- std::cout << " DELETE /api/timestamp/{timestamp} - 删除指定时间戳目录及其所有文件" << std::endl;
- std::cout << "Web界面:" << std::endl;
- std::cout << " GET / - 主页" << std::endl;
- std::cout << " GET /manager - 视频管理界面" << std::endl;
-
- return true;
- }
- void HttpServer::stop() {
- if (!is_running_.load()) {
- return;
- }
-
- std::cout << "正在停止HTTP服务器..." << std::endl;
- is_running_.store(false);
-
- if (server_thread_ && server_thread_->joinable()) {
- server_thread_->join();
- }
-
- std::cout << "HTTP服务器已停止" << std::endl;
- }
- void HttpServer::server_main_loop() {
- try {
- boost::asio::io_context io_context;
- boost::asio::ip::tcp::acceptor acceptor(
- io_context,
- boost::asio::ip::tcp::endpoint(boost::asio::ip::tcp::v4(), port_)
- );
-
- acceptor.listen();
-
- while (is_running_.load()) {
- boost::asio::ip::tcp::socket socket(io_context);
-
- // 设置接受超时
- acceptor.non_blocking(true);
-
- boost::system::error_code ec;
- acceptor.accept(socket, ec);
-
- if (!ec) {
- // 在新线程中处理请求
- std::thread([this](boost::asio::ip::tcp::socket sock) {
- handle_client(std::move(sock));
- }, std::move(socket)).detach();
- } else if (ec != boost::asio::error::would_block) {
- std::cerr << "接受连接错误: " << ec.message() << std::endl;
- }
-
- // 短暂休眠避免CPU占用过高
- std::this_thread::sleep_for(std::chrono::milliseconds(10));
- }
-
- } catch (const std::exception& e) {
- std::cerr << "HTTP服务器异常: " << e.what() << std::endl;
- }
- }
- void HttpServer::handle_client(boost::asio::ip::tcp::socket socket) {
- try {
- // 读取请求
- boost::asio::streambuf buffer;
- boost::system::error_code ec;
-
- // 读取直到双换行(HTTP请求结束标志)
- boost::asio::read_until(socket, buffer, "\r\n\r\n", ec);
-
- if (ec && ec != boost::asio::error::eof) {
- std::cerr << "读取请求错误: " << ec.message() << std::endl;
- return;
- }
-
- // 转换为字符串
- std::string request_data(
- boost::asio::buffers_begin(buffer.data()),
- boost::asio::buffers_begin(buffer.data()) + buffer.size()
- );
-
- // 解析请求
- HttpRequest request = parse_request(request_data);
-
- // 路由处理
- HttpResponse response = route_request(request);
-
- // 构建响应
- std::string response_data = build_response(response);
-
- // 发送响应
- boost::asio::write(socket, boost::asio::buffer(response_data), ec);
-
- if (ec) {
- std::cerr << "发送响应错误: " << ec.message() << std::endl;
- }
-
- // 关闭连接
- socket.close();
-
- } catch (const std::exception& e) {
- std::cerr << "处理客户端请求异常: " << e.what() << std::endl;
- }
- }
- HttpRequest HttpServer::parse_request(const std::string& request_data) {
- HttpRequest request;
- std::istringstream stream(request_data);
- std::string line;
-
- // 解析请求行
- if (std::getline(stream, line)) {
- std::istringstream request_line(line);
- std::string uri;
- request_line >> request.method >> uri;
-
- // 分离路径和查询字符串
- size_t query_pos = uri.find('?');
- if (query_pos != std::string::npos) {
- request.path = uri.substr(0, query_pos);
- request.query_string = uri.substr(query_pos + 1);
- } else {
- request.path = uri;
- }
- }
-
- // 解析请求头
- while (std::getline(stream, line) && line != "\r") {
- size_t colon_pos = line.find(':');
- if (colon_pos != std::string::npos) {
- std::string key = line.substr(0, colon_pos);
- std::string value = line.substr(colon_pos + 1);
- // 去除首尾空格
- value.erase(0, value.find_first_not_of(" \t\r\n"));
- value.erase(value.find_last_not_of(" \t\r\n") + 1);
- request.headers[key] = value;
- }
- }
-
- return request;
- }
- std::string HttpServer::build_response(const HttpResponse& response) {
- std::ostringstream oss;
-
- // 状态行
- oss << "HTTP/1.1 " << response.status_code << " " << response.status_message << "\r\n";
-
- // 响应头
- oss << "Content-Type: " << response.content_type << "\r\n";
- oss << "Content-Length: " << response.body.length() << "\r\n";
- oss << "Access-Control-Allow-Origin: *\r\n";
- oss << "Access-Control-Allow-Methods: GET, POST, DELETE, OPTIONS\r\n";
- oss << "Accept-Ranges: bytes\r\n"; // 支持Range请求
- oss << "Connection: close\r\n";
- oss << "\r\n";
-
- // 响应体
- oss << response.body;
-
- return oss.str();
- }
- HttpResponse HttpServer::route_request(const HttpRequest& request) {
- std::cout << request.method << " " << request.path;
- if (!request.query_string.empty()) {
- std::cout << "?" << request.query_string;
- }
- std::cout << std::endl;
-
- // OPTIONS请求处理(CORS预检)
- if (request.method == "OPTIONS") {
- return HttpResponse(200, "OK", "");
- }
-
- // 路由分发
- if (request.method == "GET" || request.method == "HEAD") {
- if (request.path == "/api/videos") {
- return handle_list_videos(request);
- } else if (request.path == "/api/timestamps") {
- return handle_list_timestamps(request);
- } else if (request.path == "/api/reports") {
- return handle_list_reports(request);
- } else if (request.path.find("/api/report/") == 0) {
- return handle_get_report(request);
- } else if (request.path.find("/api/videos/") == 0) {
- return handle_get_video_info(request);
- } else if (request.path.find("/videos/") == 0) {
- // 处理视频文件访问
- auto response = handle_video_file(request);
- // 如果是HEAD请求,只返回头部,不返回体
- if (request.method == "HEAD") {
- response.body.clear();
- }
- return response;
- } else {
- // 处理静态文件访问(HTML、CSS、JS等)
- return handle_static_file(request);
- }
- } else if (request.method == "DELETE") {
- if (request.path == "/api/video") {
- return handle_delete_video(request);
- } else if (request.path.find("/api/timestamp/") == 0) {
- return handle_delete_timestamp(request);
- }
- }
-
- // 404未找到
- boost::json::object error_obj;
- error_obj["error"] = "Not Found";
- error_obj["message"] = "请求的资源不存在";
- return HttpResponse(404, "Not Found", boost::json::serialize(error_obj));
- }
- HttpResponse HttpServer::handle_list_videos(const HttpRequest& request) {
- try {
- auto query_params = parse_query_string(request.query_string);
- std::vector<VideoFileInfo> videos;
-
- // 检查是否指定了时间戳
- if (query_params.count("timestamp")) {
- std::string timestamp = query_params["timestamp"];
- std::string dir_path = output_directory_ + "/" + timestamp;
- videos = list_videos_in_directory(dir_path);
- } else {
- videos = list_all_videos();
- }
-
- // 构建JSON响应
- boost::json::array videos_array;
- for (const auto& video : videos) {
- boost::json::object video_obj;
- video_obj["filename"] = video.filename;
- video_obj["full_path"] = video.full_path;
- video_obj["timestamp_dir"] = video.timestamp_dir;
- video_obj["file_size"] = video.file_size;
- video_obj["created_time"] = video.created_time;
- video_obj["stream_index"] = video.stream_index;
- videos_array.push_back(video_obj);
- }
-
- boost::json::object result;
- result["count"] = videos.size();
- result["videos"] = videos_array;
-
- return HttpResponse(200, "OK", boost::json::serialize(result));
-
- } catch (const std::exception& e) {
- boost::json::object error_obj;
- error_obj["error"] = "Internal Server Error";
- error_obj["message"] = e.what();
- return HttpResponse(500, "Internal Server Error", boost::json::serialize(error_obj));
- }
- }
- HttpResponse HttpServer::handle_list_timestamps(const HttpRequest& request) {
- try {
- auto timestamps = list_timestamp_directories();
-
- // 构建JSON响应
- boost::json::array timestamps_array;
- for (const auto& timestamp : timestamps) {
- boost::json::object ts_obj;
- ts_obj["timestamp"] = timestamp;
-
- // 获取该目录下的视频数量
- std::string dir_path = output_directory_ + "/" + timestamp;
- auto videos = list_videos_in_directory(dir_path);
- ts_obj["video_count"] = videos.size();
-
- // 计算总大小
- int64_t total_size = 0;
- for (const auto& video : videos) {
- total_size += video.file_size;
- }
- ts_obj["total_size"] = total_size;
-
- timestamps_array.push_back(ts_obj);
- }
-
- boost::json::object result;
- result["count"] = timestamps.size();
- result["timestamps"] = timestamps_array;
-
- return HttpResponse(200, "OK", boost::json::serialize(result));
-
- } catch (const std::exception& e) {
- boost::json::object error_obj;
- error_obj["error"] = "Internal Server Error";
- error_obj["message"] = e.what();
- return HttpResponse(500, "Internal Server Error", boost::json::serialize(error_obj));
- }
- }
- HttpResponse HttpServer::handle_delete_video(const HttpRequest& request) {
- try {
- auto query_params = parse_query_string(request.query_string);
-
- if (!query_params.count("path")) {
- boost::json::object error_obj;
- error_obj["error"] = "Bad Request";
- error_obj["message"] = "缺少path参数";
- return HttpResponse(400, "Bad Request", boost::json::serialize(error_obj));
- }
-
- std::string file_path = query_params["path"];
-
- // 安全检查:确保文件路径在输出目录内
- std::filesystem::path abs_path = std::filesystem::absolute(file_path);
- std::filesystem::path abs_output = std::filesystem::absolute(output_directory_);
-
- if (abs_path.string().find(abs_output.string()) != 0) {
- boost::json::object error_obj;
- error_obj["error"] = "Forbidden";
- error_obj["message"] = "无权删除该文件";
- return HttpResponse(403, "Forbidden", boost::json::serialize(error_obj));
- }
-
- bool success = delete_file(file_path);
-
- boost::json::object result;
- result["success"] = success;
- result["path"] = file_path;
-
- if (success) {
- result["message"] = "文件删除成功";
- return HttpResponse(200, "OK", boost::json::serialize(result));
- } else {
- result["message"] = "文件删除失败";
- return HttpResponse(500, "Internal Server Error", boost::json::serialize(result));
- }
-
- } catch (const std::exception& e) {
- boost::json::object error_obj;
- error_obj["error"] = "Internal Server Error";
- error_obj["message"] = e.what();
- return HttpResponse(500, "Internal Server Error", boost::json::serialize(error_obj));
- }
- }
- HttpResponse HttpServer::handle_delete_timestamp(const HttpRequest& request) {
- try {
- // 从路径中提取时间戳 /api/timestamp/{timestamp}
- std::string path = request.path;
- size_t pos = path.find_last_of('/');
- if (pos == std::string::npos) {
- boost::json::object error_obj;
- error_obj["error"] = "Bad Request";
- error_obj["message"] = "无效的请求路径";
- return HttpResponse(400, "Bad Request", boost::json::serialize(error_obj));
- }
-
- std::string timestamp = path.substr(pos + 1);
- std::string dir_path = output_directory_ + "/" + timestamp;
-
- // 安全检查
- std::filesystem::path abs_path = std::filesystem::absolute(dir_path);
- std::filesystem::path abs_output = std::filesystem::absolute(output_directory_);
-
- if (abs_path.string().find(abs_output.string()) != 0) {
- boost::json::object error_obj;
- error_obj["error"] = "Forbidden";
- error_obj["message"] = "无权删除该目录";
- return HttpResponse(403, "Forbidden", boost::json::serialize(error_obj));
- }
-
- bool success = delete_directory(dir_path);
-
- boost::json::object result;
- result["success"] = success;
- result["timestamp"] = timestamp;
- result["path"] = dir_path;
-
- if (success) {
- result["message"] = "目录删除成功";
- return HttpResponse(200, "OK", boost::json::serialize(result));
- } else {
- result["message"] = "目录删除失败";
- return HttpResponse(500, "Internal Server Error", boost::json::serialize(result));
- }
-
- } catch (const std::exception& e) {
- boost::json::object error_obj;
- error_obj["error"] = "Internal Server Error";
- error_obj["message"] = e.what();
- return HttpResponse(500, "Internal Server Error", boost::json::serialize(error_obj));
- }
- }
- HttpResponse HttpServer::handle_get_video_info(const HttpRequest& request) {
- // 从路径中提取时间戳 /api/videos/{timestamp}
- std::string path = request.path;
- size_t pos = path.find_last_of('/');
- if (pos == std::string::npos) {
- boost::json::object error_obj;
- error_obj["error"] = "Bad Request";
- error_obj["message"] = "无效的请求路径";
- return HttpResponse(400, "Bad Request", boost::json::serialize(error_obj));
- }
-
- std::string timestamp = path.substr(pos + 1);
- std::string dir_path = output_directory_ + "/" + timestamp;
-
- auto videos = list_videos_in_directory(dir_path);
-
- boost::json::array videos_array;
- for (const auto& video : videos) {
- boost::json::object video_obj;
- video_obj["filename"] = video.filename;
- video_obj["full_path"] = video.full_path;
- video_obj["timestamp_dir"] = video.timestamp_dir;
- video_obj["file_size"] = video.file_size;
- video_obj["created_time"] = video.created_time;
- video_obj["stream_index"] = video.stream_index;
- videos_array.push_back(video_obj);
- }
-
- boost::json::object result;
- result["timestamp"] = timestamp;
- result["count"] = videos.size();
- result["videos"] = videos_array;
-
- return HttpResponse(200, "OK", boost::json::serialize(result));
- }
- HttpResponse HttpServer::handle_video_file(const HttpRequest& request) {
- try {
- // 从路径中提取文件路径 /videos/{timestamp}/{filename}
- std::string path = request.path;
- size_t pos = path.find("/videos/");
- if (pos == std::string::npos) {
- boost::json::object error_obj;
- error_obj["error"] = "Bad Request";
- error_obj["message"] = "无效的请求路径";
- return HttpResponse(400, "Bad Request", boost::json::serialize(error_obj));
- }
-
- // 获取相对路径(URL解码)
- std::string encoded_path = path.substr(pos + 8); // 跳过 "/videos/"
- std::string relative_path = url_decode(encoded_path);
-
- // 构建完整文件路径
- std::string file_path = output_directory_ + "/" + relative_path;
-
- std::cout << "请求视频文件: " << file_path << std::endl;
-
- // 安全检查:确保文件路径在输出目录内
- std::filesystem::path abs_path = std::filesystem::absolute(file_path);
- std::filesystem::path abs_output = std::filesystem::absolute(output_directory_);
-
- if (abs_path.string().find(abs_output.string()) != 0) {
- boost::json::object error_obj;
- error_obj["error"] = "Forbidden";
- error_obj["message"] = "无权访问该文件";
- return HttpResponse(403, "Forbidden", boost::json::serialize(error_obj));
- }
-
- // 检查文件是否存在
- if (!std::filesystem::exists(file_path)) {
- boost::json::object error_obj;
- error_obj["error"] = "Not Found";
- error_obj["message"] = "文件不存在: " + file_path;
- return HttpResponse(404, "Not Found", boost::json::serialize(error_obj));
- }
-
- // 获取文件大小
- size_t file_size = std::filesystem::file_size(file_path);
-
- // 读取文件内容
- std::ifstream file(file_path, std::ios::binary);
- if (!file.is_open()) {
- boost::json::object error_obj;
- error_obj["error"] = "Internal Server Error";
- error_obj["message"] = "无法打开文件";
- return HttpResponse(500, "Internal Server Error", boost::json::serialize(error_obj));
- }
-
- // 读取全部文件内容
- std::string content(file_size, '\0');
- file.read(&content[0], file_size);
- file.close();
-
- std::cout << "发送视频文件: " << file_path << " (" << file_size << " bytes)" << std::endl;
-
- // 构建响应
- HttpResponse response;
- response.status_code = 200;
- response.status_message = "OK";
-
- // 根据文件扩展名设置Content-Type
- std::string ext = std::filesystem::path(file_path).extension().string();
- if (ext == ".mp4") {
- response.content_type = "video/mp4";
- } else if (ext == ".avi") {
- response.content_type = "video/x-msvideo";
- } else if (ext == ".mkv") {
- response.content_type = "video/x-matroska";
- } else if (ext == ".flv") {
- response.content_type = "video/x-flv";
- } else {
- response.content_type = "application/octet-stream";
- }
-
- response.body = content;
-
- return response;
-
- } catch (const std::exception& e) {
- boost::json::object error_obj;
- error_obj["error"] = "Internal Server Error";
- error_obj["message"] = e.what();
- return HttpResponse(500, "Internal Server Error", boost::json::serialize(error_obj));
- }
- }
- std::vector<std::string> HttpServer::list_timestamp_directories() {
- std::vector<std::string> timestamps;
-
- try {
- if (!std::filesystem::exists(output_directory_)) {
- return timestamps;
- }
-
- for (const auto& entry : std::filesystem::directory_iterator(output_directory_)) {
- if (entry.is_directory()) {
- timestamps.push_back(entry.path().filename().string());
- }
- }
-
- // 按时间戳排序(最新的在前)
- std::sort(timestamps.begin(), timestamps.end(), std::greater<std::string>());
-
- } catch (const std::exception& e) {
- std::cerr << "列出时间戳目录错误: " << e.what() << std::endl;
- }
-
- return timestamps;
- }
- std::vector<VideoFileInfo> HttpServer::list_videos_in_directory(const std::string& dir_path) {
- std::vector<VideoFileInfo> videos;
-
- try {
- if (!std::filesystem::exists(dir_path)) {
- return videos;
- }
-
- for (const auto& entry : std::filesystem::directory_iterator(dir_path)) {
- if (entry.is_regular_file()) {
- std::string filename = entry.path().filename().string();
- std::string ext = entry.path().extension().string();
-
- // 只列出视频文件
- if (ext == ".mp4" || ext == ".avi" || ext == ".mkv" || ext == ".flv") {
- VideoFileInfo info;
- info.filename = filename;
- info.full_path = entry.path().string();
- info.timestamp_dir = std::filesystem::path(dir_path).filename().string();
- info.file_size = get_file_size(info.full_path);
- info.created_time = get_file_created_time(info.full_path);
-
- // 从文件名提取流索引
- size_t stream_pos = filename.find("stream");
- if (stream_pos != std::string::npos) {
- size_t num_start = stream_pos + 6;
- size_t num_end = filename.find_first_not_of("0123456789", num_start);
- if (num_end != std::string::npos) {
- info.stream_index = filename.substr(num_start, num_end - num_start);
- }
- }
-
- videos.push_back(info);
- }
- }
- }
-
- // 按文件名排序
- std::sort(videos.begin(), videos.end(),
- [](const VideoFileInfo& a, const VideoFileInfo& b) {
- return a.filename < b.filename;
- });
-
- } catch (const std::exception& e) {
- std::cerr << "列出视频文件错误: " << e.what() << std::endl;
- }
-
- return videos;
- }
- std::vector<VideoFileInfo> HttpServer::list_all_videos() {
- std::vector<VideoFileInfo> all_videos;
-
- auto timestamps = list_timestamp_directories();
- for (const auto& timestamp : timestamps) {
- std::string dir_path = output_directory_ + "/" + timestamp;
- auto videos = list_videos_in_directory(dir_path);
- all_videos.insert(all_videos.end(), videos.begin(), videos.end());
- }
-
- return all_videos;
- }
- bool HttpServer::delete_file(const std::string& file_path) {
- try {
- if (std::filesystem::exists(file_path)) {
- std::filesystem::remove(file_path);
- std::cout << "已删除文件: " << file_path << std::endl;
- return true;
- }
- return false;
- } catch (const std::exception& e) {
- std::cerr << "删除文件错误: " << e.what() << std::endl;
- return false;
- }
- }
- bool HttpServer::delete_directory(const std::string& dir_path) {
- try {
- if (std::filesystem::exists(dir_path)) {
- std::filesystem::remove_all(dir_path);
- std::cout << "已删除目录: " << dir_path << std::endl;
- return true;
- }
- return false;
- } catch (const std::exception& e) {
- std::cerr << "删除目录错误: " << e.what() << std::endl;
- return false;
- }
- }
- std::string HttpServer::get_file_created_time(const std::string& file_path) {
- try {
- auto ftime = std::filesystem::last_write_time(file_path);
- auto sctp = std::chrono::time_point_cast<std::chrono::system_clock::duration>(
- ftime - std::filesystem::file_time_type::clock::now() + std::chrono::system_clock::now()
- );
- auto time_t = std::chrono::system_clock::to_time_t(sctp);
- auto tm = *std::localtime(&time_t);
-
- std::ostringstream oss;
- oss << std::put_time(&tm, "%Y-%m-%d %H:%M:%S");
- return oss.str();
- } catch (const std::exception& e) {
- return "Unknown";
- }
- }
- int64_t HttpServer::get_file_size(const std::string& file_path) {
- try {
- return std::filesystem::file_size(file_path);
- } catch (const std::exception& e) {
- return 0;
- }
- }
- std::map<std::string, std::string> HttpServer::parse_query_string(const std::string& query) {
- std::map<std::string, std::string> params;
-
- if (query.empty()) {
- return params;
- }
-
- std::istringstream stream(query);
- std::string pair;
-
- while (std::getline(stream, pair, '&')) {
- size_t eq_pos = pair.find('=');
- if (eq_pos != std::string::npos) {
- std::string key = pair.substr(0, eq_pos);
- std::string value = pair.substr(eq_pos + 1);
- params[key] = value;
- }
- }
-
- return params;
- }
- std::string HttpServer::url_decode(const std::string& str) {
- std::string result;
- result.reserve(str.size());
-
- for (size_t i = 0; i < str.size(); ++i) {
- if (str[i] == '%') {
- if (i + 2 < str.size()) {
- int value;
- std::istringstream is(str.substr(i + 1, 2));
- if (is >> std::hex >> value) {
- result += static_cast<char>(value);
- i += 2;
- } else {
- result += str[i];
- }
- } else {
- result += str[i];
- }
- } else if (str[i] == '+') {
- result += ' ';
- } else {
- result += str[i];
- }
- }
-
- return result;
- }
- HttpResponse HttpServer::handle_static_file(const HttpRequest& request) {
- try {
- std::string file_path;
- std::string content_type = "text/html";
-
- // 路由映射
- if (request.path == "/" || request.path == "/index.html") {
- // 主页 - 显示系统信息
- return generate_index_page();
- } else if (request.path == "/manager" || request.path == "/manager.html") {
- // 视频管理界面
- file_path = "video_manager.html";
- content_type = "text/html";
- } else if (request.path == "/api" || request.path == "/api.html") {
- // API文档
- return generate_api_doc();
- } else {
- // 404
- boost::json::object error_obj;
- error_obj["error"] = "Not Found";
- error_obj["message"] = "请求的资源不存在";
- return HttpResponse(404, "Not Found", boost::json::serialize(error_obj));
- }
-
- // 读取静态文件
- if (!std::filesystem::exists(file_path)) {
- boost::json::object error_obj;
- error_obj["error"] = "Not Found";
- error_obj["message"] = "文件不存在: " + file_path;
- return HttpResponse(404, "Not Found", boost::json::serialize(error_obj));
- }
-
- std::ifstream file(file_path);
- if (!file.is_open()) {
- boost::json::object error_obj;
- error_obj["error"] = "Internal Server Error";
- error_obj["message"] = "无法打开文件";
- return HttpResponse(500, "Internal Server Error", boost::json::serialize(error_obj));
- }
-
- std::string content((std::istreambuf_iterator<char>(file)),
- std::istreambuf_iterator<char>());
- file.close();
-
- HttpResponse response;
- response.status_code = 200;
- response.status_message = "OK";
- response.content_type = content_type;
- response.body = content;
-
- return response;
-
- } catch (const std::exception& e) {
- boost::json::object error_obj;
- error_obj["error"] = "Internal Server Error";
- error_obj["message"] = e.what();
- return HttpResponse(500, "Internal Server Error", boost::json::serialize(error_obj));
- }
- }
- HttpResponse HttpServer::handle_list_reports(const HttpRequest& request) {
- try {
- auto timestamps = list_timestamp_directories();
-
- // 构建JSON响应
- boost::json::array reports_array;
- for (const auto& timestamp : timestamps) {
- std::string report_path = output_directory_ + "/" + timestamp + "/rtsp_report.json";
-
- // 检查报告文件是否存在
- if (std::filesystem::exists(report_path)) {
- // 读取报告文件
- std::ifstream file(report_path);
- if (file.is_open()) {
- std::string content((std::istreambuf_iterator<char>(file)),
- std::istreambuf_iterator<char>());
- file.close();
-
- try {
- // 解析JSON
- auto report = boost::json::parse(content);
- boost::json::object report_info;
- report_info["timestamp"] = timestamp;
- report_info["has_report"] = true;
-
- // 提取摘要信息
- if (report.is_object()) {
- auto& obj = report.as_object();
- if (obj.contains("summary")) {
- report_info["summary"] = obj.at("summary");
- }
- if (obj.contains("report_generated_at")) {
- report_info["generated_at"] = obj.at("report_generated_at");
- }
- }
-
- reports_array.push_back(report_info);
- } catch (...) {
- // JSON解析失败,跳过
- }
- }
- }
- }
-
- boost::json::object result;
- result["count"] = reports_array.size();
- result["reports"] = reports_array;
-
- return HttpResponse(200, "OK", boost::json::serialize(result));
-
- } catch (const std::exception& e) {
- boost::json::object error_obj;
- error_obj["error"] = "Internal Server Error";
- error_obj["message"] = e.what();
- return HttpResponse(500, "Internal Server Error", boost::json::serialize(error_obj));
- }
- }
- HttpResponse HttpServer::handle_get_report(const HttpRequest& request) {
- try {
- // 从路径中提取时间戳 /api/report/{timestamp}
- std::string path = request.path;
- size_t pos = path.find_last_of('/');
- if (pos == std::string::npos) {
- boost::json::object error_obj;
- error_obj["error"] = "Bad Request";
- error_obj["message"] = "无效的请求路径";
- return HttpResponse(400, "Bad Request", boost::json::serialize(error_obj));
- }
-
- std::string timestamp = path.substr(pos + 1);
- std::string report_path = output_directory_ + "/" + timestamp + "/rtsp_report.json";
-
- // 检查文件是否存在
- if (!std::filesystem::exists(report_path)) {
- boost::json::object error_obj;
- error_obj["error"] = "Not Found";
- error_obj["message"] = "报告文件不存在";
- error_obj["timestamp"] = timestamp;
- return HttpResponse(404, "Not Found", boost::json::serialize(error_obj));
- }
-
- // 读取报告文件
- std::ifstream file(report_path);
- if (!file.is_open()) {
- boost::json::object error_obj;
- error_obj["error"] = "Internal Server Error";
- error_obj["message"] = "无法打开报告文件";
- return HttpResponse(500, "Internal Server Error", boost::json::serialize(error_obj));
- }
-
- std::string content((std::istreambuf_iterator<char>(file)),
- std::istreambuf_iterator<char>());
- file.close();
-
- // 验证JSON格式
- try {
- boost::json::parse(content);
- } catch (const std::exception& e) {
- boost::json::object error_obj;
- error_obj["error"] = "Internal Server Error";
- error_obj["message"] = "报告文件格式错误";
- return HttpResponse(500, "Internal Server Error", boost::json::serialize(error_obj));
- }
-
- return HttpResponse(200, "OK", content);
-
- } catch (const std::exception& e) {
- boost::json::object error_obj;
- error_obj["error"] = "Internal Server Error";
- error_obj["message"] = e.what();
- return HttpResponse(500, "Internal Server Error", boost::json::serialize(error_obj));
- }
- }
- HttpResponse HttpServer::generate_index_page() {
- std::string html = R"(
- <!DOCTYPE html>
- <html lang="zh-CN">
- <head>
- <meta charset="UTF-8">
- <title>RTSP视频流管理系统</title>
- <style>
- body { font-family: -apple-system, BlinkMacSystemFont, sans-serif; margin: 0; padding: 40px; background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); min-height: 100vh; }
- .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); }
- h1 { color: #333; text-align: center; }
- .card { background: #f8f9fa; padding: 20px; margin: 20px 0; border-radius: 8px; }
- .btn { display: inline-block; padding: 12px 24px; background: #667eea; color: white; text-decoration: none; border-radius: 5px; margin: 10px 10px 10px 0; }
- .btn:hover { background: #5568d3; }
- </style>
- </head>
- <body>
- <div class="container">
- <h1>🌅 RTSP视频流管理系统</h1>
- <div class="card">
- <h2>🚀 快速开始</h2>
- <p>欢迎使用RTSP视频流管理系统!</p>
- <a href="/manager" class="btn">📹 视频管理</a>
- <a href="/api" class="btn">📊 API文档</a>
- </div>
- </div>
- </body>
- </html>
- )";
-
- HttpResponse response;
- response.status_code = 200;
- response.status_message = "OK";
- response.content_type = "text/html";
- response.body = html;
- return response;
- }
- HttpResponse HttpServer::generate_api_doc() {
- std::string html = R"(
- <!DOCTYPE html>
- <html lang="zh-CN">
- <head>
- <meta charset="UTF-8">
- <title>API文档</title>
- <style>
- body { font-family: sans-serif; margin: 0; padding: 20px; background: #f5f5f5; }
- .container { max-width: 1000px; margin: 0 auto; background: white; padding: 40px; border-radius: 10px; }
- .endpoint { background: #f8f9fa; padding: 20px; margin: 20px 0; border-radius: 8px; }
- .method { display: inline-block; padding: 4px 8px; border-radius: 4px; color: white; font-weight: bold; margin-right: 10px; }
- .get { background: #28a745; }
- .delete { background: #dc3545; }
- </style>
- </head>
- <body>
- <div class="container">
- <h1>📊 API文档</h1>
- <div class="endpoint">
- <h3><span class="method get">GET</span>/api/videos</h3>
- <p>列出所有视频文件</p>
- </div>
- <div class="endpoint">
- <h3><span class="method delete">DELETE</span>/api/video</h3>
- <p>删除指定视频文件</p>
- </div>
- </div>
- </body>
- </html>
- )";
-
- HttpResponse response;
- response.status_code = 200;
- response.status_message = "OK";
- response.content_type = "text/html";
- response.body = html;
- return response;
- }
- } // namespace jtjai_media
|