/* * Copyright (c) 2017-2023 zhllxt * * author : zhllxt * email : 37792738@qq.com * * Distributed under the Boost Software License, Version 1.0. (See accompanying * file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) */ #ifndef __ASIO2_HTTP_ROUTER_HPP__ #define __ASIO2_HTTP_ROUTER_HPP__ #if defined(_MSC_VER) && (_MSC_VER >= 1200) #pragma once #endif // defined(_MSC_VER) && (_MSC_VER >= 1200) #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include namespace asio2::detail { template class http_router_t; } #ifdef ASIO2_HEADER_ONLY namespace bho::beast::websocket #else namespace boost::beast::websocket #endif { namespace detail { struct listener_tag {}; } template class listener : public detail::listener_tag { template friend class asio2::detail::http_router_t; // Cannot access the protected member of bho::beast::websocket::listener::operator () template friend struct asio2::detail::function_traits; public: using self = listener; listener() = default; listener(listener&&) noexcept = default; listener(listener const&) = default; listener& operator=(listener&&) noexcept = default; listener& operator=(listener const&) = default; template inline listener& on_message(F&& f) { this->_bind(websocket::frame::message, std::forward(f)); return (*this); } template inline listener& on_ping(F&& f) { this->_bind(websocket::frame::ping, std::forward(f)); return (*this); } template inline listener& on_pong(F&& f) { this->_bind(websocket::frame::pong, std::forward(f)); return (*this); } template inline listener& on_open(F&& f) { this->_bind(websocket::frame::open, std::forward(f)); return (*this); } template inline listener& on_close(F&& f) { this->_bind(websocket::frame::close, std::forward(f)); return (*this); } template inline listener& on_message(F&& f, C* c) { auto mf = std::bind(std::forward(f), c, std::placeholders::_1, std::placeholders::_2); this->_bind(websocket::frame::message, std::move(mf)); return (*this); } template inline listener& on_ping(F&& f, C* c) { auto mf = std::bind(std::forward(f), c, std::placeholders::_1, std::placeholders::_2); this->_bind(websocket::frame::ping, std::move(mf)); return (*this); } template inline listener& on_pong(F&& f, C* c) { auto mf = std::bind(std::forward(f), c, std::placeholders::_1, std::placeholders::_2); this->_bind(websocket::frame::pong, std::move(mf)); return (*this); } template inline listener& on_open(F&& f, C* c) { auto mf = std::bind(std::forward(f), c, std::placeholders::_1, std::placeholders::_2); this->_bind(websocket::frame::open, std::move(mf)); return (*this); } template inline listener& on_close(F&& f, C* c) { auto mf = std::bind(std::forward(f), c, std::placeholders::_1, std::placeholders::_2); this->_bind(websocket::frame::close, std::move(mf)); return (*this); } template inline listener& on_message(F&& f, C& c) { return this->on_message(std::forward(f), std::addressof(c)); } template inline listener& on_ping(F&& f, C& c) { return this->on_ping(std::forward(f), std::addressof(c)); } template inline listener& on_pong(F&& f, C& c) { return this->on_pong(std::forward(f), std::addressof(c)); } template inline listener& on_open(F&& f, C& c) { return this->on_open(std::forward(f), std::addressof(c)); } template inline listener& on_close(F&& f, C& c) { return this->on_close(std::forward(f), std::addressof(c)); } template inline listener& on(std::string_view type, F&& f) { if (beast::iequals(type, "message")) this->_bind(websocket::frame::message, std::forward(f)); else if (beast::iequals(type, "ping" )) this->_bind(websocket::frame::ping , std::forward(f)); else if (beast::iequals(type, "pong" )) this->_bind(websocket::frame::pong , std::forward(f)); else if (beast::iequals(type, "open" )) this->_bind(websocket::frame::open , std::forward(f)); else if (beast::iequals(type, "close" )) this->_bind(websocket::frame::close , std::forward(f)); return (*this); } template inline listener& on(std::string_view type, F&& f, C* c) { auto mf = std::bind(std::forward(f), c, std::placeholders::_1, std::placeholders::_2); if (beast::iequals(type, "message")) this->_bind(websocket::frame::message, std::move(mf)); else if (beast::iequals(type, "ping" )) this->_bind(websocket::frame::ping , std::move(mf)); else if (beast::iequals(type, "pong" )) this->_bind(websocket::frame::pong , std::move(mf)); else if (beast::iequals(type, "open" )) this->_bind(websocket::frame::open , std::move(mf)); else if (beast::iequals(type, "close" )) this->_bind(websocket::frame::close , std::move(mf)); return (*this); } template inline listener& on(std::string_view type, F&& f, C& c) { return this->on(std::move(type), std::forward(f), std::addressof(c)); } public: // under gcc 8.2.0, if this "operator()" is protected, it compiled error : // is protected within this context : function_traits{}; inline void operator()(std::shared_ptr& caller, http::web_request& req, http::web_response&) { ASIO2_ASSERT(caller->is_websocket()); auto& f = cbs_[asio2::detail::to_underlying(req.ws_frame_type_)]; if (f) f(caller, req.ws_frame_data_); } protected: template inline void _bind(websocket::frame type, F&& f) { this->cbs_[asio2::detail::to_underlying(type)] = std::bind( &self::template _proxy, this, std::forward(f), std::placeholders::_1, std::placeholders::_2); } template inline void _proxy(F& f, std::shared_ptr& caller, std::string_view data) { asio2::detail::ignore_unused(data); if constexpr (asio2::detail::is_template_callable_v&, std::string_view>) { f(caller, data); } else { f(caller); } } protected: std::array&, std::string_view)>, asio2::detail::to_underlying(frame::close) + 1> cbs_; }; } namespace asio2::detail { ASIO2_CLASS_FORWARD_DECLARE_BASE; ASIO2_CLASS_FORWARD_DECLARE_TCP_BASE; ASIO2_CLASS_FORWARD_DECLARE_TCP_SERVER; ASIO2_CLASS_FORWARD_DECLARE_TCP_SESSION; template class http_router_t { friend caller_t; ASIO2_CLASS_FRIEND_DECLARE_BASE; ASIO2_CLASS_FRIEND_DECLARE_TCP_BASE; ASIO2_CLASS_FRIEND_DECLARE_TCP_SERVER; ASIO2_CLASS_FRIEND_DECLARE_TCP_SESSION; template struct has_member_before : std::false_type {}; template struct has_member_before>(). before((std::declval())...)), Args...> : std::true_type {}; template struct has_member_after : std::false_type {}; template struct has_member_after>(). after((std::declval())...)), Args...> : std::true_type {}; ///////////////////////////////////////////////////////////////////////////// // cinatra-master/include/cinatra/utils.hpp template struct has_type; template struct has_type> : std::disjunction...> {}; template< typename T> struct filter_helper { static constexpr auto func() { return std::tuple<>(); } template< class... Args > static constexpr auto func(T&&, Args&&...args) { return filter_helper::func(std::forward(args)...); } template< class... Args > static constexpr auto func(const T&, Args&&...args) { return filter_helper::func(std::forward(args)...); } template< class X, class... Args > static constexpr auto func(X&& x, Args&&...args) { return std::tuple_cat(std::make_tuple(std::forward(x)), filter_helper::func(std::forward(args)...)); } }; template inline auto filter_cache(Args&&... args) { return filter_helper::func(std::forward(args)...); } ///////////////////////////////////////////////////////////////////////////// public: using self = http_router_t; using opret = http::response*; using opfun = std::function&, http::web_request&, http::web_response&)>; /** * @brief constructor */ http_router_t() { this->not_found_router_ = std::make_shared( [](std::shared_ptr&, http::web_request& req, http::web_response& rep) mutable { std::string desc; desc.reserve(64); desc += "The resource for "; desc += req.method_string(); desc += " \""; desc += http::url_decode(req.target()); desc += "\" was not found"; rep.fill_page(http::status::not_found, std::move(desc), {}, req.version()); return std::addressof(rep.base()); }); #if defined(_DEBUG) || defined(DEBUG) // used to test ThreadSafetyAnalysis asio2::ignore_unused(this->http_cache_.empty()); asio2::ignore_unused(this->http_cache_.get_cache_count()); #endif } /** * @brief destructor */ ~http_router_t() = default; /** * @brief bind a function for http router * @param name - uri name in string format. * @param fun - Function object. * @param caop - A pointer or reference to a class object, and aop object list. * if fun is member function, the first caop param must the class object's pointer or reference. */ template typename std::enable_if_t>, self&> inline bind(std::string name, F&& fun, CAOP&&... caop) { asio2::trim_both(name); if (name == "*") name = "/*"; if (name.empty()) { ASIO2_ASSERT(false); return (*this); } if constexpr (sizeof...(M) == std::size_t(0)) { this->_bind( std::move(name), std::forward(fun), std::forward(caop)...); } else { this->_bind( std::move(name), std::forward(fun), std::forward(caop)...); } return (*this); } /** * @brief bind a function for websocket router * @param name - uri name in string format. * @param listener - callback listener. * @param aop - aop object list. */ template inline self& bind(std::string name, websocket::listener listener, AOP&&... aop) { asio2::trim_both(name); ASIO2_ASSERT(!name.empty()); if (!name.empty()) this->_bind(std::move(name), std::move(listener), std::forward(aop)...); return (*this); } /** * @brief set the 404 not found router function */ template inline self& bind_not_found(F&& f, C&&... obj) { this->_bind_not_found(std::forward(f), std::forward(obj)...); return (*this); } /** * @brief set the 404 not found router function */ template inline self& bind_404(F&& f, C&&... obj) { this->_bind_not_found(std::forward(f), std::forward(obj)...); return (*this); } public: /** * @brief set the root directory where we load the files. */ inline self& set_root_directory(const std::filesystem::path& path) { std::error_code ec{}; this->root_directory_ = std::filesystem::canonical(path, ec); assert(!ec); return (*this); } /** * @brief get the root directory where we load the files. */ inline const std::filesystem::path& get_root_directory() noexcept { return this->root_directory_; } /** * @brief set whether websocket is supported, default is true */ inline self& set_support_websocket(bool v) noexcept { this->support_websocket_ = v; return (*this); } /** * @brief set whether websocket is supported, default is true, same as set_support_websocket */ inline self& support_websocket(bool v) noexcept { return this->set_support_websocket(v); } /** * @brief get whether websocket is supported, default is true */ inline bool is_support_websocket() const noexcept { return this->support_websocket_; } protected: inline self& _router() noexcept { return (*this); } template inline decltype(auto) _make_uris(std::string name) { std::size_t index = 0; std::array uris; //while (!name.empty() && name.back() == '*') // name.erase(std::prev(name.end())); while (name.size() > static_cast(1) && name.back() == '/') name.erase(std::prev(name.end())); ASIO2_ASSERT(!name.empty()); if (!name.empty()) { ((uris[index++] = std::string{ this->_to_char(M) } +name), ...); } return uris; } template typename std::enable_if_t>, void> inline _bind(std::string name, F f, AOP&&... aop) { constexpr bool CacheFlag = has_type...>>::value; auto tp = filter_cache(std::forward(aop)...); using Tup = decltype(tp); #if defined(ASIO2_ENABLE_LOG) static_assert(!has_type::value); #endif std::shared_ptr op = std::make_shared( std::bind(&self::template _proxy, this, std::move(f), std::move(tp), std::placeholders::_1, std::placeholders::_2, std::placeholders::_3)); this->_bind_uris(name, std::move(op), this->_make_uris(name)); } template typename std::enable_if_t> && std::is_same_v::class_type>, void> inline _bind(std::string name, F f, C* c, AOP&&... aop) { constexpr bool CacheFlag = has_type...>>::value; auto tp = filter_cache(std::forward(aop)...); using Tup = decltype(tp); #if defined(ASIO2_ENABLE_LOG) static_assert(!has_type::value); #endif std::shared_ptr op = std::make_shared( std::bind(&self::template _proxy, this, std::move(f), c, std::move(tp), std::placeholders::_1, std::placeholders::_2, std::placeholders::_3)); this->_bind_uris(name, std::move(op), this->_make_uris(name)); } template typename std::enable_if_t> && std::is_same_v::class_type>, void> inline _bind(std::string name, F f, C& c, AOP&&... aop) { this->_bind(std::move(name), std::move(f), std::addressof(c), std::forward(aop)...); } template inline void _bind(std::string name, websocket::listener listener, AOP&&... aop) { auto tp = filter_cache(std::forward(aop)...); using Tup = decltype(tp); #if defined(ASIO2_ENABLE_LOG) static_assert(!has_type::value); #endif std::shared_ptr op = std::make_shared( std::bind(&self::template _proxy, this, std::move(listener), std::move(tp), std::placeholders::_1, std::placeholders::_2, std::placeholders::_3)); // https://datatracker.ietf.org/doc/rfc6455/ // Once a connection to the server has been established (including a // connection via a proxy or over a TLS-encrypted tunnel), the client // MUST send an opening handshake to the server. The handshake consists // of an HTTP Upgrade request, along with a list of required and // optional header fields. The requirements for this handshake are as // follows. // 2. The method of the request MUST be GET, and the HTTP version MUST // be at least 1.1. this->_bind_uris(name, std::move(op), std::array{std::string{ "Z" } +name}); } template inline void _bind_uris(std::string& name, std::shared_ptr op, URIS uris) { for (auto& uri : uris) { if (uri.empty()) continue; if (name.back() == '*') { ASIO2_ASSERT(this->wildcard_routers_.find(uri) == this->wildcard_routers_.end()); this->wildcard_routers_[std::move(uri)] = op; } else { ASIO2_ASSERT(this->strictly_routers_.find(uri) == this->strictly_routers_.end()); this->strictly_routers_[std::move(uri)] = op; } } } //template //inline void _bind(std::string name, R(C::*memfun)(Ps...), C* c, AOP&&... aop) //{ //} template typename std::enable_if_t inline _proxy(F& f, Tup& aops, std::shared_ptr& caller, http::web_request& req, http::web_response& rep) { using fun_traits_type = function_traits; using arg0_type = typename std::remove_cv_t::type>>; if (!_call_aop_before(aops, caller, req, rep)) return std::addressof(rep.base()); if constexpr (std::is_same_v, arg0_type>) { f(caller, req, rep); } else { f(req, rep); } _call_aop_after(aops, caller, req, rep); return std::addressof(rep.base()); } template typename std::enable_if_t inline _proxy(F& f, C* c, Tup& aops, std::shared_ptr& caller, http::web_request& req, http::web_response& rep) { using fun_traits_type = function_traits; using arg0_type = typename std::remove_cv_t::type>>; if (!_call_aop_before(aops, caller, req, rep)) return std::addressof(rep.base()); if constexpr (std::is_same_v, arg0_type>) { if (c) (c->*f)(caller, req, rep); } else { if (c) (c->*f)(req, rep); } _call_aop_after(aops, caller, req, rep); return std::addressof(rep.base()); } template inline opret _proxy(websocket::listener& listener, Tup& aops, std::shared_ptr& caller, http::web_request& req, http::web_response& rep) { ASIO2_ASSERT(req.ws_frame_type_ != websocket::frame::unknown); if (req.ws_frame_type_ == websocket::frame::open) { if (!_call_aop_before(aops, caller, req, rep)) return nullptr; } if (req.ws_frame_type_ != websocket::frame::unknown) { listener(caller, req, rep); } if (req.ws_frame_type_ == websocket::frame::open) { if (!_call_aop_after(aops, caller, req, rep)) return nullptr; } return std::addressof(rep.base()); } template typename std::enable_if_t inline _proxy(F& f, Tup& aops, std::shared_ptr& caller, http::web_request& req, http::web_response& rep) { using fun_traits_type = function_traits; using arg0_type = typename std::remove_cv_t::type>>; using pcache_node = decltype(this->http_cache_.find("")); if (http::is_cache_enabled(req.base())) { pcache_node pcn = this->http_cache_.find(req.target()); if (!pcn) { if (!_call_aop_before(aops, caller, req, rep)) return std::addressof(rep.base()); if constexpr (std::is_same_v, arg0_type>) { f(caller, req, rep); } else { f(req, rep); } if (_call_aop_after(aops, caller, req, rep) && rep.result() == http::status::ok) { http::web_response::super msg{ std::move(rep.base()) }; if (msg.body().to_text()) { this->http_cache_.shrink_to_fit(); pcn = this->http_cache_.emplace(req.target(), std::move(msg)); return std::addressof(pcn->msg); } else { rep.base() = std::move(msg); } } return std::addressof(rep.base()); } else { if (!_call_aop_before(aops, caller, req, rep)) return std::addressof(rep.base()); if (!_call_aop_after(aops, caller, req, rep)) return std::addressof(rep.base()); ASIO2_ASSERT(!pcn->msg.body().is_file()); pcn->update_alive_time(); return std::addressof(pcn->msg); } } else { if (!_call_aop_before(aops, caller, req, rep)) return std::addressof(rep.base()); if constexpr (std::is_same_v, arg0_type>) { f(caller, req, rep); } else { f(req, rep); } _call_aop_after(aops, caller, req, rep); return std::addressof(rep.base()); } } template typename std::enable_if_t inline _proxy(F& f, C* c, Tup& aops, std::shared_ptr& caller, http::web_request& req, http::web_response& rep) { using fun_traits_type = function_traits; using arg0_type = typename std::remove_cv_t::type>>; using pcache_node = decltype(this->http_cache_.find("")); if (http::is_cache_enabled(req.base())) { pcache_node pcn = this->http_cache_.find(req.target()); if (!pcn) { if (!_call_aop_before(aops, caller, req, rep)) return std::addressof(rep.base()); if constexpr (std::is_same_v, arg0_type>) { if (c) (c->*f)(caller, req, rep); } else { if (c) (c->*f)(req, rep); } if (_call_aop_after(aops, caller, req, rep) && rep.result() == http::status::ok) { http::web_response::super msg{ std::move(rep.base()) }; if (msg.body().to_text()) { this->http_cache_.shrink_to_fit(); pcn = this->http_cache_.emplace(req.target(), std::move(msg)); return std::addressof(pcn->msg); } else { rep.base() = std::move(msg); } } return std::addressof(rep.base()); } else { if (!_call_aop_before(aops, caller, req, rep)) return std::addressof(rep.base()); if (!_call_aop_after(aops, caller, req, rep)) return std::addressof(rep.base()); ASIO2_ASSERT(!pcn->msg.body().is_file()); pcn->update_alive_time(); return std::addressof(pcn->msg); } } else { if (!_call_aop_before(aops, caller, req, rep)) return std::addressof(rep.base()); if constexpr (std::is_same_v, arg0_type>) { if (c) (c->*f)(caller, req, rep); } else { if (c) (c->*f)(req, rep); } _call_aop_after(aops, caller, req, rep); return std::addressof(rep.base()); } } template inline void _bind_not_found(F f) { this->not_found_router_ = std::make_shared( std::bind(&self::template _not_found_proxy, this, std::move(f), std::placeholders::_1, std::placeholders::_2, std::placeholders::_3)); } template inline void _bind_not_found(F f, C* c) { this->not_found_router_ = std::make_shared( std::bind(&self::template _not_found_proxy, this, std::move(f), c, std::placeholders::_1, std::placeholders::_2, std::placeholders::_3)); } template inline void _bind_not_found(F f, C& c) { this->_bind_not_found(std::move(f), std::addressof(c)); } template inline opret _not_found_proxy(F& f, std::shared_ptr& caller, http::web_request& req, http::web_response& rep) { asio2::detail::ignore_unused(caller); if constexpr (detail::is_template_callable_v&, http::web_request&, http::web_response&>) { f(caller, req, rep); } else { f(req, rep); } return std::addressof(rep.base()); } template inline opret _not_found_proxy(F& f, C* c, std::shared_ptr& caller, http::web_request& req, http::web_response& rep) { asio2::detail::ignore_unused(caller); if constexpr (detail::is_template_callable_v&, http::web_request&, http::web_response&>) { if (c) (c->*f)(caller, req, rep); } else { if (c) (c->*f)(req, rep); } return std::addressof(rep.base()); } template inline void _for_each_tuple(std::tuple& t, const F& f, std::index_sequence) { (f(std::get(t)), ...); } template inline bool _do_call_aop_before( Tup& aops, std::shared_ptr& caller, http::web_request& req, http::web_response& rep) { asio2::detail::ignore_unused(aops, caller, req, rep); asio2::detail::get_current_object>() = caller; bool continued = true; _for_each_tuple(aops, [&continued, &caller, &req, &rep](auto& aop) { asio2::detail::ignore_unused(caller, req, rep); if (!continued) return; if constexpr (has_member_before&, http::web_request&, http::web_response&>::value) { continued = aop.before(caller, req, rep); } else if constexpr (has_member_before::value) { continued = aop.before(req, rep); } else { std::ignore = true; } }, std::make_index_sequence>{}); return continued; } template inline bool _call_aop_before( Tup& aops, std::shared_ptr& caller, http::web_request& req, http::web_response& rep) { asio2::detail::ignore_unused(aops, caller, req, rep); if constexpr (!std::tuple_size_v) { return true; } else { return this->_do_call_aop_before(aops, caller, req, rep); } } template inline bool _do_call_aop_after( Tup& aops, std::shared_ptr& caller, http::web_request& req, http::web_response& rep) { asio2::detail::ignore_unused(aops, caller, req, rep); asio2::detail::get_current_object>() = caller; bool continued = true; _for_each_tuple(aops, [&continued, &caller, &req, &rep](auto& aop) { asio2::detail::ignore_unused(caller, req, rep); if (!continued) return; if constexpr (has_member_after&, http::web_request&, http::web_response&>::value) { continued = aop.after(caller, req, rep); } else if constexpr (has_member_after::value) { continued = aop.after(req, rep); } else { std::ignore = true; } }, std::make_index_sequence>{}); return continued; } template inline bool _call_aop_after( Tup& aops, std::shared_ptr& caller, http::web_request& req, http::web_response& rep) { asio2::detail::ignore_unused(aops, caller, req, rep); if constexpr (!std::tuple_size_v) { return true; } else { if (rep.defer_guard_) { rep.defer_guard_->aop_after_cb_ = std::make_unique>( [this, &aops, caller, &req, &rep]() mutable { caller_t* p = caller.get(); asio::dispatch(p->io_->context(), make_allocator(p->wallocator(), [this, &aops, caller = std::move(caller), &req, &rep]() mutable { this->_do_call_aop_after(aops, caller, req, rep); })); }); return true; } else { return this->_do_call_aop_after(aops, caller, req, rep); } } } inline std::string _make_uri(std::string_view root, std::string_view path) { std::string uri; if (http::has_undecode_char(path, 1)) { std::string decoded = http::url_decode(path); path = decoded; while (path.size() > static_cast(1) && path.back() == '/') { path.remove_suffix(1); } uri.reserve(root.size() + path.size()); uri += root; uri += path; } else { while (path.size() > static_cast(1) && path.back() == '/') { path.remove_suffix(1); } uri.reserve(root.size() + path.size()); uri += root; uri += path; } return uri; } template inline std::shared_ptr& _find(http::web_request& req, http::web_response& rep) { asio2::detail::ignore_unused(rep); std::string uri; if constexpr (IsHttp) { uri = this->_make_uri(this->_to_char(req.method()), req.path()); } else { uri = this->_make_uri(std::string_view{ "Z" }, req.path()); } { auto it = this->strictly_routers_.find(uri); if (it != this->strictly_routers_.end()) { return (it->second); } } // Find the best match url from tail to head if (!wildcard_routers_.empty()) { for (auto it = this->wildcard_routers_.rbegin(); it != this->wildcard_routers_.rend(); ++it) { auto& k = it->first; ASIO2_ASSERT(k.size() >= std::size_t(3)); if (!uri.empty() && !k.empty() && uri.front() == k.front() && uri.size() >= (k.size() - 2) && uri[k.size() - 3] == k[k.size() - 3] && http::url_match(k, uri)) { return (it->second); } } } return this->dummy_router_; } inline opret _route(std::shared_ptr& caller, http::web_request& req, http::web_response& rep) { if (caller->websocket_router_) { return (*(caller->websocket_router_))(caller, req, rep); } std::shared_ptr& router_ptr = this->template _find(req, rep); if (router_ptr) { return (*router_ptr)(caller, req, rep); } if (this->not_found_router_ && (*(this->not_found_router_))) (*(this->not_found_router_))(caller, req, rep); return std::addressof(rep.base()); } inline constexpr std::string_view _to_char(http::verb method) noexcept { using namespace std::literals; // Use a special name to avoid conflicts with global variable names constexpr std::string_view _hrmchars = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"sv; return _hrmchars.substr(detail::to_underlying(method), 1); } protected: std::filesystem::path root_directory_ = std::filesystem::current_path(); bool support_websocket_ = true; std::unordered_map> strictly_routers_; std:: map> wildcard_routers_; std::shared_ptr not_found_router_; inline static std::shared_ptr dummy_router_; detail::http_cache_t http_cache_; }; } #include #endif // !__ASIO2_HTTP_ROUTER_HPP__