#ifndef _ROUTER_HPP #define _ROUTER_HPP #include #include #include #include #include #include #include #include #include #include #include "database.hpp" #include "session.hpp" #include "utils.hpp" #include "config.hpp" namespace bserv { namespace beast = boost::beast; namespace http = beast::http; using request_type = http::request; using response_type = http::response; 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> transaction; } // 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( const std::vector&, request_type&, response_type&, Type&& val) { return static_cast(val); } template = 0), int> = 0> const std::string& get_parameter_data( const std::vector& url_params, request_type&, response_type&, placeholders::placeholder) { return url_params[N]; } std::shared_ptr get_parameter_data( const std::vector&, request_type& request, response_type& response, placeholders::placeholder<-1>) { std::string cookie_str{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 (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( const std::vector&, request_type& request, response_type&, placeholders::placeholder<-2>) { return request; } response_type& get_parameter_data( const std::vector&, request_type&, response_type& response, placeholders::placeholder<-3>) { return response; } boost::json::object get_parameter_data( const std::vector&, request_type& request, response_type&, placeholders::placeholder<-4>) { std::string target{request.target()}; auto&& [url, dict_params, list_params] = utils::parse_url(target); boost::ignore_unused(url); boost::json::object body; if (!request.body().empty()) { try { body = boost::json::parse(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; } std::shared_ptr get_parameter_data( const std::vector&, request_type&, response_type&, placeholders::placeholder<-5>) { return db_conn_mgr->get_or_block(); } template struct path_handler; template struct path_handler> { Ret invoke(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, 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(Ret (*pf)(Args ...), parameter_pack& params, const std::vector& url_params, request_type& request, response_type& response, 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(pf, params, url_params, request, response, get_parameter_data(url_params, request, response, 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_/\.\-]+))"} }; 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( const std::vector&, request_type&, response_type&) = 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( const std::vector& url_params, request_type& request, response_type& response) { return handler_.invoke( pf_, params_, url_params, request, response); } }; } // 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_; public: router(const std::initializer_list& paths) : paths_{paths} {} 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); } throw url_not_found_exception{}; } }; } // bserv #endif // _ROUTER_HPP