Loading client/client.cpp +111 −0 Original line number Diff line number Diff line Loading @@ -46,6 +46,39 @@ namespace authdb { namespace client { // Static GPO cache members int Client::_GpoCacheTTL = Client::DEFAULT_GPO_CACHE_TTL; std::mutex Client::_GpoCacheMutex; std::unordered_map<std::string, std::chrono::steady_clock::time_point> Client::_GpoCache; void Client::setGpoCacheTTL(int seconds) { _GpoCacheTTL = seconds; } bool Client::checkGpoCache(const std::string& key) { std::lock_guard<std::mutex> lk(_GpoCacheMutex); auto it = _GpoCache.find(key); if (it == _GpoCache.end()) return false; if (std::chrono::steady_clock::now() > it->second) { _GpoCache.erase(it); return false; } return true; } void Client::cacheGpoResult(const std::string& key) { std::lock_guard<std::mutex> lk(_GpoCacheMutex); _GpoCache[key] = std::chrono::steady_clock::now() + std::chrono::seconds(_GpoCacheTTL); if (_GpoCache.size() > 2000) { auto now = std::chrono::steady_clock::now(); for (auto ci = _GpoCache.begin(); ci != _GpoCache.end(); ) { if (now > ci->second) ci = _GpoCache.erase(ci); else ++ci; } } } namespace { std::vector<char> safe_post(libhttppp::HttpClient& client, libhttppp::HttpRequest& req, const std::vector<char>& body, size_t maxTries) { Loading Loading @@ -627,6 +660,10 @@ void Client::getAvatar(const ::uuid::uuid& did, const ::uuid::uuid& uid, } bool Client::GPOcheck(class ::uuid::uuid gpoid) { // Check cache first — avoid network round-trip std::string cache_key = std::string(_Con.getSessionID().c_str()) + ":" + gpoid.c_str(); if (checkGpoCache(cache_key)) return true; ensure_http_client(_Con); json_object* jresponse = json_object_new_object(); Loading Loading @@ -692,6 +729,7 @@ bool Client::GPOcheck(class ::uuid::uuid gpoid) { } json_object_put(jrequest); json_tokener_free(jtok); if (ret) cacheGpoResult(cache_key); return ret; } Loading Loading @@ -1058,5 +1096,78 @@ std::vector<Client::GroupInfo> Client::listGroups() { return groups; } // -------- PooledConnection -------- PooledConnection::PooledConnection(ConnectionPool* pool, std::unique_ptr<ClientConnection> con, std::unique_ptr<Client> clt) : _Pool(pool), _Con(std::move(con)), _Clt(std::move(clt)) {} PooledConnection::PooledConnection(PooledConnection&& other) noexcept : _Pool(other._Pool), _Con(std::move(other._Con)), _Clt(std::move(other._Clt)) { other._Pool = nullptr; } PooledConnection& PooledConnection::operator=(PooledConnection&& other) noexcept { if (this != &other) { if (_Pool && _Con && _Clt) _Pool->release(std::move(_Con), std::move(_Clt)); _Pool = other._Pool; _Con = std::move(other._Con); _Clt = std::move(other._Clt); other._Pool = nullptr; } return *this; } PooledConnection::~PooledConnection() { if (_Pool && _Con && _Clt) _Pool->release(std::move(_Con), std::move(_Clt)); } ClientConnection& PooledConnection::connection() { return *_Con; } Client& PooledConnection::client() { return *_Clt; } void PooledConnection::setSessionID(const uuid::uuid& sid) { _Con->setSessionID(sid); } // -------- ConnectionPool -------- ConnectionPool::ConnectionPool(const std::string& url, const std::string& client_name, const std::string& client_secret, std::size_t max_idle) : _Url(url), _CltName(client_name), _CltSecret(client_secret), _MaxIdle(max_idle) {} ConnectionPool::~ConnectionPool() = default; PooledConnection ConnectionPool::acquire() { { std::lock_guard<std::mutex> lk(_Mutex); if (!_Idle.empty()) { auto entry = std::move(_Idle.front()); _Idle.pop_front(); return PooledConnection(this, std::move(entry.con), std::move(entry.clt)); } } // Create new connection auto con = std::make_unique<ClientConnection>(); con->setUrl(_Url); con->setClientName(_CltName); con->setClientSecret(_CltSecret); auto clt = std::make_unique<Client>(*con); return PooledConnection(this, std::move(con), std::move(clt)); } void ConnectionPool::release(std::unique_ptr<ClientConnection> con, std::unique_ptr<Client> clt) { std::lock_guard<std::mutex> lk(_Mutex); if (_Idle.size() < _MaxIdle) { _Idle.push_back({std::move(con), std::move(clt)}); } // else: drop — exceeds max idle, connection closes } } // namespace client } // namespace authdb client/client.h +80 −0 Original line number Diff line number Diff line Loading @@ -28,6 +28,10 @@ #include <string> #include <memory> #include <vector> #include <mutex> #include <deque> #include <unordered_map> #include <chrono> #include <uuidp.h> #ifndef Windows Loading Loading @@ -129,9 +133,85 @@ namespace authdb { }; std::vector<GroupInfo> listGroups(); // GPO result cache TTL in seconds (default 300 = 5 min) static void setGpoCacheTTL(int seconds); private: ClientConnection &_Con; // Shared GPO result cache: "session_id:gpo_id" -> expiry static constexpr int DEFAULT_GPO_CACHE_TTL = 300; static int _GpoCacheTTL; static std::mutex _GpoCacheMutex; static std::unordered_map<std::string, std::chrono::steady_clock::time_point> _GpoCache; static bool checkGpoCache(const std::string& key); static void cacheGpoResult(const std::string& key); friend class Session; }; // ---- ConnectionPool: reuses TCP connections to authdb ---- class ConnectionPool; class VISIBILITY PooledConnection { public: PooledConnection(PooledConnection&& other) noexcept; PooledConnection& operator=(PooledConnection&& other) noexcept; ~PooledConnection(); PooledConnection(const PooledConnection&) = delete; PooledConnection& operator=(const PooledConnection&) = delete; // Access the underlying connection and client ClientConnection& connection(); Client& client(); // Set per-request session ID void setSessionID(const uuid::uuid& sid); private: friend class ConnectionPool; PooledConnection(ConnectionPool* pool, std::unique_ptr<ClientConnection> con, std::unique_ptr<Client> clt); ConnectionPool* _Pool; std::unique_ptr<ClientConnection> _Con; std::unique_ptr<Client> _Clt; }; class VISIBILITY ConnectionPool { public: ConnectionPool(const std::string& url, const std::string& client_name, const std::string& client_secret, std::size_t max_idle = 4); ~ConnectionPool(); ConnectionPool(const ConnectionPool&) = delete; ConnectionPool& operator=(const ConnectionPool&) = delete; // Acquire a ready-to-use connection (creates new if pool empty) PooledConnection acquire(); private: friend class PooledConnection; void release(std::unique_ptr<ClientConnection> con, std::unique_ptr<Client> clt); std::string _Url; std::string _CltName; std::string _CltSecret; std::size_t _MaxIdle; std::mutex _Mutex; struct IdleEntry { std::unique_ptr<ClientConnection> con; std::unique_ptr<Client> clt; }; std::deque<IdleEntry> _Idle; }; }; }; debian/changelog +9 −0 Original line number Diff line number Diff line authdb (20260424+7) unstable; urgency=medium * Client library: add ConnectionPool class for TCP connection reuse. PooledConnection RAII wrapper auto-returns connections to the pool. * Client library: add GPO result cache (5 min TTL) in GPOcheck() to avoid redundant network round-trips for repeated policy checks. -- Jan Koester <jan.koester@tuxist.de> Thu, 24 Apr 2026 00:00:00 +0200 authdb (20260423+6) unstable; urgency=high * Cluster health monitor: replace sentinel store+remove with filesystem Loading Loading
client/client.cpp +111 −0 Original line number Diff line number Diff line Loading @@ -46,6 +46,39 @@ namespace authdb { namespace client { // Static GPO cache members int Client::_GpoCacheTTL = Client::DEFAULT_GPO_CACHE_TTL; std::mutex Client::_GpoCacheMutex; std::unordered_map<std::string, std::chrono::steady_clock::time_point> Client::_GpoCache; void Client::setGpoCacheTTL(int seconds) { _GpoCacheTTL = seconds; } bool Client::checkGpoCache(const std::string& key) { std::lock_guard<std::mutex> lk(_GpoCacheMutex); auto it = _GpoCache.find(key); if (it == _GpoCache.end()) return false; if (std::chrono::steady_clock::now() > it->second) { _GpoCache.erase(it); return false; } return true; } void Client::cacheGpoResult(const std::string& key) { std::lock_guard<std::mutex> lk(_GpoCacheMutex); _GpoCache[key] = std::chrono::steady_clock::now() + std::chrono::seconds(_GpoCacheTTL); if (_GpoCache.size() > 2000) { auto now = std::chrono::steady_clock::now(); for (auto ci = _GpoCache.begin(); ci != _GpoCache.end(); ) { if (now > ci->second) ci = _GpoCache.erase(ci); else ++ci; } } } namespace { std::vector<char> safe_post(libhttppp::HttpClient& client, libhttppp::HttpRequest& req, const std::vector<char>& body, size_t maxTries) { Loading Loading @@ -627,6 +660,10 @@ void Client::getAvatar(const ::uuid::uuid& did, const ::uuid::uuid& uid, } bool Client::GPOcheck(class ::uuid::uuid gpoid) { // Check cache first — avoid network round-trip std::string cache_key = std::string(_Con.getSessionID().c_str()) + ":" + gpoid.c_str(); if (checkGpoCache(cache_key)) return true; ensure_http_client(_Con); json_object* jresponse = json_object_new_object(); Loading Loading @@ -692,6 +729,7 @@ bool Client::GPOcheck(class ::uuid::uuid gpoid) { } json_object_put(jrequest); json_tokener_free(jtok); if (ret) cacheGpoResult(cache_key); return ret; } Loading Loading @@ -1058,5 +1096,78 @@ std::vector<Client::GroupInfo> Client::listGroups() { return groups; } // -------- PooledConnection -------- PooledConnection::PooledConnection(ConnectionPool* pool, std::unique_ptr<ClientConnection> con, std::unique_ptr<Client> clt) : _Pool(pool), _Con(std::move(con)), _Clt(std::move(clt)) {} PooledConnection::PooledConnection(PooledConnection&& other) noexcept : _Pool(other._Pool), _Con(std::move(other._Con)), _Clt(std::move(other._Clt)) { other._Pool = nullptr; } PooledConnection& PooledConnection::operator=(PooledConnection&& other) noexcept { if (this != &other) { if (_Pool && _Con && _Clt) _Pool->release(std::move(_Con), std::move(_Clt)); _Pool = other._Pool; _Con = std::move(other._Con); _Clt = std::move(other._Clt); other._Pool = nullptr; } return *this; } PooledConnection::~PooledConnection() { if (_Pool && _Con && _Clt) _Pool->release(std::move(_Con), std::move(_Clt)); } ClientConnection& PooledConnection::connection() { return *_Con; } Client& PooledConnection::client() { return *_Clt; } void PooledConnection::setSessionID(const uuid::uuid& sid) { _Con->setSessionID(sid); } // -------- ConnectionPool -------- ConnectionPool::ConnectionPool(const std::string& url, const std::string& client_name, const std::string& client_secret, std::size_t max_idle) : _Url(url), _CltName(client_name), _CltSecret(client_secret), _MaxIdle(max_idle) {} ConnectionPool::~ConnectionPool() = default; PooledConnection ConnectionPool::acquire() { { std::lock_guard<std::mutex> lk(_Mutex); if (!_Idle.empty()) { auto entry = std::move(_Idle.front()); _Idle.pop_front(); return PooledConnection(this, std::move(entry.con), std::move(entry.clt)); } } // Create new connection auto con = std::make_unique<ClientConnection>(); con->setUrl(_Url); con->setClientName(_CltName); con->setClientSecret(_CltSecret); auto clt = std::make_unique<Client>(*con); return PooledConnection(this, std::move(con), std::move(clt)); } void ConnectionPool::release(std::unique_ptr<ClientConnection> con, std::unique_ptr<Client> clt) { std::lock_guard<std::mutex> lk(_Mutex); if (_Idle.size() < _MaxIdle) { _Idle.push_back({std::move(con), std::move(clt)}); } // else: drop — exceeds max idle, connection closes } } // namespace client } // namespace authdb
client/client.h +80 −0 Original line number Diff line number Diff line Loading @@ -28,6 +28,10 @@ #include <string> #include <memory> #include <vector> #include <mutex> #include <deque> #include <unordered_map> #include <chrono> #include <uuidp.h> #ifndef Windows Loading Loading @@ -129,9 +133,85 @@ namespace authdb { }; std::vector<GroupInfo> listGroups(); // GPO result cache TTL in seconds (default 300 = 5 min) static void setGpoCacheTTL(int seconds); private: ClientConnection &_Con; // Shared GPO result cache: "session_id:gpo_id" -> expiry static constexpr int DEFAULT_GPO_CACHE_TTL = 300; static int _GpoCacheTTL; static std::mutex _GpoCacheMutex; static std::unordered_map<std::string, std::chrono::steady_clock::time_point> _GpoCache; static bool checkGpoCache(const std::string& key); static void cacheGpoResult(const std::string& key); friend class Session; }; // ---- ConnectionPool: reuses TCP connections to authdb ---- class ConnectionPool; class VISIBILITY PooledConnection { public: PooledConnection(PooledConnection&& other) noexcept; PooledConnection& operator=(PooledConnection&& other) noexcept; ~PooledConnection(); PooledConnection(const PooledConnection&) = delete; PooledConnection& operator=(const PooledConnection&) = delete; // Access the underlying connection and client ClientConnection& connection(); Client& client(); // Set per-request session ID void setSessionID(const uuid::uuid& sid); private: friend class ConnectionPool; PooledConnection(ConnectionPool* pool, std::unique_ptr<ClientConnection> con, std::unique_ptr<Client> clt); ConnectionPool* _Pool; std::unique_ptr<ClientConnection> _Con; std::unique_ptr<Client> _Clt; }; class VISIBILITY ConnectionPool { public: ConnectionPool(const std::string& url, const std::string& client_name, const std::string& client_secret, std::size_t max_idle = 4); ~ConnectionPool(); ConnectionPool(const ConnectionPool&) = delete; ConnectionPool& operator=(const ConnectionPool&) = delete; // Acquire a ready-to-use connection (creates new if pool empty) PooledConnection acquire(); private: friend class PooledConnection; void release(std::unique_ptr<ClientConnection> con, std::unique_ptr<Client> clt); std::string _Url; std::string _CltName; std::string _CltSecret; std::size_t _MaxIdle; std::mutex _Mutex; struct IdleEntry { std::unique_ptr<ClientConnection> con; std::unique_ptr<Client> clt; }; std::deque<IdleEntry> _Idle; }; }; };
debian/changelog +9 −0 Original line number Diff line number Diff line authdb (20260424+7) unstable; urgency=medium * Client library: add ConnectionPool class for TCP connection reuse. PooledConnection RAII wrapper auto-returns connections to the pool. * Client library: add GPO result cache (5 min TTL) in GPOcheck() to avoid redundant network round-trips for repeated policy checks. -- Jan Koester <jan.koester@tuxist.de> Thu, 24 Apr 2026 00:00:00 +0200 authdb (20260423+6) unstable; urgency=high * Cluster health monitor: replace sentinel store+remove with filesystem Loading