diff --git a/bserv/client.hpp b/bserv/client.hpp index f00cbde..31b2bd1 100644 --- a/bserv/client.hpp +++ b/bserv/client.hpp @@ -13,7 +13,6 @@ #include #include -#include "router.hpp" #include "logging.hpp" namespace bserv { @@ -24,6 +23,9 @@ namespace asio = boost::asio; namespace json = boost::json; using asio::ip::tcp; +using request_type = http::request; +using response_type = http::response; + class request_failed_exception : public std::exception { private: @@ -37,9 +39,9 @@ public: // sends one async request to a remote server template -class client_session +class http_client_session : public std::enable_shared_from_this< - client_session> { + http_client_session> { private: tcp::resolver resolver_; beast::tcp_stream stream_; @@ -54,7 +56,7 @@ private: request_failed_exception{what + ": " + ec.message()})); } public: - client_session( + http_client_session( asio::io_context& ioc, const http::request& req) : resolver_{asio::make_strand(ioc)}, @@ -65,15 +67,15 @@ public: resolver_.async_resolve( host, port, beast::bind_front_handler( - &client_session::on_resolve, - client_session::shared_from_this())); + &http_client_session::on_resolve, + http_client_session::shared_from_this())); return promise_.get_future(); } void on_resolve( beast::error_code ec, tcp::resolver::results_type results) { if (ec) { - failed(ec, "client_session::resolver resolve"); + failed(ec, "http_client_session::resolver resolve"); return; } // sets a timeout on the operation @@ -82,14 +84,14 @@ public: stream_.async_connect( results, beast::bind_front_handler( - &client_session::on_connect, - client_session::shared_from_this())); + &http_client_session::on_connect, + http_client_session::shared_from_this())); } void on_connect( beast::error_code ec, tcp::resolver::results_type::endpoint_type) { if (ec) { - failed(ec, "client_session::stream connect"); + failed(ec, "http_client_session::stream connect"); return; } // sets a timeout on the operation @@ -98,23 +100,23 @@ public: http::async_write( stream_, req_, beast::bind_front_handler( - &client_session::on_write, - client_session::shared_from_this())); + &http_client_session::on_write, + http_client_session::shared_from_this())); } void on_write( beast::error_code ec, std::size_t bytes_transferred) { boost::ignore_unused(bytes_transferred); if (ec) { - failed(ec, "client_session::stream write"); + failed(ec, "http_client_session::stream write"); return; } // receives the HTTP response http::async_read( stream_, buffer_, res_, beast::bind_front_handler( - &client_session::on_read, - client_session::shared_from_this())); + &http_client_session::on_read, + http_client_session::shared_from_this())); } static_assert(std::is_same>::value || std::is_same::value, @@ -124,7 +126,7 @@ public: std::size_t bytes_transferred) { boost::ignore_unused(bytes_transferred); if (ec) { - failed(ec, "client_session::stream read"); + failed(ec, "http_client_session::stream read"); return; } if constexpr (std::is_same>::value) { @@ -141,25 +143,41 @@ public: // `not_connected` happens sometimes so don't bother reporting it if (ec && ec != beast::errc::not_connected) { // reports the error to the log! - fail(ec, "client_session::stream::socket shutdown"); + fail(ec, "http_client_session::stream::socket shutdown"); return; } // if we get here then the connection is closed gracefully } }; -class client { +request_type get_request( + const std::string& host, + const std::string& target, + const http::verb& method, + const boost::json::object& obj) { + request_type req; + req.method(method); + req.target(target); + req.set(http::field::host, host); + req.set(http::field::user_agent, NAME); + req.set(http::field::content_type, "application/json"); + req.body() = boost::json::serialize(obj); + req.prepare_payload(); + return req; +} + +class http_client { private: asio::io_context& ioc_; public: - client(asio::io_context& ioc) + http_client(asio::io_context& ioc) : ioc_{ioc} {} std::future> request( const std::string& host, const std::string& port, const http::request& req) { return std::make_shared< - client_session> + http_client_session> >(ioc_, req)->send(host, port); } std::future request_for_object( @@ -167,30 +185,9 @@ public: const std::string& port, const http::request& req) { return std::make_shared< - client_session + http_client_session >(ioc_, req)->send(host, port); } -}; - -std::shared_ptr client_ptr; - -namespace request { - - request_type get_request( - const std::string& host, - const std::string& target, - const http::verb& method, - const boost::json::object& obj) { - request_type req; - req.method(method); - req.target(target); - req.set(http::field::host, host); - req.set(http::field::user_agent, NAME); - req.set(http::field::content_type, "application/json"); - req.body() = boost::json::serialize(obj); - req.prepare_payload(); - return req; - } std::future send( const std::string& host, @@ -199,9 +196,8 @@ namespace request { const http::verb& method, const boost::json::object& obj) { request_type req = get_request(host, target, method, obj); - return client_ptr->request(host, port, req); + return request(host, port, req); } - std::future send_for_object( const std::string& host, const std::string& port, @@ -209,9 +205,8 @@ namespace request { const http::verb& method, const boost::json::object& obj) { request_type req = get_request(host, target, method, obj); - return client_ptr->request_for_object(host, port, req); + return request_for_object(host, port, req); } - std::future get( const std::string& host, const std::string& port, @@ -219,7 +214,6 @@ namespace request { const boost::json::object& obj) { return send(host, port, target, http::verb::get, obj); } - std::future get_for_object( const std::string& host, const std::string& port, @@ -227,7 +221,6 @@ namespace request { const boost::json::object& obj) { return send_for_object(host, port, target, http::verb::get, obj); } - std::future put( const std::string& host, const std::string& port, @@ -235,7 +228,6 @@ namespace request { const boost::json::object& obj) { return send(host, port, target, http::verb::put, obj); } - std::future put_for_object( const std::string& host, const std::string& port, @@ -243,7 +235,6 @@ namespace request { const boost::json::object& obj) { return send_for_object(host, port, target, http::verb::put, obj); } - std::future post( const std::string& host, const std::string& port, @@ -251,7 +242,6 @@ namespace request { const boost::json::object& obj) { return send(host, port, target, http::verb::post, obj); } - std::future post_for_object( const std::string& host, const std::string& port, @@ -259,7 +249,6 @@ namespace request { const boost::json::object& obj) { return send_for_object(host, port, target, http::verb::post, obj); } - std::future delete_( const std::string& host, const std::string& port, @@ -267,7 +256,6 @@ namespace request { const boost::json::object& obj) { return send(host, port, target, http::verb::delete_, obj); } - std::future delete_for_object( const std::string& host, const std::string& port, @@ -275,8 +263,7 @@ namespace request { const boost::json::object& obj) { return send_for_object(host, port, target, http::verb::delete_, obj); } - -} // request +}; } // bserv diff --git a/bserv/database.hpp b/bserv/database.hpp index 5c4235b..8ecc678 100644 --- a/bserv/database.hpp +++ b/bserv/database.hpp @@ -88,8 +88,6 @@ db_connection::~db_connection() { mgr_.counter_lock_.unlock(); } -std::shared_ptr db_conn_mgr; - // ************************************************************************** class db_parameter { diff --git a/bserv/router.hpp b/bserv/router.hpp index b4d7156..9e5d832 100644 --- a/bserv/router.hpp +++ b/bserv/router.hpp @@ -14,6 +14,7 @@ #include +#include "client.hpp" #include "database.hpp" #include "session.hpp" #include "utils.hpp" @@ -24,8 +25,11 @@ namespace bserv { namespace beast = boost::beast; namespace http = beast::http; -using request_type = http::request; -using response_type = http::response; +struct server_resources { + std::shared_ptr session_mgr; + std::shared_ptr db_conn_mgr; + std::shared_ptr http_client_ptr; +}; namespace placeholders { @@ -55,7 +59,9 @@ constexpr placeholder<-3> response; // boost::json::object&& constexpr placeholder<-4> json_params; // std::shared_ptr -constexpr placeholder<-5> transaction; +constexpr placeholder<-5> db_connection_ptr; +// std::shared_ptr +constexpr placeholder<-6> http_client_ptr; } // placeholders @@ -116,6 +122,7 @@ struct get_parameter<0, Head, Tail...> { template Type&& get_parameter_data( + server_resources&, const std::vector&, request_type&, response_type&, Type&& val) { return static_cast(val); @@ -123,6 +130,7 @@ Type&& get_parameter_data( template = 0), int> = 0> const std::string& get_parameter_data( + server_resources&, const std::vector& url_params, request_type&, response_type&, placeholders::placeholder) { @@ -130,6 +138,7 @@ const std::string& get_parameter_data( } std::shared_ptr get_parameter_data( + server_resources& resources, const std::vector&, request_type& request, response_type& response, placeholders::placeholder<-1>) { @@ -142,13 +151,14 @@ std::shared_ptr get_parameter_data( session_id = cookie_dict[SESSION_NAME]; } std::shared_ptr session_ptr; - if (session_mgr->get_or_create(session_id, session_ptr)) { + if (resources.session_mgr->get_or_create(session_id, session_ptr)) { response.set(http::field::set_cookie, SESSION_NAME + "=" + session_id); } return session_ptr; } request_type& get_parameter_data( + server_resources&, const std::vector&, request_type& request, response_type&, placeholders::placeholder<-2>) { @@ -156,6 +166,7 @@ request_type& get_parameter_data( } response_type& get_parameter_data( + server_resources&, const std::vector&, request_type&, response_type& response, placeholders::placeholder<-3>) { @@ -163,6 +174,7 @@ response_type& get_parameter_data( } boost::json::object get_parameter_data( + server_resources&, const std::vector&, request_type& request, response_type&, placeholders::placeholder<-4>) { @@ -195,10 +207,19 @@ boost::json::object get_parameter_data( } std::shared_ptr get_parameter_data( + server_resources& resources, const std::vector&, request_type&, response_type&, placeholders::placeholder<-5>) { - return db_conn_mgr->get_or_block(); + return resources.db_conn_mgr->get_or_block(); +} + +std::shared_ptr get_parameter_data( + server_resources& resources, + const std::vector&, + request_type&, response_type&, + placeholders::placeholder<-6>) { + return resources.http_client_ptr; } template @@ -206,15 +227,16 @@ struct path_handler; template struct path_handler> { - Ret invoke(Ret (*pf)(Args ...), parameter_pack& params, + Ret invoke(server_resources& resources, + Ret (*pf)(Args ...), parameter_pack& params, const std::vector& url_params, request_type& request, response_type& response) { if constexpr (Idx == 0) return (*pf)(); else return static_cast, typename get_parameter::type>* - >(this)->invoke2(pf, params, url_params, request, response, - get_parameter_data(url_params, request, response, + >(this)->invoke2(resources, pf, params, url_params, request, response, + get_parameter_data(resources, url_params, request, response, get_parameter_value(params))); } }; @@ -228,7 +250,8 @@ struct path_handler = 0> - Ret invoke2(Ret (*pf)(Args ...), parameter_pack& params, + Ret invoke2(server_resources& resources, + Ret (*pf)(Args ...), parameter_pack& params, const std::vector& url_params, request_type& request, response_type& response, Head2&& head2, Tail2&& ...tail2) { @@ -238,8 +261,8 @@ struct path_handler, typename get_parameter::type, Head, Tail...>* - >(this)->invoke2(pf, params, url_params, request, response, - get_parameter_data(url_params, request, response, + >(this)->invoke2(resources, pf, params, url_params, request, response, + get_parameter_data(resources, url_params, request, response, get_parameter_value(params)), static_cast(head2), static_cast(tail2)...); } @@ -265,6 +288,7 @@ struct path_holder : std::enable_shared_from_this { const std::string&, std::vector&) const = 0; virtual std::optional invoke( + server_resources&, const std::vector&, request_type&, response_type&) = 0; }; @@ -295,9 +319,11 @@ public: return matched; } std::optional invoke( + server_resources& resources, const std::vector& url_params, request_type& request, response_type& response) { return handler_.invoke( + resources, pf_, params_, url_params, request, response); } @@ -335,15 +361,19 @@ class router { private: using path_holder_type = std::shared_ptr; std::vector paths_; + std::shared_ptr resources_; public: router(const std::initializer_list& paths) : paths_{paths} {} + void set_resources(std::shared_ptr resources) { + resources_ = resources; + } std::optional operator()( const std::string& url, request_type& request, response_type& response) { std::vector url_params; for (auto& ptr : paths_) { if (ptr->match(url, url_params)) - return ptr->invoke(url_params, request, response); + return ptr->invoke(*resources_, url_params, request, response); } throw url_not_found_exception{}; } diff --git a/bserv/server.hpp b/bserv/server.hpp index 365cace..d4a8d98 100644 --- a/bserv/server.hpp +++ b/bserv/server.hpp @@ -320,22 +320,32 @@ private: // io_context for all I/O asio::io_context ioc_; router routes_; + std::shared_ptr session_mgr_; + std::shared_ptr db_conn_mgr_; + std::shared_ptr http_client_ptr_; public: server(const server_config& config, router&& routes) - : ioc_{config.get_num_threads()}, routes_{routes} { + : ioc_{config.get_num_threads()}, + routes_{std::move(routes)} { init_logging(config); // database connection try { - db_conn_mgr = std::make_shared< + db_conn_mgr_ = std::make_shared< db_connection_manager>(config.get_db_conn_str(), config.get_num_db_conn()); } catch (const std::exception& e) { lgfatal << "db connection initialization failed: " << e.what() << std::endl; exit(EXIT_FAILURE); } - session_mgr = std::make_shared(); + session_mgr_ = std::make_shared(); + http_client_ptr_ = std::make_shared(ioc_); - client_ptr = std::make_shared(ioc_); + std::shared_ptr resources_ptr = std::make_shared(); + resources_ptr->session_mgr = session_mgr_; + resources_ptr->db_conn_mgr = db_conn_mgr_; + resources_ptr->http_client_ptr = http_client_ptr_; + + routes_.set_resources(resources_ptr); // creates and launches a listening port std::make_shared( diff --git a/bserv/session.hpp b/bserv/session.hpp index a150cad..e397a14 100644 --- a/bserv/session.hpp +++ b/bserv/session.hpp @@ -23,9 +23,9 @@ const std::string SESSION_NAME = "bsessionid"; // using session_type = std::map; using session_type = boost::json::object; -struct session_base - : std::enable_shared_from_this { - virtual ~session_base() = default; +struct session_manager_base + : std::enable_shared_from_this { + virtual ~session_manager_base() = default; // if `key` refers to an existing session, that session will be placed in // `session_ptr` and this function will return `false`. // otherwise, this function will create a new session, place the created @@ -39,9 +39,7 @@ struct session_base std::shared_ptr& session_ptr) = 0; }; -std::shared_ptr session_mgr; - -class memory_session : public session_base { +class memory_session_manager : public session_manager_base { private: using time_point = std::chrono::steady_clock::time_point; std::mt19937 rng_; @@ -58,7 +56,7 @@ private: std::set> queue_; mutable std::mutex lock_; public: - memory_session() + memory_session_manager() : rng_{utils::internal::get_rd_value()}, dist_{0, std::numeric_limits::max()} {} bool get_or_create( diff --git a/handlers.hpp b/handlers.hpp index 7fc10ed..9a21db3 100644 --- a/handlers.hpp +++ b/handlers.hpp @@ -209,10 +209,10 @@ boost::json::object user_logout( }; } -boost::json::object send_request() { +boost::json::object send_request(std::shared_ptr client_ptr) { // post for response: - // auto res = bserv::request::post( - // "localhost", "8081", "/test", {{"msg", "request"}} + // auto res = client_ptr->post( + // "localhost", "8080", "/echo", {{"msg", "request"}} // ).get(); // return {{"response", boost::json::parse(res.body())}}; // ------------------------------------------------------- @@ -221,10 +221,15 @@ boost::json::object send_request() { // ------------------------------------------------------- // post for json response (json value, rather than json // object, is returned): - auto obj = bserv::request::post_for_object( - "localhost", "8081", "/test", {{"msg", "request"}} + auto obj = client_ptr->post_for_object( + "localhost", "8080", "/echo", {{"msg", "request"}} ).get(); return {{"response", obj}}; } +boost::json::object echo( + boost::json::object&& params) { + return params; +} + #endif // _HANDLERS_HPP \ No newline at end of file diff --git a/main.cpp b/main.cpp index 8e26114..f649bab 100644 --- a/main.cpp +++ b/main.cpp @@ -108,18 +108,21 @@ int main(int argc, char* argv[]) { bserv::make_path("/register", &user_register, bserv::placeholders::request, bserv::placeholders::json_params, - bserv::placeholders::transaction), + bserv::placeholders::db_connection_ptr), bserv::make_path("/login", &user_login, bserv::placeholders::request, bserv::placeholders::json_params, - bserv::placeholders::transaction, + bserv::placeholders::db_connection_ptr, bserv::placeholders::session), bserv::make_path("/logout", &user_logout, bserv::placeholders::session), bserv::make_path("/find/", &find_user, - bserv::placeholders::transaction, + bserv::placeholders::db_connection_ptr, bserv::placeholders::_1), - bserv::make_path("/send", &send_request) + bserv::make_path("/send", &send_request, + bserv::placeholders::http_client_ptr), + bserv::make_path("/echo", &echo, + bserv::placeholders::json_params) }}; return EXIT_SUCCESS;