From 26cdb5cc4f7814b0a7b0dda92d6f665744db6eb6 Mon Sep 17 00:00:00 2001 From: blankie Date: Sat, 25 Nov 2023 14:39:35 +1100 Subject: [PATCH] Add caching support --- CMakeLists.txt | 6 ++--- client.cpp | 54 +++++++++++++++++++++++++++++++++------------ client.h | 10 ++++----- hiredis_wrapper.cpp | 25 +++++++++++++++++++++ hiredis_wrapper.h | 3 +++ main.cpp | 7 ++++++ 6 files changed, 83 insertions(+), 22 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index d86d122..1117cfa 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -9,8 +9,8 @@ set(HTTPLIB_REQUIRE_OPENSSL ON) add_subdirectory(thirdparty/httplib) set(LEXBOR_BUILD_SHARED OFF) add_subdirectory(thirdparty/lexbor) -#find_package(PkgConfig REQUIRED) -#pkg_check_modules(HIREDIS REQUIRED hiredis) +find_package(PkgConfig REQUIRED) +pkg_check_modules(HIREDIS REQUIRED hiredis) if (CMAKE_BUILD_TYPE MATCHES "Debug|RelWithDebInfo") @@ -29,7 +29,7 @@ list(APPEND FLAGS -Werror -Wall -Wextra -Wshadow -Wpedantic -Wno-gnu-anonymous-s add_link_options(${FLAGS}) -add_executable(${PROJECT_NAME} main.cpp numberhelper.cpp config.cpp models.cpp client.cpp servehelper.cpp timeutils.cpp +add_executable(${PROJECT_NAME} main.cpp numberhelper.cpp config.cpp models.cpp client.cpp servehelper.cpp timeutils.cpp hiredis_wrapper.cpp routes/home.cpp routes/css.cpp routes/user.cpp routes/status.cpp routes/tags.cpp routes/about.cpp blankie/serializer.cpp blankie/escape.cpp) set_target_properties(${PROJECT_NAME} diff --git a/client.cpp b/client.cpp index 22432a7..e43ae21 100644 --- a/client.cpp +++ b/client.cpp @@ -4,9 +4,11 @@ #include "client.h" #include "models.h" +#include "hiredis_wrapper.h" MastodonClient mastodon_client; +static void lowercase(std::string& str); static void handle_post_server(Post& post, const std::string& host); static std::string url_encode(const std::string& in); static inline void hexencode(char c, char out[2]); @@ -69,11 +71,16 @@ MastodonClient::~MastodonClient() { } } -std::optional MastodonClient::get_account_by_username(const std::string& host, const std::string& username) { +std::optional MastodonClient::get_account_by_username(std::string host, std::string username) { using namespace std::string_literals; + lowercase(host); + lowercase(username); + if (username.size() > host.size() && username.ends_with(host) && username[username.size() - host.size() - 1] == '@') { + username.erase(username.size() - host.size() - 1); + } try { - Account account = this->_send_request("https://"s + host + "/api/v1/accounts/lookup?acct=" + url_encode(username)); + Account account = this->_send_request("coyote:"s + host + ":@" + username, "https://"s + host + "/api/v1/accounts/lookup?acct=" + url_encode(username)); account.same_server = host == account.server; return account; } catch (const MastodonException& e) { @@ -85,10 +92,11 @@ std::optional MastodonClient::get_account_by_username(const std::string } } -std::vector MastodonClient::get_pinned_posts(const std::string& host, const std::string& account_id) { +std::vector MastodonClient::get_pinned_posts(std::string host, const std::string& account_id) { using namespace std::string_literals; + lowercase(host); - std::vector posts = this->_send_request("https://"s + host + "/api/v1/accounts/" + account_id + "/statuses?pinned=true"); + std::vector posts = this->_send_request("coyote:"s + host + ':' + account_id + ":pinned", "https://"s + host + "/api/v1/accounts/" + account_id + "/statuses?pinned=true"); for (Post& post : posts) { handle_post_server(post, host); @@ -115,7 +123,7 @@ std::vector MastodonClient::get_posts(const std::string& host, const std:: url += '?'; url += query; } - std::vector posts = this->_send_request(url); + std::vector posts = this->_send_request(std::nullopt, url); for (Post& post : posts) { handle_post_server(post, host); @@ -128,7 +136,7 @@ std::optional MastodonClient::get_post(const std::string& host, const std: using namespace std::string_literals; try { - Post post = this->_send_request("https://"s + host + "/api/v1/statuses/" + id); + Post post = this->_send_request(std::nullopt, "https://"s + host + "/api/v1/statuses/" + id); handle_post_server(post, host); return post; } catch (const MastodonException& e) { @@ -143,7 +151,7 @@ std::optional MastodonClient::get_post(const std::string& host, const std: PostContext MastodonClient::get_post_context(const std::string& host, const std::string& id) { using namespace std::string_literals; - PostContext context = this->_send_request("https://"s + host + "/api/v1/statuses/" + id + "/context"); + PostContext context = this->_send_request(std::nullopt, "https://"s + host + "/api/v1/statuses/" + id + "/context"); for (Post& post : context.ancestors) { handle_post_server(post, host); @@ -164,25 +172,27 @@ std::vector MastodonClient::get_tag_timeline(const std::string& host, cons url += std::move(*max_id); } - std::vector posts = this->_send_request(url); + std::vector posts = this->_send_request(std::nullopt, url); for (Post& post : posts) { handle_post_server(post, host); } return posts; } -Instance MastodonClient::get_instance(const std::string& host) { +Instance MastodonClient::get_instance(std::string host) { using namespace std::string_literals; + lowercase(host); - Instance instance = this->_send_request("https://"s + host + "/api/v2/instance"); + Instance instance = this->_send_request("coyote:"s + host + ":instance", "https://"s + host + "/api/v2/instance"); instance.contact_account.same_server = instance.contact_account.server == host; return instance; } -blankie::html::HTMLString MastodonClient::get_extended_description(const std::string& host) { +blankie::html::HTMLString MastodonClient::get_extended_description(std::string host) { using namespace std::string_literals; + lowercase(host); - nlohmann::json j = this->_send_request("https://"s + host + "/api/v1/instance/extended_description"); + nlohmann::json j = this->_send_request("coyote:"s + host + ":desc", "https://"s + host + "/api/v1/instance/extended_description"); return blankie::html::HTMLString(j.at("content").get()); } @@ -214,7 +224,12 @@ CURL* MastodonClient::_get_easy() { return curl; } -nlohmann::json MastodonClient::_send_request(const std::string& url) { +nlohmann::json MastodonClient::_send_request(std::optional cache_key, const std::string& url) { + std::optional cached; + if (redis && cache_key && (cached = redis->get(*cache_key))) { + return nlohmann::json::parse(std::move(*cached)); + } + std::string res; CURL* curl = this->_get_easy(); @@ -227,11 +242,14 @@ nlohmann::json MastodonClient::_send_request(const std::string& url) { } long response_code = this->_response_status_code(); - nlohmann::json j = nlohmann::json::parse(std::move(res)); + nlohmann::json j = nlohmann::json::parse(res); if (response_code != 200) { throw MastodonException(response_code, j.at("error").get()); } + if (redis && cache_key) { + redis->set(*cache_key, std::move(res), 60 * 60); + } return j; } @@ -245,6 +263,14 @@ long MastodonClient::_response_status_code() { } +static void lowercase(std::string& str) { + for (size_t i = 0; i < str.size(); i++) { + if (str[i] >= 'A' && str[i] <= 'Z') { + str[i] = str[i] - 'A' + 'a'; + } + } +} + static void handle_post_server(Post& post, const std::string& host) { post.account.same_server = host == post.account.server; if (post.reblog) { diff --git a/client.h b/client.h index 1672a7e..1b04f79 100644 --- a/client.h +++ b/client.h @@ -64,8 +64,8 @@ public: curl_global_cleanup(); } - std::optional get_account_by_username(const std::string& host, const std::string& username); - std::vector get_pinned_posts(const std::string& host, const std::string& account_id); + std::optional get_account_by_username(std::string host, std::string username); + std::vector get_pinned_posts(std::string host, const std::string& account_id); std::vector get_posts(const std::string& host, const std::string& account_id, PostSortingMethod sorting_method, std::optional max_id); std::optional get_post(const std::string& host, const std::string& id); @@ -73,12 +73,12 @@ public: std::vector get_tag_timeline(const std::string& host, const std::string& tag, std::optional max_id); - Instance get_instance(const std::string& host); - blankie::html::HTMLString get_extended_description(const std::string& host); + Instance get_instance(std::string host); + blankie::html::HTMLString get_extended_description(std::string host); private: CURL* _get_easy(); - nlohmann::json _send_request(const std::string& url); + nlohmann::json _send_request(std::optional cache_key, const std::string& url); long _response_status_code(); std::mutex _share_locks[CURL_LOCK_DATA_LAST]; diff --git a/hiredis_wrapper.cpp b/hiredis_wrapper.cpp index dfa94ec..bef8426 100644 --- a/hiredis_wrapper.cpp +++ b/hiredis_wrapper.cpp @@ -1,5 +1,9 @@ +#include "config.h" #include "hiredis_wrapper.h" +std::optional redis; + + Redis::Redis(const std::string& address, int port) { this->_context = redisConnect(address.c_str(), port); if (!this->_context) { @@ -126,3 +130,24 @@ void Redis::hset(const std::string& key, const std::string& field, const std::st throw std::runtime_error("SET gave an unexpected return type"); } } + + +void init_redis() { + if (!config.redis_config) { + return; + } + + if (const IPConnection* ip = std::get_if(&config.redis_config->connection_method)) { + redis.emplace(ip->address, ip->port); + } else if (const UnixConnection* unix = std::get_if(&config.redis_config->connection_method)) { + redis.emplace(unix->unix); + } else { + __builtin_unreachable(); + } + + if (config.redis_config->username && config.redis_config->password) { + redis->auth(*config.redis_config->username, *config.redis_config->password); + } else if (config.redis_config->password) { + redis->auth(*config.redis_config->password); + } +} diff --git a/hiredis_wrapper.h b/hiredis_wrapper.h index 8f3473b..76c4284 100644 --- a/hiredis_wrapper.h +++ b/hiredis_wrapper.h @@ -71,3 +71,6 @@ private: bool _fake_expire_nx = false; }; + +extern std::optional redis; +void init_redis(); diff --git a/main.cpp b/main.cpp index f8379f5..f38c7bb 100644 --- a/main.cpp +++ b/main.cpp @@ -3,6 +3,7 @@ #include "config.h" #include "client.h" +#include "hiredis_wrapper.h" #include "servehelper.h" #include "routes/routes.h" @@ -28,6 +29,12 @@ int main(int argc, char** argv) { fprintf(stderr, "Failed to load config: %s\n", e.what()); return 1; } + try { + init_redis(); + } catch (const std::exception& e) { + fprintf(stderr, "Failed to init redis: %s\n", e.what()); + return 1; + } MastodonClient::init(); atexit(MastodonClient::cleanup);