From 74c6e3981d0afd839215a47691fd167ddbf3a816 Mon Sep 17 00:00:00 2001 From: wyc <1104749580@qq.com> Date: Thu, 16 Jul 2026 10:06:01 +0800 Subject: [PATCH] =?UTF-8?q?=E7=93=A6=E7=89=87=E5=A4=84=E7=90=86=E7=A3=81?= =?UTF-8?q?=E7=9B=98=E7=BC=93=E5=AD=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- module/Local_Server/server/Global.h | 1 + module/Local_Server/server/server.cpp | 12 +- module/Local_Server/server/tiles.cpp | 161 ++++++++++++++++++++++++++ todolist.txt | 3 - 4 files changed, 163 insertions(+), 14 deletions(-) create mode 100644 module/Local_Server/server/tiles.cpp diff --git a/module/Local_Server/server/Global.h b/module/Local_Server/server/Global.h index efd1e9d..a2887c2 100644 --- a/module/Local_Server/server/Global.h +++ b/module/Local_Server/server/Global.h @@ -165,6 +165,7 @@ public: std::string api = "/api/"; Mlat_Handler mlat_handler; void init_web_server(); + void init_tiles(); void handle_old_logs(); std::time_t start_server_time; DELETE_COPY(Global) diff --git a/module/Local_Server/server/server.cpp b/module/Local_Server/server/server.cpp index 5e2714f..1a226c2 100644 --- a/module/Local_Server/server/server.cpp +++ b/module/Local_Server/server/server.cpp @@ -172,17 +172,7 @@ void Global::init_web_server() { }, {drogon::Get}); }; - drogon::app().registerHandlerViaRegex( - R"(^/tiles/.*$)", - [return_file]( - const drogon::HttpRequestPtr& req, - std::function&& callback) { - auto path = web_server.get_true_tiles() + req->getPath().substr(6); - auto resp = return_file(path); - resp->addHeader("Cache-Control", "public, max-age=31536000, immutable"); - callback(resp); - }, - {drogon::Get}); + init_tiles(); drogon::app().registerHandlerViaRegex( "/", [return_file]( diff --git a/module/Local_Server/server/tiles.cpp b/module/Local_Server/server/tiles.cpp new file mode 100644 index 0000000..c224615 --- /dev/null +++ b/module/Local_Server/server/tiles.cpp @@ -0,0 +1,161 @@ +#include "Global.h" +#include "Performance_Monitor.h" +#include +#include +#include +#include +#include +#include +#include +#include +/** + * 瓦片文件缓存策略: + * * 首次请求通过 newFileResponse() 直接发送文件,避免先把整个文件读取到内存。 + * * 根据文件修改时间和大小生成弱 ETag,同时返回 Last-Modified。 + * * 浏览器缓存未过期时直接使用本地缓存,不再请求服务器。 + * * 缓存过期或强制刷新时,浏览器自动携带 If-None-Match 或 If-Modified-Since。 + * * 服务端优先验证 If-None-Match,文件未变化时返回 304,不重复传输文件内容。 + * * 404 响应使用 no-store,避免不存在的瓦片被浏览器长期缓存。 + * * max-age=31536000, immutable 仅适用于同一 URL 对应的文件永远不被原地修改; + * 如果瓦片可能在原路径更新,应移除 immutable 并使用较短的 max-age。 + */ +namespace { +struct File_Cache_Info { + std::uint64_t size; + std::time_t modified_time; + std::string etag; + std::string last_modified; +}; +std::string format_http_date(std::time_t time) { + std::tm tm{}; +#ifdef _WIN32 + gmtime_s(&tm, &time); +#else + gmtime_r(&time, &tm); +#endif + char buffer[64]; + auto size = std::strftime(buffer, sizeof(buffer), "%a, %d %b %Y %H:%M:%S GMT", &tm); + return std::string(buffer, size); +} +std::optional get_file_cache_info(const std::string& path) { + auto native_path = std::filesystem::path(path); +#ifdef _WIN32 + struct _stati64 info{}; + if (_wstati64(native_path.c_str(), &info) != 0 || (info.st_mode & _S_IFMT) != _S_IFREG) { + return std::nullopt; + } +#else + struct stat info{}; + if (::stat(native_path.c_str(), &info) != 0 || !S_ISREG(info.st_mode)) { + return std::nullopt; + } +#endif + File_Cache_Info result; + result.size = static_cast(info.st_size); + result.modified_time = info.st_mtime; + result.etag = std::format( + "W/\"{:x}-{:x}\"", + static_cast(result.modified_time), + result.size); + result.last_modified = format_http_date(result.modified_time); + return result; +} +std::string_view trim_http_ows(std::string_view value) { + while (!value.empty() && (value.front() == ' ' || value.front() == '\t')) { + value.remove_prefix(1); + } + while (!value.empty() && (value.back() == ' ' || value.back() == '\t')) { + value.remove_suffix(1); + } + return value; +} +std::string_view remove_weak_prefix(std::string_view etag) { + if (etag.starts_with("W/")) { + etag.remove_prefix(2); + } + return etag; +} +bool etag_matches(std::string_view values, std::string_view current_etag) { + while (!values.empty()) { + auto pos = values.find(','); + auto value = trim_http_ows(values.substr(0, pos)); + if (value == "*" || remove_weak_prefix(value) == remove_weak_prefix(current_etag)) { + return true; + } + if (pos == std::string_view::npos) { + break; + } + values.remove_prefix(pos + 1); + } + return false; +} +void add_tile_cache_headers( + const drogon::HttpResponsePtr& response, + const File_Cache_Info& info) { + response->addHeader("Cache-Control", "public, max-age=86400"); + //response->addHeader("Cache-Control", "public, max-age=31536000, immutable"); + response->addHeader("ETag", info.etag); + response->addHeader("Last-Modified", info.last_modified); +} +drogon::HttpResponsePtr make_tile_not_found_response(const std::string& path) { + auto response = drogon::HttpResponse::newHttpResponse(); + JSON ret = JSON::object(); + ret.append({"LOG_POS", LOG_POS}); + ret.append({"not_found_path", path}); + response->setStatusCode(drogon::k404NotFound); + response->setContentTypeCode(drogon::CT_TEXT_PLAIN); + response->setBody(ret.to_json_string()); + response->addHeader("Cache-Control", "no-store"); + return response; +} +drogon::HttpResponsePtr return_tile_file( + const drogon::HttpRequestPtr& req, + const std::string& path) { + auto info = get_file_cache_info(path); + if (!info) { + return make_tile_not_found_response(path); + } + const auto& if_none_match = req->getHeader("if-none-match"); + bool not_modified; + if (!if_none_match.empty()) { + not_modified = etag_matches(if_none_match, info->etag); + } + else { + const auto& if_modified_since = req->getHeader("if-modified-since"); + not_modified = !if_modified_since.empty() && if_modified_since == info->last_modified; + } + if (not_modified) { + auto response = drogon::HttpResponse::newHttpResponse(); + response->setStatusCode(drogon::k304NotModified); + response->setContentTypeCode(drogon::CT_NONE); + add_tile_cache_headers(response, *info); + return response; + } + auto content_type = httplib::detail::find_content_type( + path, + {}, + "application/octet-stream"); + auto response = drogon::HttpResponse::newFileResponse( + path, + "", + drogon::CT_CUSTOM, + content_type, + req); + if (response->statusCode() == drogon::k200OK || + response->statusCode() == drogon::k206PartialContent) { + add_tile_cache_headers(response, *info); + } + return response; +} +} +void Global::init_tiles() { + static auto& web_server = web_server_config; + drogon::app().registerHandlerViaRegex( + R"(^/tiles/.*$)", + [](const drogon::HttpRequestPtr& req, + std::function&& callback) { + auto path = web_server.get_true_tiles() + req->getPath().substr(6); + callback(return_tile_file(req, path)); + }, + {drogon::Get}); +} diff --git a/todolist.txt b/todolist.txt index c9b5957..d43dc8e 100644 --- a/todolist.txt +++ b/todolist.txt @@ -1,6 +1,3 @@ 网页显示的开启服务时间 同时得知系统的开机时间 系统的内存占用 cpu占用 线程的信息 显示在网页上 先调研前端有什么图标库做这个事情 -梯子的网页配置来源是从哪里 前端缓存瓦片 - -后台处理发送消息 分流