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