Add caching support
This commit is contained in:
parent
b76128f82b
commit
26cdb5cc4f
|
@ -9,8 +9,8 @@ set(HTTPLIB_REQUIRE_OPENSSL ON)
|
||||||
add_subdirectory(thirdparty/httplib)
|
add_subdirectory(thirdparty/httplib)
|
||||||
set(LEXBOR_BUILD_SHARED OFF)
|
set(LEXBOR_BUILD_SHARED OFF)
|
||||||
add_subdirectory(thirdparty/lexbor)
|
add_subdirectory(thirdparty/lexbor)
|
||||||
#find_package(PkgConfig REQUIRED)
|
find_package(PkgConfig REQUIRED)
|
||||||
#pkg_check_modules(HIREDIS REQUIRED hiredis)
|
pkg_check_modules(HIREDIS REQUIRED hiredis)
|
||||||
|
|
||||||
|
|
||||||
if (CMAKE_BUILD_TYPE MATCHES "Debug|RelWithDebInfo")
|
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_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
|
routes/home.cpp routes/css.cpp routes/user.cpp routes/status.cpp routes/tags.cpp routes/about.cpp
|
||||||
blankie/serializer.cpp blankie/escape.cpp)
|
blankie/serializer.cpp blankie/escape.cpp)
|
||||||
set_target_properties(${PROJECT_NAME}
|
set_target_properties(${PROJECT_NAME}
|
||||||
|
|
54
client.cpp
54
client.cpp
|
@ -4,9 +4,11 @@
|
||||||
|
|
||||||
#include "client.h"
|
#include "client.h"
|
||||||
#include "models.h"
|
#include "models.h"
|
||||||
|
#include "hiredis_wrapper.h"
|
||||||
|
|
||||||
MastodonClient mastodon_client;
|
MastodonClient mastodon_client;
|
||||||
|
|
||||||
|
static void lowercase(std::string& str);
|
||||||
static void handle_post_server(Post& post, const std::string& host);
|
static void handle_post_server(Post& post, const std::string& host);
|
||||||
static std::string url_encode(const std::string& in);
|
static std::string url_encode(const std::string& in);
|
||||||
static inline void hexencode(char c, char out[2]);
|
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;
|
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 {
|
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;
|
account.same_server = host == account.server;
|
||||||
return account;
|
return account;
|
||||||
} catch (const MastodonException& e) {
|
} 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;
|
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) {
|
for (Post& post : posts) {
|
||||||
handle_post_server(post, host);
|
handle_post_server(post, host);
|
||||||
|
@ -115,7 +123,7 @@ std::vector<Post> MastodonClient::get_posts(const std::string& host, const std::
|
||||||
url += '?';
|
url += '?';
|
||||||
url += query;
|
url += query;
|
||||||
}
|
}
|
||||||
std::vector<Post> posts = this->_send_request(url);
|
std::vector<Post> posts = this->_send_request(std::nullopt, url);
|
||||||
|
|
||||||
for (Post& post : posts) {
|
for (Post& post : posts) {
|
||||||
handle_post_server(post, host);
|
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;
|
using namespace std::string_literals;
|
||||||
|
|
||||||
try {
|
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);
|
handle_post_server(post, host);
|
||||||
return post;
|
return post;
|
||||||
} catch (const MastodonException& e) {
|
} 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) {
|
PostContext MastodonClient::get_post_context(const std::string& host, const std::string& id) {
|
||||||
using namespace std::string_literals;
|
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) {
|
for (Post& post : context.ancestors) {
|
||||||
handle_post_server(post, host);
|
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);
|
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) {
|
for (Post& post : posts) {
|
||||||
handle_post_server(post, host);
|
handle_post_server(post, host);
|
||||||
}
|
}
|
||||||
return posts;
|
return posts;
|
||||||
}
|
}
|
||||||
|
|
||||||
Instance MastodonClient::get_instance(const std::string& host) {
|
Instance MastodonClient::get_instance(std::string host) {
|
||||||
using namespace std::string_literals;
|
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;
|
instance.contact_account.same_server = instance.contact_account.server == host;
|
||||||
return instance;
|
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;
|
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>());
|
return blankie::html::HTMLString(j.at("content").get<std::string>());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -214,7 +224,12 @@ CURL* MastodonClient::_get_easy() {
|
||||||
return curl;
|
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;
|
std::string res;
|
||||||
CURL* curl = this->_get_easy();
|
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();
|
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) {
|
if (response_code != 200) {
|
||||||
throw MastodonException(response_code, j.at("error").get<std::string>());
|
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;
|
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) {
|
static void handle_post_server(Post& post, const std::string& host) {
|
||||||
post.account.same_server = host == post.account.server;
|
post.account.same_server = host == post.account.server;
|
||||||
if (post.reblog) {
|
if (post.reblog) {
|
||||||
|
|
10
client.h
10
client.h
|
@ -64,8 +64,8 @@ public:
|
||||||
curl_global_cleanup();
|
curl_global_cleanup();
|
||||||
}
|
}
|
||||||
|
|
||||||
std::optional<Account> get_account_by_username(const std::string& host, const std::string& username);
|
std::optional<Account> get_account_by_username(std::string host, std::string username);
|
||||||
std::vector<Post> get_pinned_posts(const std::string& host, const std::string& account_id);
|
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::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);
|
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);
|
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);
|
Instance get_instance(std::string host);
|
||||||
blankie::html::HTMLString get_extended_description(const std::string& host);
|
blankie::html::HTMLString get_extended_description(std::string host);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
CURL* _get_easy();
|
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();
|
long _response_status_code();
|
||||||
|
|
||||||
std::mutex _share_locks[CURL_LOCK_DATA_LAST];
|
std::mutex _share_locks[CURL_LOCK_DATA_LAST];
|
||||||
|
|
|
@ -1,5 +1,9 @@
|
||||||
|
#include "config.h"
|
||||||
#include "hiredis_wrapper.h"
|
#include "hiredis_wrapper.h"
|
||||||
|
|
||||||
|
std::optional<Redis> redis;
|
||||||
|
|
||||||
|
|
||||||
Redis::Redis(const std::string& address, int port) {
|
Redis::Redis(const std::string& address, int port) {
|
||||||
this->_context = redisConnect(address.c_str(), port);
|
this->_context = redisConnect(address.c_str(), port);
|
||||||
if (!this->_context) {
|
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");
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -71,3 +71,6 @@ private:
|
||||||
|
|
||||||
bool _fake_expire_nx = false;
|
bool _fake_expire_nx = false;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
extern std::optional<Redis> redis;
|
||||||
|
void init_redis();
|
||||||
|
|
7
main.cpp
7
main.cpp
|
@ -3,6 +3,7 @@
|
||||||
|
|
||||||
#include "config.h"
|
#include "config.h"
|
||||||
#include "client.h"
|
#include "client.h"
|
||||||
|
#include "hiredis_wrapper.h"
|
||||||
#include "servehelper.h"
|
#include "servehelper.h"
|
||||||
#include "routes/routes.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());
|
fprintf(stderr, "Failed to load config: %s\n", e.what());
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
try {
|
||||||
|
init_redis();
|
||||||
|
} catch (const std::exception& e) {
|
||||||
|
fprintf(stderr, "Failed to init redis: %s\n", e.what());
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
MastodonClient::init();
|
MastodonClient::init();
|
||||||
atexit(MastodonClient::cleanup);
|
atexit(MastodonClient::cleanup);
|
||||||
|
|
Loading…
Reference in New Issue