Add caching support

This commit is contained in:
blankie 2023-11-25 14:39:35 +11:00
parent b76128f82b
commit 26cdb5cc4f
Signed by: blankie
GPG Key ID: CC15FC822C7F61F5
6 changed files with 83 additions and 22 deletions

View File

@ -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}

View File

@ -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<Account> MastodonClient::get_account_by_username(const std::string& host, const std::string& username) {
std::optional<Account> 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<Account> MastodonClient::get_account_by_username(const std::string
}
}
std::vector<Post> MastodonClient::get_pinned_posts(const std::string& host, const std::string& account_id) {
std::vector<Post> MastodonClient::get_pinned_posts(std::string host, const std::string& account_id) {
using namespace std::string_literals;
lowercase(host);
std::vector<Post> posts = this->_send_request("https://"s + host + "/api/v1/accounts/" + account_id + "/statuses?pinned=true");
std::vector<Post> 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<Post> MastodonClient::get_posts(const std::string& host, const std::
url += '?';
url += query;
}
std::vector<Post> posts = this->_send_request(url);
std::vector<Post> posts = this->_send_request(std::nullopt, url);
for (Post& post : posts) {
handle_post_server(post, host);
@ -128,7 +136,7 @@ std::optional<Post> 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<Post> 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<Post> MastodonClient::get_tag_timeline(const std::string& host, cons
url += std::move(*max_id);
}
std::vector<Post> posts = this->_send_request(url);
std::vector<Post> 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<std::string>());
}
@ -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<std::string> cache_key, const std::string& url) {
std::optional<std::string> 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<std::string>());
}
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) {

View File

@ -64,8 +64,8 @@ public:
curl_global_cleanup();
}
std::optional<Account> get_account_by_username(const std::string& host, const std::string& username);
std::vector<Post> get_pinned_posts(const std::string& host, const std::string& account_id);
std::optional<Account> get_account_by_username(std::string host, std::string username);
std::vector<Post> get_pinned_posts(std::string host, const std::string& account_id);
std::vector<Post> get_posts(const std::string& host, const std::string& account_id, PostSortingMethod sorting_method, std::optional<std::string> max_id);
std::optional<Post> get_post(const std::string& host, const std::string& id);
@ -73,12 +73,12 @@ public:
std::vector<Post> get_tag_timeline(const std::string& host, const std::string& tag, std::optional<std::string> 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<std::string> cache_key, const std::string& url);
long _response_status_code();
std::mutex _share_locks[CURL_LOCK_DATA_LAST];

View File

@ -1,5 +1,9 @@
#include "config.h"
#include "hiredis_wrapper.h"
std::optional<Redis> 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<IPConnection>(&config.redis_config->connection_method)) {
redis.emplace(ip->address, ip->port);
} else if (const UnixConnection* unix = std::get_if<UnixConnection>(&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);
}
}

View File

@ -71,3 +71,6 @@ private:
bool _fake_expire_nx = false;
};
extern std::optional<Redis> redis;
void init_redis();

View File

@ -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);