#ifndef _ROUTER_HPP #define _ROUTER_HPP #include #include #include #include #include #include #include #include #include #include #include #include #include "client.hpp" #include "database.hpp" #include "session.hpp" #include "utils.hpp" #include "config.hpp" #include "websocket.hpp" namespace bserv { namespace beast = boost::beast; namespace http = beast::http; struct server_resources { std::shared_ptr session_mgr; std::shared_ptr db_conn_mgr; }; struct request_resources { server_resources& resources; asio::io_context& ioc; asio::yield_context& yield; std::shared_ptr ws_session; const std::vector& url_params; request_type& request; response_type& response; std::shared_ptr session_ptr; std::shared_ptr db_connection_ptr; std::shared_ptr http_client_ptr; std::shared_ptr websocket_server_ptr; }; namespace placeholders { template struct placeholder {}; #define make_place_holder(x) constexpr placeholder _##x make_place_holder(1); make_place_holder(2); make_place_holder(3); make_place_holder(4); make_place_holder(5); make_place_holder(6); make_place_holder(7); make_place_holder(8); make_place_holder(9); #undef make_place_holder // std::shared_ptr constexpr placeholder<-1> session; // bserv::request_type& constexpr placeholder<-2> request; // bserv::response_type& constexpr placeholder<-3> response; // boost::json::object&& constexpr placeholder<-4> json_params; // std::shared_ptr constexpr placeholder<-5> db_connection_ptr; // std::shared_ptr constexpr placeholder<-6> http_client_ptr; // std::shared_ptr constexpr placeholder<-7> websocket_server_ptr; } // placeholders class bad_request_exception : public std::exception { public: bad_request_exception() = default; const char* what() const noexcept { return "bad request"; } }; namespace router_internal { template struct parameter_pack; template <> struct parameter_pack<> {}; template struct parameter_pack : parameter_pack { Head head_; template parameter_pack(Head2&& head, Tail2&& ...tail) : parameter_pack{static_cast(tail)...}, head_{static_cast(head)} {} }; template struct get_parameter_pack; template struct get_parameter_pack : get_parameter_pack {}; template struct get_parameter_pack<0, Head, Tail...> { using type = parameter_pack; }; template decltype(auto) get_parameter_value(parameter_pack& params) { return (static_cast< typename get_parameter_pack::type& >(params).head_); } template struct get_parameter; template struct get_parameter : get_parameter {}; template struct get_parameter<0, Head, Tail...> { using type = Head; }; template Type&& get_parameter_data( request_resources&, Type&& val) { return static_cast(val); } template = 0), int> = 0> const std::string& get_parameter_data( request_resources& resources, placeholders::placeholder) { return resources.url_params[N]; } inline std::shared_ptr get_parameter_data( request_resources& resources, placeholders::placeholder<-1>) { if (resources.session_ptr != nullptr) return resources.session_ptr; std::string cookie_str{resources.request[http::field::cookie]}; auto&& [cookie_dict, cookie_list] = utils::parse_params(cookie_str, 0, ';'); boost::ignore_unused(cookie_list); std::string session_id; if (cookie_dict.count(SESSION_NAME) != 0) { session_id = cookie_dict[SESSION_NAME]; } std::shared_ptr session_ptr; if (resources.resources.session_mgr->get_or_create(session_id, session_ptr)) { resources.response.set(http::field::set_cookie, SESSION_NAME + "=" + session_id); } resources.session_ptr = session_ptr; return session_ptr; } inline request_type& get_parameter_data( request_resources& resources, placeholders::placeholder<-2>) { return resources.request; } inline response_type& get_parameter_data( request_resources& resources, placeholders::placeholder<-3>) { return resources.response; } inline boost::json::object get_parameter_data( request_resources& resources, placeholders::placeholder<-4>) { std::string target{resources.request.target()}; auto&& [url, dict_params, list_params] = utils::parse_url(target); boost::ignore_unused(url); boost::json::object body; if (!resources.request.body().empty()) { try { body = boost::json::parse(resources.request.body()).as_object(); } catch (const std::exception& e) { throw bad_request_exception{}; } } for (auto& [k, v] : dict_params) { if (!body.contains(k)) { body[k] = v; } } for (auto& [k, vs] : list_params) { if (!body.contains(k)) { boost::json::array a; for (auto& v : vs) { a.push_back(boost::json::string{v}); } body[k] = a; } } return body; } inline std::shared_ptr get_parameter_data( request_resources& resources, placeholders::placeholder<-5>) { if (resources.db_connection_ptr == nullptr) resources.db_connection_ptr = resources.resources.db_conn_mgr->get_or_block(); return resources.db_connection_ptr; } inline std::shared_ptr get_parameter_data( request_resources& resources, placeholders::placeholder<-6>) { if (resources.http_client_ptr == nullptr) resources.http_client_ptr = std::make_shared(resources.ioc, resources.yield); return resources.http_client_ptr; } inline std::shared_ptr get_parameter_data( request_resources& resources, placeholders::placeholder<-7>) { if (resources.websocket_server_ptr == nullptr) resources.websocket_server_ptr = std::make_shared(*resources.ws_session, resources.yield); return resources.websocket_server_ptr; } template struct path_handler; template struct path_handler> { Ret invoke(request_resources& resources, Ret (*pf)(Args ...), parameter_pack& params) { if constexpr (Idx == 0) return (*pf)(); else return static_cast, typename get_parameter::type>* >(this)->invoke2(resources, pf, params, get_parameter_data(resources, get_parameter_value(params))); } }; template struct path_handler, Head, Tail...> : path_handler, Tail...> { template < typename Head2, typename ...Tail2, std::enable_if_t = 0> Ret invoke2(request_resources& resources, Ret (*pf)(Args ...), parameter_pack& params, Head2&& head2, Tail2&& ...tail2) { if constexpr (Idx == 0) return (*pf)(static_cast(head2), static_cast(tail2)...); else return static_cast, typename get_parameter::type, Head, Tail...>* >(this)->invoke2(resources, pf, params, get_parameter_data(resources, get_parameter_value(params)), static_cast(head2), static_cast(tail2)...); } }; const std::vector> url_regex_mapping{ {std::regex{""}, "([0-9]+)"}, {std::regex{""}, R"(([A-Za-z0-9_\.\-]+))"}, {std::regex{""}, R"(([A-Za-z0-9_/\.\-]+))"} }; inline std::string get_re_url(const std::string& url) { std::string re_url = url; for (auto& [r, s] : url_regex_mapping) re_url = std::regex_replace(re_url, r, s); return re_url; } struct path_holder : std::enable_shared_from_this { path_holder() = default; virtual ~path_holder() = default; virtual bool match( const std::string&, std::vector&) const = 0; virtual std::optional invoke( request_resources&) = 0; }; template class path; template class path> : public path_holder { private: std::regex re_; Ret (*pf_)(Args ...); parameter_pack params_; path_handler<0, Ret (*)(Args ...), parameter_pack, Params...> handler_; public: path(const std::string& url, Ret (*pf)(Args ...), Params&& ...params) : re_{get_re_url(url)}, pf_{pf}, params_{static_cast(params)...} {} bool match(const std::string& url, std::vector& result) const { std::smatch r; bool matched = std::regex_match(url, r, re_); if (matched) { result.clear(); for (auto & sub : r) result.push_back(sub.str()); } return matched; } std::optional invoke( request_resources& resources) { return handler_.invoke( resources, pf_, params_); } }; } // router_internal template std::shared_ptr>> make_path( const std::string& url, Ret (*pf)(Args ...), Params&& ...params) { return std::make_shared< router_internal::path> >(url, pf, static_cast(params)...); } template std::shared_ptr>> make_path( const char* url, Ret (*pf)(Args ...), Params&& ...params) { return std::make_shared< router_internal::path> >(url, pf, static_cast(params)...); } class url_not_found_exception : public std::exception { public: url_not_found_exception() = default; const char* what() const noexcept { return "url not found"; } }; 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()( asio::io_context& ioc, asio::yield_context& yield, std::shared_ptr ws_session, const std::string& url, request_type& request, response_type& response) { std::vector url_params; for (auto& ptr : paths_) { if (ptr->match(url, url_params)) { request_resources resources{ *resources_, ioc, yield, ws_session, url_params, request, response, nullptr, nullptr, nullptr, nullptr }; return ptr->invoke(resources); } } throw url_not_found_exception{}; } }; } // bserv #endif // _ROUTER_HPP