diff --git a/Online/HTTP/include/HTTP/Cache.h b/Online/HTTP/include/HTTP/Cache.h new file mode 100644 index 0000000000000000000000000000000000000000..5a1ab8dc348dd5d38010a3eb087fad3e4d375065 --- /dev/null +++ b/Online/HTTP/include/HTTP/Cache.h @@ -0,0 +1,129 @@ +//========================================================================== +// LHCb Online software suite +//-------------------------------------------------------------------------- +// Copyright (C) Organisation europeenne pour la Recherche nucleaire (CERN) +// All rights reserved. +// +// For the licensing terms see OnlineSys/LICENSE. +// +//-------------------------------------------------------------------------- +// +// Package : RPC-GUI +// +// Author : Markus Frank +//========================================================================== +// Please note: +// This implementation was directly deduced (with some changes) from +// one of the boost::asio examples. +// boost::asio: one of the best I/O libraries ever ! +// +// Hence: +// +//========================================================================== +#ifndef HTTP_HTTP_CACHE_H +#define HTTP_HTTP_CACHE_H + +/// Framework include files +#include <HTTP/HttpReply.h> + +/// C/C++ include files +#include <ctime> +#include <mutex> +#include <string> +#include <memory> +#include <cstdint> +#include <climits> +#include <unordered_map> + +/// Namespace for the http server and client apps +namespace http { + + /// A abstract data cache to speed up HTTP requests + /** + * \author M.Frank + * \version 1.0 + * \date 10.01.2021 + */ + template <typename DATA> class Cache { + public: + /// Internal class representing a single cache entry + class entry_type { + public: + std::time_t timeout { std::numeric_limits<time_t>::max() }; + std::int64_t length { 0 }; + std::size_t hash { 0u }; + std::string path { }; + std::string encoding { }; + DATA data { }; + + public: + /// Default constructor + entry_type() = default; + /// Move constructor + entry_type(entry_type&& copy) = default; + /// Inhibit copy constructor + entry_type(const entry_type& copy) = delete; + /// Default destructor + ~entry_type() = default; + /// Move assignment operator + entry_type& operator=(entry_type&& copy) = default; + /// Inhibit assignment operator + entry_type& operator=(const entry_type& copy) = delete; + }; + + public: + typedef size_t key_type; + typedef std::unordered_map<key_type, std::unique_ptr<entry_type> > cache_type; + + public: + cache_type cache; + std::mutex lock; + std::hash<std::string> hash; + + protected: + void _set(entry_type* e, + key_type key, + std::time_t timeout, + const std::string& path, + const std::string& encoding, + const DATA& reply); + + public: + /// Default constructor + Cache(); + /// Inhibit move constructor + Cache(Cache&& copy) = delete; + /// Inhibit copy constructor + Cache(const Cache& copy) = delete; + /// Inhibit move assignment operator + Cache& operator=(Cache&& copy) = delete; + /// Inhibit assignment operator + Cache& operator=(const Cache& copy) = delete; + /// Default destructor + virtual ~Cache(); + /// Access number of entries in the cache + std::size_t size() const; + /// Find a cache entry by path. [Not thread safe, unlocked] + const entry_type* find(const std::string& key, + const std::string& encoding) const; + /// Find a cache entry by hash key [Not thread safe, unlocked] + const entry_type* find(key_type key, + const std::string& encoding) const; + /// Insert new entry into the cache [Not thread safe, unlocked] + bool insert(key_type key, + std::time_t timeout, + const std::string& path, + const std::string& encoding, + const http::HttpReply& reply); + /// Drop a cache entry by key [Not thread safe, unlocked] + std::size_t drop(key_type key); + + /// Clean "old" entries from cache [Thread safe, locked] + std::size_t drop_expired(); + /// Drop cache entries by key [Thread safe, locked] + std::size_t drop_key(key_type key); + /// Drop a cache entries by key [Thread safe, locked] + std::size_t drop_keys(const std::vector<key_type>& key); + }; +} // End namespace http +#endif // HTTP_HTTP_CACHE_H diff --git a/Online/HTTP/include/HTTP/Cache.inl.h b/Online/HTTP/include/HTTP/Cache.inl.h new file mode 100644 index 0000000000000000000000000000000000000000..d55742f084d79158af48476437dd1e8e801f03d7 --- /dev/null +++ b/Online/HTTP/include/HTTP/Cache.inl.h @@ -0,0 +1,126 @@ +//========================================================================== +// LHCb Online software suite +//-------------------------------------------------------------------------- +// Copyright (C) Organisation europeenne pour la Recherche nucleaire (CERN) +// All rights reserved. +// +// For the licensing terms see OnlineSys/LICENSE. +// +//-------------------------------------------------------------------------- +// +// Package : RPC +// +// Author : Markus Frank +//========================================================================== + +// Framework include files +#include <HTTP/Cache.h> + +// C/C++ include files +#include <cstring> + +/// Initializing constructor +template <typename DATA> http::Cache<DATA>::Cache() { +} + +/// Default destructor +template <typename DATA> http::Cache<DATA>::~Cache() { +} + +/// Access number of entries in the cache +template <typename DATA> std::size_t http::Cache<DATA>::size() const { + return this->cache.size(); +} + +/// Find a cache entry by path +template <typename DATA> const typename http::Cache<DATA>::entry_type* +http::Cache<DATA>::find(const std::string& key_name, const std::string& encoding) const { + return this->find(this->hash(key_name), encoding); +} + +/// Find a cache entry by hash key +template <typename DATA> const typename http::Cache<DATA>::entry_type* +http::Cache<DATA>::find(key_type key, const std::string& encoding) const { + auto range = this->cache.equal_range(key); + for( auto it = range.first; it != range.second; ++it ) { + if( encoding.find(it->second->encoding) != std::string::npos ) { + return it->second.get(); + } + } + return nullptr; +} + +/// Drop a cache entry by key +template <typename DATA> std::size_t http::Cache<DATA>::drop(key_type key) { + auto& c = this->cache; + std::size_t removed = 0; + for( auto it = c.find(key); it != c.end(); it = c.find(key) ) { + c.erase(it); + ++removed; + } + return removed; +} + +/// Insert new entry into the cache +template <typename DATA> +bool http::Cache<DATA>::insert(key_type key, + std::time_t tmo, + const std::string& path, + const std::string& encoding, + const http::HttpReply& reply) +{ + auto range = cache.equal_range(key); + for( auto it = range.first; it != range.second; ++it ) { + if( encoding == it->second->encoding ) { + _set(it->second.get(), key, tmo, path, encoding, reply); + return false; + } + } + auto e = std::make_unique<entry_type>(); + _set(e.get(), key, tmo, path, encoding, reply); + this->cache.emplace(key, std::move(e)); + return true; +} + +/// Clean "old" entries from cache [Thread safe, locked] +template <typename DATA> +std::size_t http::Cache<DATA>::drop_key(key_type key) { + std::lock_guard<std::mutex> lock(this->lock); + return this->drop(key); +} + +/// Drop a cache entries by key [Thread safe, locked] +template <typename DATA> +std::size_t http::Cache<DATA>::drop_keys(const std::vector<key_type>& keys) { + std::size_t cnt = 0; + Cache<DATA>::cache_type& c = this->cache; + std::lock_guard<std::mutex> lock(this->lock); + for( auto key : keys ) { + for( auto it = c.find(key); it != c.end(); it = c.find(key) ) { + ++cnt; + c.erase(it); + } + } + return cnt; +} + +/// Clean "old" entries from cache [Thread safe, locked] +template <typename DATA> +size_t http::Cache<DATA>::drop_expired() { + key_type last = 0; + std::size_t cnt = 0; + std::time_t now = ::time(0); + Cache<DATA>::cache_type& c = this->cache; + std::lock_guard<std::mutex> lock(this->lock); + for( auto it = c.begin(); it != c.end(); ++it ) { + auto* e = it->second.get(); + if ( now > e->timeout ) { + ++cnt; + c.erase(it); + it = (last != 0) ? c.find(last) : c.begin(); + } + last = (it == c.end()) ? 0 : it->first; + } + return cnt; +} + diff --git a/Online/HTTP/include/HTTP/HttpCacheCheck.h b/Online/HTTP/include/HTTP/HttpCacheCheck.h new file mode 100644 index 0000000000000000000000000000000000000000..487d004d4f5cb88fc6aa827c3bc7fbb7fc637469 --- /dev/null +++ b/Online/HTTP/include/HTTP/HttpCacheCheck.h @@ -0,0 +1,60 @@ +//========================================================================== +// LHCb Online software suite +//-------------------------------------------------------------------------- +// Copyright (C) Organisation europeenne pour la Recherche nucleaire (CERN) +// All rights reserved. +// +// For the licensing terms see OnlineSys/LICENSE. +// +//-------------------------------------------------------------------------- +// +// Package : RPC-GUI +// +// Author : Markus Frank +//========================================================================== +#ifndef HTTP_HTTP_HTTPCACHECHECK_H +#define HTTP_HTTP_HTTPCACHECHECK_H + +/// Framework include caches +#include <HTTP/Asio.h> + +/// Namespace for the http server and client apps +namespace http { + + class HttpCacheHandler; + + /// A cachecheck to cache http requests + /** + * \author M.Frank + * \version 1.0 + * \date 10.01.2021 + */ + class HttpCacheCheck { + protected: + boost::asio::deadline_timer timer; + http::HttpCacheHandler& handler; + int timeout { 100 }; + public: + /// Initializing constructor. Start timer + HttpCacheCheck(http::HttpCacheHandler& hdlr, boost::asio::io_service& io, int tmo); + /// Initializing constructor. Does not start timer. + HttpCacheCheck(http::HttpCacheHandler& hdlr, boost::asio::io_service& io); + /// Inhibit move construction + HttpCacheCheck(HttpCacheCheck&& copy) = delete; + /// Inhibit copy construction + HttpCacheCheck(const HttpCacheCheck& copy) = delete; + /// Inhibit move assignment + HttpCacheCheck& operator=(HttpCacheCheck&& copy) = delete; + /// Inhibit copy assignment + HttpCacheCheck& operator=(const HttpCacheCheck& copy) = delete; + /// Default destructor + virtual ~HttpCacheCheck(); + /// Rearm timer after single shot + void start(); + /// Rearm timer after single shot with new timeout + void start(int tmo); + /// Default cleanup handler: drop expired items + virtual void check(); + }; +} // End namespace http +#endif // HTTP_HTTP_HTTPCACHECHECK_H diff --git a/Online/HTTP/include/HTTP/HttpCacheHandler.h b/Online/HTTP/include/HTTP/HttpCacheHandler.h new file mode 100644 index 0000000000000000000000000000000000000000..a6c922d408fc64dd07cbaa4ad67bd406ea04d8f5 --- /dev/null +++ b/Online/HTTP/include/HTTP/HttpCacheHandler.h @@ -0,0 +1,104 @@ +//========================================================================== +// LHCb Online software suite +//-------------------------------------------------------------------------- +// Copyright (C) Organisation europeenne pour la Recherche nucleaire (CERN) +// All rights reserved. +// +// For the licensing terms see OnlineSys/LICENSE. +// +//-------------------------------------------------------------------------- +// +// Package : RPC-GUI +// +// Author : Markus Frank +//========================================================================== +#ifndef HTTP_HTTP_HTTPCACHEHANDLER_H +#define HTTP_HTTP_HTTPCACHEHANDLER_H + +/// Framework include caches +#include <HTTP/HttpReply.h> +#include <HTTP/Cache.h> + +/// C/C++ include files +#include <cstdint> + +/// Namespace for the http server and client apps +namespace http { + + class HttpRequest; + + /// A cachehandler to cache http requests + /** + * \author M.Frank + * \version 1.0 + * \date 10.01.2021 + */ + class HttpCacheHandler { + public: + /// Structure holding cache configuration properties + struct cache_params_t { + /// String identifier of this cache + std::string name { }; + /// Minimal content size for keeping entries in the cache + size_t minContentLength { 128 }; + /// Minimal content size for keeping entries in the cache + size_t maxContentLength { 100*MegaBYTE }; + /// Timeout in seconds for cleanup strategies + int timeout { std::numeric_limits<int>::max() }; + /// Flag to enable data cache + bool enable { false }; + /// Debug flag + bool debug { false }; + }; + + /// Structure steering the cache if enabled + cache_params_t cache; + + protected: + typedef Cache<HttpReply> cache_type; + std::unique_ptr<cache_type> reply_cache; + + /// Check cache for the existence of a given entry + std::pair<bool, http::Reply> check_cache(const std::string& path, + const std::string& encoding); + + /// Check cache for the existence of a given entry + std::pair<bool, http::Reply> check_cache(const std::string& path, + const HttpRequest& req); + + /// Add a new entry to the data cache + http::Reply add_cache(const std::string& path, + const std::string& encoding, + http::HttpReply&& reply); + + /// Auto enable or disable cache functionality + bool use_cache(std::mutex& handler_lock); + + public: + /// Default construction + HttpCacheHandler() = default; + /// Inhibit move construction + HttpCacheHandler(HttpCacheHandler&& copy) = delete; + /// Inhibit copy construction + HttpCacheHandler(const HttpCacheHandler& copy) = delete; + /// Inhibit move assignment + HttpCacheHandler& operator=(HttpCacheHandler&& copy) = delete; + /// Inhibit copy assignment + HttpCacheHandler& operator=(const HttpCacheHandler& copy) = delete; + /// Default destructor + virtual ~HttpCacheHandler(); + + /// Access number of entries in the cache + std::size_t size() const; + + /// Clean "old" entries from cache [Thread safe, locked] + std::size_t drop_expired(); + + /// Drop cache entries by key [Thread safe, locked] + std::size_t drop_key(cache_type::key_type key); + + /// Drop a cache entries by key [Thread safe, locked] + std::size_t drop_keys(const std::vector<cache_type::key_type>& key); + }; +} // End namespace http +#endif // HTTP_HTTP_HTTPCACHEHANDLER_H diff --git a/Online/HTTP/src/Cache.cpp b/Online/HTTP/src/Cache.cpp new file mode 100644 index 0000000000000000000000000000000000000000..2867565776d92a0ca11935611b960cd2276c31f6 --- /dev/null +++ b/Online/HTTP/src/Cache.cpp @@ -0,0 +1,38 @@ +//========================================================================== +// LHCb Online software suite +//-------------------------------------------------------------------------- +// Copyright (C) Organisation europeenne pour la Recherche nucleaire (CERN) +// All rights reserved. +// +// For the licensing terms see OnlineSys/LICENSE. +// +//-------------------------------------------------------------------------- +// +// Package : RPC +// +// Author : Markus Frank +//========================================================================== + +// Framework include files +#include <HTTP/Cache.h> +#include <HTTP/Cache.inl.h> + +// C/C++ include files +#include <cstring> + +template <> +void http::Cache<http::HttpReply>::_set(http::Cache<http::HttpReply>::entry_type* e, + http::Cache<http::HttpReply>::key_type key, + std::time_t tmo, + const std::string& path, + const std::string& encoding, + const http::HttpReply& reply) +{ + e->data = std::move(reply.clone()); + e->timeout = ::time(0) + tmo; + e->encoding = encoding; + e->path = path; + e->hash = key; +} + +template class http::Cache<http::HttpReply>; diff --git a/Online/HTTP/src/HttpCacheCheck.cpp b/Online/HTTP/src/HttpCacheCheck.cpp new file mode 100644 index 0000000000000000000000000000000000000000..37c0705253a113ede9a6bb9529416ecb344de64b --- /dev/null +++ b/Online/HTTP/src/HttpCacheCheck.cpp @@ -0,0 +1,61 @@ +//========================================================================== +// LHCb Online software suite +//-------------------------------------------------------------------------- +// Copyright (C) Organisation europeenne pour la Recherche nucleaire (CERN) +// All rights reserved. +// +// For the licensing terms see OnlineSys/LICENSE. +// +//-------------------------------------------------------------------------- +// +// Package : HTTP +// +// Author : Markus Frank +//========================================================================== + +// Framework include files +#include <HTTP/HttpCacheCheck.h> +#include <HTTP/HttpCacheHandler.h> + +// C/C++ include files +#include <boost/date_time/posix_time/posix_time.hpp> +#include <boost/bind.hpp> + +/// Initializing constructor. Start timer +http::HttpCacheCheck::HttpCacheCheck(HttpCacheHandler& hdlr, boost::asio::io_service& io, int tmo) + : timer(io), handler(hdlr), timeout(tmo) +{ + this->start(); +} + +/// Initializing constructor. Does not start timer. +http::HttpCacheCheck::HttpCacheCheck(HttpCacheHandler& hdlr, boost::asio::io_service& io) + : timer(io), handler(hdlr), timeout(10) +{ +} + +/// Default destructor +http::HttpCacheCheck::~HttpCacheCheck() { +} + +/// Rearm timer after single shot +void http::HttpCacheCheck::start() { + timer.expires_from_now(boost::posix_time::seconds(this->timeout)); + timer.async_wait(boost::bind(&HttpCacheCheck::check, this)); +} + +/// Rearm timer after single shot with new timeout +void http::HttpCacheCheck::start(int tmo) { + this->timeout = tmo; + this->start(); +} + +/// Default cleanup handler: drop expired items +void http::HttpCacheCheck::check() { + if ( this->handler.cache.debug ) { + std::cout << std::setw(14) << std::left << this->handler.cache.name+":" + << "Timer callback: Flush old cache entries...." << std::endl; + } + this->handler.drop_expired(); + this->start(); +} diff --git a/Online/HTTP/src/HttpCacheHandler.cpp b/Online/HTTP/src/HttpCacheHandler.cpp new file mode 100644 index 0000000000000000000000000000000000000000..e30ffcf305880efe05debf1e884231ed1e3e82b0 --- /dev/null +++ b/Online/HTTP/src/HttpCacheHandler.cpp @@ -0,0 +1,148 @@ +//========================================================================== +// LHCb Online software suite +//-------------------------------------------------------------------------- +// Copyright (C) Organisation europeenne pour la Recherche nucleaire (CERN) +// All rights reserved. +// +// For the licensing terms see OnlineSys/LICENSE. +// +//-------------------------------------------------------------------------- +// +// Package : HTTP +// +// Author : Markus Frank +//========================================================================== + +// Framework include files +#include <HTTP/HttpCacheHandler.h> +#include <HTTP/HttpRequest.h> + +// C/C++ include files +#include <cstring> +#include <iostream> + +/// Default destructor +http::HttpCacheHandler::~HttpCacheHandler() { + this->reply_cache.reset(); +} + +/// Access number of entries in the cache +std::size_t http::HttpCacheHandler::size() const { + if ( reply_cache.get() ) + return this->reply_cache->size(); + return 0; +} + +/// Auto enable or disable cache functionality +bool http::HttpCacheHandler::use_cache(std::mutex& handler_lock) { + std::lock_guard<std::mutex> lock(handler_lock); + if ( this->cache.enable ) { + if ( !this->reply_cache.get() ) + this->reply_cache = std::make_unique<Cache<HttpReply> >(); + return true; + } + this->reply_cache.reset(); + return false; +} + +/// Check cache for the existence of a given entry +std::pair<bool, http::Reply> +http::HttpCacheHandler::check_cache(const std::string& path, + const http::HttpRequest& req) +{ + std::size_t len = ::strlen("Accept-Encoding"); + for(const auto& h : req.headers) { + if ( ::strncasecmp(h.name.c_str(),"Accept-Encoding",len) == 0 ) { + return this->check_cache(path, h.value); + } + } + return std::make_pair(false,http::Reply()); +} + +/// Check cache for the existence of a given entry +std::pair<bool, http::Reply> +http::HttpCacheHandler::check_cache(const std::string& path, + const std::string& encoding) +{ + if ( this->reply_cache ) { + auto key = this->reply_cache->hash(path); + std::lock_guard<std::mutex> lock(this->reply_cache->lock); + const auto* e = this->reply_cache->find(key, encoding); + if ( e ) { + const auto& rep = e->data; + if ( this->cache.debug ) { + std::cout << "Cache-fetch: " << e->path + << " " << e->encoding + << " [" << rep.content.size() << " bytes]" + << std::endl; + } + return std::make_pair(true, rep.clone()); + } + } + return std::make_pair(false,http::Reply()); +} + +/// Add or update an entry in the data cache +http::Reply +http::HttpCacheHandler::add_cache(const std::string& path, + const std::string& encoding, + http::HttpReply&& reply) +{ + if ( this->reply_cache ) { + auto key = this->reply_cache->hash(path); + std::lock_guard<std::mutex> lock(this->reply_cache->lock); + if ( this->cache.debug ) { + std::cout << "Cache-insert: " << path + << " " << encoding + << " [" << reply.content.size() << " bytes]" + << std::endl; + } + this->reply_cache->insert(key, cache.timeout, path, encoding, reply); + } + return std::move(reply); +} + +/// Clean "old" entries from cache [Thread safe, locked] +std::size_t http::HttpCacheHandler::drop_expired() { + if ( this->reply_cache ) { + std::size_t count = this->reply_cache->drop_expired(); + if ( this->cache.debug ) { + std::cout << "Cache-clean: " << this->cache.name << " Dropped " << count + << " entries from cache." + << " Now: " << this->reply_cache->size() << " entries." + << std::endl; + } + return count; + } + return 0; +} + +/// Drop cache entries by key [Thread safe, locked] +std::size_t http::HttpCacheHandler::drop_key(cache_type::key_type key) { + if ( this->reply_cache ) { + std::size_t count = this->reply_cache->drop_key(key); + if ( this->cache.debug ) { + std::cout << "Cache-clean: " << this->cache.name << "Dropped " << count + << " entries from cache." + << " Now: " << this->reply_cache->size() << " entries." + << std::endl; + } + return count; + } + return 0; +} + +/// Drop a cache entries by key [Thread safe, locked] +std::size_t http::HttpCacheHandler::drop_keys(const std::vector<cache_type::key_type>& keys) { + if ( this->reply_cache ) { + std::size_t count = this->reply_cache->drop_keys(keys); + if ( this->cache.debug ) { + std::cout << "Cache-clean: " << this->cache.name << "Dropped " << count + << " entries from cache." + << " Now: " << this->reply_cache->size() << " entries." + << std::endl; + } + return count; + } + return 0; +}