diff --git a/src/Infrastructure/Cache/Cache.cc b/src/Infrastructure/Cache/Cache.cc new file mode 100644 index 00000000..393e9c93 --- /dev/null +++ b/src/Infrastructure/Cache/Cache.cc @@ -0,0 +1,65 @@ +#include +#include + +namespace evento { + +std::string CacheManager::generateKey(http::verb verb, + urls::url_view url, + const std::initializer_list& params) { + std::string key = std::format("{}|{}", url.data(), static_cast(verb)); + for (const auto& param : params) { + key += std::format("|{}={}", param.key, param.value); + } + return key; +} + +bool CacheManager::isExpired(const CacheEntry& entry) { + using namespace std::chrono_literals; + return std::chrono::steady_clock::now() - entry.insertTime >= 1h; +} + +void CacheManager::insert(const std::string& key, const CacheEntry& entry) { + auto it = _cacheMap.find(key); + if (it != _cacheMap.end()) { + _cacheList.erase(it->second); + currentCacheSize -= it->second->second.size; + } + + _cacheList.emplace_front(key, entry); + _cacheMap[key] = _cacheList.begin(); + currentCacheSize += entry.size; + + while (currentCacheSize > MAX_CACHE_SIZE) { + auto last = _cacheList.end(); + --last; + currentCacheSize -= last->second.size; + _cacheMap.erase(last->first); + _cacheList.pop_back(); + } +} + +std::optional CacheManager::get(std::string const& key) { + if (isExpired(_cacheMap[key]->second)) { + currentCacheSize -= _cacheMap[key]->second.size; + _cacheList.erase(_cacheMap[key]); + _cacheMap.erase(key); + return std::nullopt; + } + auto it = _cacheMap.find(key); + if (it == _cacheMap.end()) { + return std::nullopt; + } + return it->second->second; +} + +void CacheManager::load() {} + +void CacheManager::save() {} + +void CacheManager::saveToDisk() {} + +std::string CacheManager::generateFilename(urls::url_view url) { + return ""; +} + +} // namespace evento \ No newline at end of file diff --git a/src/Infrastructure/Cache/Cache.h b/src/Infrastructure/Cache/Cache.h new file mode 100644 index 00000000..6d2765a1 --- /dev/null +++ b/src/Infrastructure/Cache/Cache.h @@ -0,0 +1,54 @@ +#pragma once + +#include +#include +#include +#include +#include +#include +#include +#include + +namespace evento { + +namespace beast = boost::beast; +namespace http = beast::http; +namespace urls = boost::urls; + +struct CacheEntry { + nlohmann::basic_json<> result; + std::chrono::steady_clock::time_point insertTime; + std::size_t size; //cache size +}; + +class CacheManager { +public: + CacheManager() { load(); } + ~CacheManager() { save(); } + + static std::string generateKey(http::verb verb, + urls::url_view url, + const std::initializer_list& params); + + static std::string generateFilename(urls::url_view url); + + static bool isExpired(const CacheEntry& entry); + + void insert(const std::string& key, const CacheEntry& entry); + + std::optional get(std::string const& key); + + static constexpr size_t MAX_CACHE_SIZE = 64 * 1024 * 1024; + +private: + void load(); + void save(); + + void saveToDisk(); + + std::list> _cacheList; + std::unordered_map _cacheMap; + inline static size_t currentCacheSize = 0; +}; + +} // namespace evento \ No newline at end of file diff --git a/src/Infrastructure/Network/HttpsAccessManager.cc b/src/Infrastructure/Network/HttpsAccessManager.cc index 0c173a85..a75aebaa 100644 --- a/src/Infrastructure/Network/HttpsAccessManager.cc +++ b/src/Infrastructure/Network/HttpsAccessManager.cc @@ -5,16 +5,10 @@ #include #include #include -#include -#include -#include #include #include -#include #include #include -#include -#include namespace evento { @@ -24,15 +18,6 @@ Task HttpsAccessManager::makeReply(std::string host, http::request req) { std::string url = host + std::string(req.target()); - // Check cache first - { - std::lock_guard lock(_cacheMutex); - auto it = _cache.find(url); - if (it != _cache.end()) { - co_return Ok(it->second); - } - } - auto resolver = net::use_awaitable_t::as_default_on( tcp::resolver(co_await net::this_coro::executor)); @@ -86,16 +71,6 @@ Task HttpsAccessManager::makeReply(std::string host, auto [ec] = co_await stream.async_shutdown(net::as_tuple(net::use_awaitable)); if (!ec || ec == net::error::eof || (ignoreSslError && ec == ssl::error::stream_truncated)) { // If we get here then the connection is closed gracefully - // Cache the response - { - std::lock_guard lock(_cacheMutex); - _cache[url] = res; - } - - // Generate a unique filename and save the response body to a file - std::string filename = generateFilename(url); - saveToFile(filename, _buffer); - co_return Ok(res); } @@ -103,20 +78,4 @@ Task HttpsAccessManager::makeReply(std::string host, co_return Err(Error(Error::Network, ec.message())); } -void HttpsAccessManager::saveToFile(const std::string& filename, const beast::flat_buffer& buffer) { - std::ofstream file(filename, std::ios::binary); - if (file.is_open()) { - file.write(static_cast(buffer.data().data()), buffer.size()); - file.close(); - } -} - -std::string HttpsAccessManager::generateFilename(const std::string& url) { - std::hash hasher; - std::size_t hash = hasher(url); - std::stringstream ss; - ss << "image_" << hash << ".jpg"; - return ss.str(); -} - } // namespace evento diff --git a/src/Infrastructure/Network/HttpsAccessManager.h b/src/Infrastructure/Network/HttpsAccessManager.h index 4f075f90..c67b52f8 100644 --- a/src/Infrastructure/Network/HttpsAccessManager.h +++ b/src/Infrastructure/Network/HttpsAccessManager.h @@ -6,16 +6,10 @@ #include #include #include -#include -#include -#include #include #include #include -#include // for std::hash -#include #include -#include namespace evento { @@ -52,16 +46,6 @@ class HttpsAccessManager { net::ssl::context& _ctx; std::chrono::seconds _timeout; // respective timeout of ssl handshake & http beast::flat_buffer _buffer; // http buffer is used for reading and must be persisted - - // Cache for storing downloaded images - std::unordered_map> _cache; - std::mutex _cacheMutex; - - // Helper function to save response body to a file - void saveToFile(const std::string& filename, const beast::flat_buffer& buffer); - - // Helper function to generate a unique filename based on URL - std::string generateFilename(const std::string& url); }; } // namespace evento \ No newline at end of file diff --git a/src/Infrastructure/Network/NetworkClient.cc b/src/Infrastructure/Network/NetworkClient.cc index bf0b9184..9014c21f 100644 --- a/src/Infrastructure/Network/NetworkClient.cc +++ b/src/Infrastructure/Network/NetworkClient.cc @@ -1,9 +1,7 @@ #include "NetworkClient.h" #include #include -#include #include -#include #include #include #include @@ -19,7 +17,7 @@ constexpr const char MIME_FORM_URL_ENCODED[] = "application/x-www-form-urlencode NetworkClient::NetworkClient(net::ssl::context& ctx) : _ctx(ctx) - , _manager(std::make_unique(_ctx, true)) {} + , _httpsAccessManager(std::make_unique(_ctx, true)) {} NetworkClient* NetworkClient::getInstance() { static ssl::context ctx(ssl::context::sslv23); @@ -348,17 +346,6 @@ Task> NetworkClient::getDepartmentList() { co_return Ok(entity); } -std::string NetworkClient::generateCacheKey(http::verb verb, - const urls::url_view& url, - const std::initializer_list& params) { - std::string key = std::string(url.data(), url.size()) + "|" - + std::to_string(static_cast(verb)); - for (const auto& param : params) { - key += "|" + std::string(param.key) + "=" + std::string(param.value); - } - return key; -} - urls::url NetworkClient::endpoint(std::string_view endpoint) { return urls::url(EVENTO_API_GATEWAY + endpoint.data()); } diff --git a/src/Infrastructure/Network/NetworkClient.h b/src/Infrastructure/Network/NetworkClient.h index 1817e5af..2193f692 100644 --- a/src/Infrastructure/Network/NetworkClient.h +++ b/src/Infrastructure/Network/NetworkClient.h @@ -1,5 +1,6 @@ #pragma once +#include #include #include #include @@ -8,18 +9,16 @@ #include #include #include -#include #include #include -#include #include +#include #include -#include -#include +#include #include #include +#include #include -#include namespace evento { @@ -37,12 +36,6 @@ using ContributorList = std::vector; template using Task = net::awaitable; -struct CacheEntry { - JsonResult result; - std::chrono::steady_clock::time_point insertTime; - std::size_t size; //cache size -}; - class NetworkClient { public: NetworkClient(const NetworkClient&) = delete; @@ -92,6 +85,8 @@ class NetworkClient { Task> getLatestRelease(); + Task> downloadFile(urls::url_view url); + // access token // NOTE: `AUTOMATICALLY` added to request header if exists std::optional tokenBytes; @@ -100,69 +95,50 @@ class NetworkClient { NetworkClient(net::ssl::context& ctx); static NetworkClient* getInstance(); //cache data processing - static std::string generateCacheKey( - http::verb verb, - const urls::url_view& url, - const std::initializer_list& params); //auxiliary function to generate cache key - - static bool isCacheEntryExpired(const CacheEntry& entry) { - auto now = std::chrono::steady_clock::now(); - auto duration = std::chrono::duration_cast(now - entry.insertTime); - return duration.count() >= 3; - } - void updateCache(const std::string& key, CacheEntry entry) { - try { - auto it = cacheMap.find(key); - if (it != cacheMap.end()) { - cacheList.erase(it->second); - currentCacheSize -= it->second->second.size; - } - cacheList.emplace_front(key, std::move(entry)); - cacheMap[key] = cacheList.begin(); - currentCacheSize += cacheList.begin()->second.size; - - while (currentCacheSize > maxCacheSize) { - auto last = cacheList.end(); - --last; - currentCacheSize -= last->second.size; - cacheMap.erase(last->first); - cacheList.pop_back(); - } - } catch (const std::exception& e) { - std::cerr << "Error occurred: " << e.what() << std::endl; - } - } + // - success => return the `data` field from response json // maybe json object or json array // - error => return error message template Api> Task request(http::verb verb, urls::url_view url, - std::initializer_list const& params = {}) { + std::initializer_list const& params = {}, + bool useCache = false) { try { - //Generate cache - std::string cacheKey = generateCacheKey(verb, url, params); - - //Check cache - auto it = cacheMap.find(cacheKey); - if (it != cacheMap.end() && !isCacheEntryExpired(it->second->second)) { - cacheList.splice(cacheList.begin(), cacheList, it->second); - co_return it->second->second.result; + spdlog::info("Requesting: {}", url.data()); + + auto cacheKey = CacheManager::generateKey(verb, url, params); + + if (useCache) { + //Generate cache + + //Check cache + auto cacheEntry = _cacheManager->get(cacheKey); + + if (cacheEntry) { + spdlog::info("Cache hit: {}", cacheKey); + co_return Ok(cacheEntry->result); + } } - debug(), url; + auto req = Api::makeRequest(verb, url, tokenBytes, params); - auto reply = co_await _manager->makeReply(url.host(), req); + auto reply = co_await _httpsAccessManager->makeReply(url.host(), req); if (reply.isErr()) co_return reply.unwrapErr(); auto result = handleResponse(reply.unwrap()); - // Update cache - size_t entrySize = result.unwrap().dump().size(); - updateCache(cacheKey, - CacheEntry{std::move(result), std::chrono::steady_clock::now(), entrySize}); + if (useCache && result.isOk()) { + // Update cache + size_t entrySize = result.unwrap().dump().size(); + + _cacheManager->insert(cacheKey, + {std::move(result.unwrap()), + std::chrono::steady_clock::now(), + entrySize}); + } co_return result; } catch (const std::exception& e) { @@ -176,7 +152,7 @@ class NetworkClient { urls::url_view url, std::initializer_list const& params = {}) { auto req = Api::makeRequest(verb, url, std::nullopt, params); - auto reply = co_await _manager->makeReply(url.host(), req); + auto reply = co_await _httpsAccessManager->makeReply(url.host(), req); if (reply.isErr()) co_return reply.unwrapErr(); @@ -204,11 +180,8 @@ class NetworkClient { private: net::ssl::context& _ctx; - std::unique_ptr _manager; - std::list> cacheList; // LRU 缓存列表 - std::unordered_map cacheMap; // 缓存映射(changed,TBD) - size_t currentCacheSize = 0; // 当前缓存大小 - const size_t maxCacheSize = 64 * 1024 * 1024; + std::unique_ptr _httpsAccessManager; + std::unique_ptr _cacheManager; friend NetworkClient* networkClient(); };