| #pragma once |
| |
| #include <cstdint> |
| #include <limits> |
| #include <memory> |
| #include <tuple> |
| #include <utility> |
| #include <vector> |
| #include <boost/lexical_cast.hpp> |
| |
| #include "boost/container/flat_map.hpp" |
| |
| #include "crow/common.h" |
| #include "crow/http_request.h" |
| #include "crow/http_response.h" |
| #include "crow/logging.h" |
| #include "crow/utility.h" |
| #include "crow/websocket.h" |
| |
| namespace crow { |
| class BaseRule { |
| public: |
| BaseRule(std::string rule) : rule_(std::move(rule)) {} |
| |
| virtual ~BaseRule() {} |
| |
| virtual void validate() = 0; |
| std::unique_ptr<BaseRule> upgrade() { |
| if (rule_to_upgrade_) return std::move(rule_to_upgrade_); |
| return {}; |
| } |
| |
| virtual void handle(const request&, response&, const routing_params&) = 0; |
| virtual void handle_upgrade(const request&, response& res, SocketAdaptor&&) { |
| res = response(404); |
| res.end(); |
| } |
| #ifdef CROW_ENABLE_SSL |
| virtual void handle_upgrade(const request&, response& res, SSLAdaptor&&) { |
| res = response(404); |
| res.end(); |
| } |
| #endif |
| |
| uint32_t get_methods() { return methods_; } |
| |
| protected: |
| uint32_t methods_{1 << (int)HTTPMethod::Get}; |
| |
| std::string rule_; |
| std::string name_; |
| |
| std::unique_ptr<BaseRule> rule_to_upgrade_; |
| |
| friend class Router; |
| template <typename T> |
| friend struct RuleParameterTraits; |
| }; |
| |
| namespace detail { |
| namespace routing_handler_call_helper { |
| template <typename T, int Pos> |
| struct call_pair { |
| using type = T; |
| static const int pos = Pos; |
| }; |
| |
| template <typename H1> |
| struct call_params { |
| H1& handler; |
| const routing_params& params; |
| const request& req; |
| response& res; |
| }; |
| |
| template <typename F, int NInt, int NUint, int NDouble, int NString, |
| typename S1, typename S2> |
| struct call {}; |
| |
| template <typename F, int NInt, int NUint, int NDouble, int NString, |
| typename... Args1, typename... Args2> |
| struct call<F, NInt, NUint, NDouble, NString, black_magic::S<int64_t, Args1...>, |
| black_magic::S<Args2...>> { |
| void operator()(F cparams) { |
| using pushed = typename black_magic::S<Args2...>::template push_back< |
| call_pair<int64_t, NInt>>; |
| call<F, NInt + 1, NUint, NDouble, NString, black_magic::S<Args1...>, |
| pushed>()(cparams); |
| } |
| }; |
| |
| template <typename F, int NInt, int NUint, int NDouble, int NString, |
| typename... Args1, typename... Args2> |
| struct call<F, NInt, NUint, NDouble, NString, |
| black_magic::S<uint64_t, Args1...>, black_magic::S<Args2...>> { |
| void operator()(F cparams) { |
| using pushed = typename black_magic::S<Args2...>::template push_back< |
| call_pair<uint64_t, NUint>>; |
| call<F, NInt, NUint + 1, NDouble, NString, black_magic::S<Args1...>, |
| pushed>()(cparams); |
| } |
| }; |
| |
| template <typename F, int NInt, int NUint, int NDouble, int NString, |
| typename... Args1, typename... Args2> |
| struct call<F, NInt, NUint, NDouble, NString, black_magic::S<double, Args1...>, |
| black_magic::S<Args2...>> { |
| void operator()(F cparams) { |
| using pushed = typename black_magic::S<Args2...>::template push_back< |
| call_pair<double, NDouble>>; |
| call<F, NInt, NUint, NDouble + 1, NString, black_magic::S<Args1...>, |
| pushed>()(cparams); |
| } |
| }; |
| |
| template <typename F, int NInt, int NUint, int NDouble, int NString, |
| typename... Args1, typename... Args2> |
| struct call<F, NInt, NUint, NDouble, NString, |
| black_magic::S<std::string, Args1...>, black_magic::S<Args2...>> { |
| void operator()(F cparams) { |
| using pushed = typename black_magic::S<Args2...>::template push_back< |
| call_pair<std::string, NString>>; |
| call<F, NInt, NUint, NDouble, NString + 1, black_magic::S<Args1...>, |
| pushed>()(cparams); |
| } |
| }; |
| |
| template <typename F, int NInt, int NUint, int NDouble, int NString, |
| typename... Args1> |
| struct call<F, NInt, NUint, NDouble, NString, black_magic::S<>, |
| black_magic::S<Args1...>> { |
| void operator()(F cparams) { |
| cparams.handler( |
| cparams.req, cparams.res, |
| cparams.params.template get<typename Args1::type>(Args1::pos)...); |
| } |
| }; |
| |
| template <typename Func, typename... ArgsWrapped> |
| struct Wrapped { |
| template <typename... Args> |
| void set( |
| Func f, |
| typename std::enable_if< |
| !std::is_same< |
| typename std::tuple_element<0, std::tuple<Args..., void>>::type, |
| const request&>::value, |
| int>::type = 0) { |
| handler_ = ( |
| #ifdef CROW_CAN_USE_CPP14 |
| [f = std::move(f)] |
| #else |
| [f] |
| #endif |
| (const request&, response& res, Args... args) { |
| res = response(f(args...)); |
| res.end(); |
| }); |
| } |
| |
| template <typename Req, typename... Args> |
| struct req_handler_wrapper { |
| req_handler_wrapper(Func f) : f(std::move(f)) {} |
| |
| void operator()(const request& req, response& res, Args... args) { |
| res = response(f(req, args...)); |
| res.end(); |
| } |
| |
| Func f; |
| }; |
| |
| template <typename... Args> |
| void set( |
| Func f, |
| typename std::enable_if< |
| std::is_same< |
| typename std::tuple_element<0, std::tuple<Args..., void>>::type, |
| const request&>::value && |
| !std::is_same<typename std::tuple_element< |
| 1, std::tuple<Args..., void, void>>::type, |
| response&>::value, |
| int>::type = 0) { |
| handler_ = req_handler_wrapper<Args...>(std::move(f)); |
| /*handler_ = ( |
| [f = std::move(f)] |
| (const request& req, response& res, Args... args){ |
| res = response(f(req, args...)); |
| res.end(); |
| });*/ |
| } |
| |
| template <typename... Args> |
| void set( |
| Func f, |
| typename std::enable_if< |
| std::is_same< |
| typename std::tuple_element<0, std::tuple<Args..., void>>::type, |
| const request&>::value && |
| std::is_same<typename std::tuple_element< |
| 1, std::tuple<Args..., void, void>>::type, |
| response&>::value, |
| int>::type = 0) { |
| handler_ = std::move(f); |
| } |
| |
| template <typename... Args> |
| struct handler_type_helper { |
| using type = |
| std::function<void(const crow::request&, crow::response&, Args...)>; |
| using args_type = black_magic::S<typename black_magic::promote_t<Args>...>; |
| }; |
| |
| template <typename... Args> |
| struct handler_type_helper<const request&, Args...> { |
| using type = |
| std::function<void(const crow::request&, crow::response&, Args...)>; |
| using args_type = black_magic::S<typename black_magic::promote_t<Args>...>; |
| }; |
| |
| template <typename... Args> |
| struct handler_type_helper<const request&, response&, Args...> { |
| using type = |
| std::function<void(const crow::request&, crow::response&, Args...)>; |
| using args_type = black_magic::S<typename black_magic::promote_t<Args>...>; |
| }; |
| |
| typename handler_type_helper<ArgsWrapped...>::type handler_; |
| |
| void operator()(const request& req, response& res, |
| const routing_params& params) { |
| detail::routing_handler_call_helper::call< |
| detail::routing_handler_call_helper::call_params<decltype(handler_)>, 0, |
| 0, 0, 0, typename handler_type_helper<ArgsWrapped...>::args_type, |
| black_magic::S<>>()( |
| detail::routing_handler_call_helper::call_params<decltype(handler_)>{ |
| handler_, params, req, res}); |
| } |
| }; |
| } |
| } |
| |
| class WebSocketRule : public BaseRule { |
| using self_t = WebSocketRule; |
| |
| public: |
| WebSocketRule(std::string rule) : BaseRule(std::move(rule)) {} |
| |
| void validate() override {} |
| |
| void handle(const request&, response& res, const routing_params&) override { |
| res = response(404); |
| res.end(); |
| } |
| |
| void handle_upgrade(const request& req, response&, |
| SocketAdaptor&& adaptor) override { |
| new crow::websocket::Connection<SocketAdaptor>( |
| req, std::move(adaptor), open_handler_, message_handler_, |
| close_handler_, error_handler_); |
| } |
| #ifdef CROW_ENABLE_SSL |
| void handle_upgrade(const request& req, response&, |
| SSLAdaptor&& adaptor) override { |
| new crow::websocket::Connection<SSLAdaptor>(req, std::move(adaptor), |
| open_handler_, message_handler_, |
| close_handler_, error_handler_); |
| } |
| #endif |
| |
| template <typename Func> |
| self_t& onopen(Func f) { |
| open_handler_ = f; |
| return *this; |
| } |
| |
| template <typename Func> |
| self_t& onmessage(Func f) { |
| message_handler_ = f; |
| return *this; |
| } |
| |
| template <typename Func> |
| self_t& onclose(Func f) { |
| close_handler_ = f; |
| return *this; |
| } |
| |
| template <typename Func> |
| self_t& onerror(Func f) { |
| error_handler_ = f; |
| return *this; |
| } |
| |
| protected: |
| std::function<void(crow::websocket::connection&)> open_handler_; |
| std::function<void(crow::websocket::connection&, const std::string&, bool)> |
| message_handler_; |
| std::function<void(crow::websocket::connection&, const std::string&)> |
| close_handler_; |
| std::function<void(crow::websocket::connection&)> error_handler_; |
| }; |
| |
| template <typename T> |
| struct RuleParameterTraits { |
| using self_t = T; |
| WebSocketRule& websocket() { |
| auto p = new WebSocketRule(((self_t*)this)->rule_); |
| ((self_t*)this)->rule_to_upgrade_.reset(p); |
| return *p; |
| } |
| |
| self_t& name(std::string name) noexcept { |
| ((self_t*)this)->name_ = std::move(name); |
| return (self_t&)*this; |
| } |
| |
| self_t& methods(HTTPMethod method) { |
| ((self_t*)this)->methods_ = 1 << (int)method; |
| return (self_t&)*this; |
| } |
| |
| template <typename... MethodArgs> |
| self_t& methods(HTTPMethod method, MethodArgs... args_method) { |
| methods(args_method...); |
| ((self_t*)this)->methods_ |= 1 << (int)method; |
| return (self_t&)*this; |
| } |
| }; |
| |
| class DynamicRule : public BaseRule, public RuleParameterTraits<DynamicRule> { |
| public: |
| DynamicRule(std::string rule) : BaseRule(std::move(rule)) {} |
| |
| void validate() override { |
| if (!erased_handler_) { |
| throw std::runtime_error(name_ + (!name_.empty() ? ": " : "") + |
| "no handler for url " + rule_); |
| } |
| } |
| |
| void handle(const request& req, response& res, |
| const routing_params& params) override { |
| erased_handler_(req, res, params); |
| } |
| |
| template <typename Func> |
| void operator()(Func f) { |
| using function_t = utility::function_traits<Func>; |
| |
| erased_handler_ = |
| wrap(std::move(f), black_magic::gen_seq<function_t::arity>()); |
| } |
| |
| // enable_if Arg1 == request && Arg2 == response |
| // enable_if Arg1 == request && Arg2 != resposne |
| // enable_if Arg1 != request |
| |
| template <typename Func, unsigned... Indices> |
| |
| std::function<void(const request&, response&, const routing_params&)> wrap( |
| Func f, black_magic::seq<Indices...>) { |
| using function_t = utility::function_traits<Func>; |
| |
| if (!black_magic::is_parameter_tag_compatible( |
| black_magic::get_parameter_tag_runtime(rule_.c_str()), |
| black_magic::compute_parameter_tag_from_args_list< |
| typename function_t::template arg<Indices>...>::value)) { |
| throw std::runtime_error( |
| "route_dynamic: Handler type is mismatched with URL parameters: " + |
| rule_); |
| } |
| auto ret = detail::routing_handler_call_helper::Wrapped< |
| Func, typename function_t::template arg<Indices>...>(); |
| ret.template set<typename function_t::template arg<Indices>...>( |
| std::move(f)); |
| return ret; |
| } |
| |
| template <typename Func> |
| void operator()(std::string name, Func&& f) { |
| name_ = std::move(name); |
| (*this).template operator()<Func>(std::forward(f)); |
| } |
| |
| private: |
| std::function<void(const request&, response&, const routing_params&)> |
| erased_handler_; |
| }; |
| |
| template <typename... Args> |
| class TaggedRule : public BaseRule, |
| public RuleParameterTraits<TaggedRule<Args...>> { |
| public: |
| using self_t = TaggedRule<Args...>; |
| |
| TaggedRule(std::string rule) : BaseRule(std::move(rule)) {} |
| |
| void validate() override { |
| if (!handler_) { |
| throw std::runtime_error(name_ + (!name_.empty() ? ": " : "") + |
| "no handler for url " + rule_); |
| } |
| } |
| |
| template <typename Func> |
| typename std::enable_if< |
| black_magic::CallHelper<Func, black_magic::S<Args...>>::value, void>::type |
| operator()(Func&& f) { |
| static_assert( |
| black_magic::CallHelper<Func, black_magic::S<Args...>>::value || |
| black_magic::CallHelper< |
| Func, black_magic::S<crow::request, Args...>>::value, |
| "Handler type is mismatched with URL parameters"); |
| static_assert( |
| !std::is_same<void, decltype(f(std::declval<Args>()...))>::value, |
| "Handler function cannot have void return type; valid return types: " |
| "string, int, crow::resposne, nlohmann::json"); |
| |
| handler_ = [f = std::move(f)](const request&, response& res, Args... args) { |
| res = response(f(args...)); |
| res.end(); |
| }; |
| } |
| |
| template <typename Func> |
| typename std::enable_if< |
| !black_magic::CallHelper<Func, black_magic::S<Args...>>::value && |
| black_magic::CallHelper< |
| Func, black_magic::S<crow::request, Args...>>::value, |
| void>::type |
| operator()(Func&& f) { |
| static_assert( |
| black_magic::CallHelper<Func, black_magic::S<Args...>>::value || |
| black_magic::CallHelper< |
| Func, black_magic::S<crow::request, Args...>>::value, |
| "Handler type is mismatched with URL parameters"); |
| static_assert( |
| !std::is_same<void, decltype(f(std::declval<crow::request>(), |
| std::declval<Args>()...))>::value, |
| "Handler function cannot have void return type; valid return types: " |
| "string, int, crow::resposne,nlohmann::json"); |
| |
| handler_ = [f = std::move(f)](const crow::request& req, crow::response& res, |
| Args... args) { |
| res = response(f(req, args...)); |
| res.end(); |
| }; |
| } |
| |
| template <typename Func> |
| typename std::enable_if< |
| !black_magic::CallHelper<Func, black_magic::S<Args...>>::value && |
| !black_magic::CallHelper< |
| Func, black_magic::S<crow::request, Args...>>::value, |
| void>::type |
| operator()(Func&& f) { |
| static_assert( |
| black_magic::CallHelper<Func, black_magic::S<Args...>>::value || |
| black_magic::CallHelper< |
| Func, black_magic::S<crow::request, Args...>>::value || |
| black_magic::CallHelper< |
| Func, |
| black_magic::S<crow::request, crow::response&, Args...>>::value, |
| "Handler type is mismatched with URL parameters"); |
| static_assert( |
| std::is_same<void, decltype(f(std::declval<crow::request>(), |
| std::declval<crow::response&>(), |
| std::declval<Args>()...))>::value, |
| "Handler function with response argument should have void return type"); |
| |
| handler_ = std::move(f); |
| } |
| |
| template <typename Func> |
| void operator()(std::string name, Func&& f) { |
| name_ = std::move(name); |
| (*this).template operator()<Func>(std::forward(f)); |
| } |
| |
| void handle(const request& req, response& res, |
| const routing_params& params) override { |
| detail::routing_handler_call_helper::call< |
| detail::routing_handler_call_helper::call_params<decltype(handler_)>, 0, |
| 0, 0, 0, black_magic::S<Args...>, black_magic::S<>>()( |
| detail::routing_handler_call_helper::call_params<decltype(handler_)>{ |
| handler_, params, req, res}); |
| } |
| |
| private: |
| std::function<void(const crow::request&, crow::response&, Args...)> handler_; |
| }; |
| |
| const int RULE_SPECIAL_REDIRECT_SLASH = 1; |
| |
| class Trie { |
| public: |
| struct Node { |
| unsigned rule_index{}; |
| std::array<unsigned, (int)ParamType::MAX> param_childrens{}; |
| boost::container::flat_map<std::string, unsigned> children; |
| |
| bool IsSimpleNode() const { |
| return !rule_index && |
| std::all_of(std::begin(param_childrens), std::end(param_childrens), |
| [](unsigned x) { return !x; }); |
| } |
| }; |
| |
| Trie() : nodes_(1) {} |
| |
| private: |
| void optimizeNode(Node* node) { |
| for (auto x : node->param_childrens) { |
| if (!x) continue; |
| Node* child = &nodes_[x]; |
| optimizeNode(child); |
| } |
| if (node->children.empty()) return; |
| bool mergeWithChild = true; |
| for (auto& kv : node->children) { |
| Node* child = &nodes_[kv.second]; |
| if (!child->IsSimpleNode()) { |
| mergeWithChild = false; |
| break; |
| } |
| } |
| if (mergeWithChild) { |
| decltype(node->children) merged; |
| for (auto& kv : node->children) { |
| Node* child = &nodes_[kv.second]; |
| for (auto& child_kv : child->children) { |
| merged[kv.first + child_kv.first] = child_kv.second; |
| } |
| } |
| node->children = std::move(merged); |
| optimizeNode(node); |
| } else { |
| for (auto& kv : node->children) { |
| Node* child = &nodes_[kv.second]; |
| optimizeNode(child); |
| } |
| } |
| } |
| |
| void optimize() { optimizeNode(head()); } |
| |
| public: |
| void validate() { |
| if (!head()->IsSimpleNode()) |
| throw std::runtime_error("Internal error: Trie header should be simple!"); |
| optimize(); |
| } |
| |
| void find_route_indexes(const std::string& req_url, |
| std::vector<unsigned>& route_indexes, |
| const Node* node = nullptr, unsigned pos = 0) { |
| if (node == nullptr) { |
| node = head(); |
| } |
| for (auto& kv : node->children) { |
| const std::string& fragment = kv.first; |
| const Node* child = &nodes_[kv.second]; |
| if (pos >= req_url.size()) { |
| if (child->rule_index != 0 && fragment != "/") { |
| route_indexes.push_back(child->rule_index); |
| } |
| find_route_indexes(req_url, route_indexes, child, |
| pos + fragment.size()); |
| } else { |
| if (req_url.compare(pos, fragment.size(), fragment) == 0) { |
| find_route_indexes(req_url, route_indexes, child, |
| pos + fragment.size()); |
| } |
| } |
| } |
| } |
| |
| std::pair<unsigned, routing_params> find( |
| const std::string& req_url, const Node* node = nullptr, unsigned pos = 0, |
| routing_params* params = nullptr) const { |
| routing_params empty; |
| if (params == nullptr) params = ∅ |
| |
| unsigned found{}; |
| routing_params match_params; |
| |
| if (node == nullptr) node = head(); |
| if (pos == req_url.size()) return {node->rule_index, *params}; |
| |
| auto update_found = |
| [&found, &match_params](std::pair<unsigned, routing_params>& ret) { |
| if (ret.first && (!found || found > ret.first)) { |
| found = ret.first; |
| match_params = std::move(ret.second); |
| } |
| }; |
| |
| if (node->param_childrens[(int)ParamType::INT]) { |
| char c = req_url[pos]; |
| if ((c >= '0' && c <= '9') || c == '+' || c == '-') { |
| char* eptr; |
| errno = 0; |
| long long int value = strtoll(req_url.data() + pos, &eptr, 10); |
| if (errno != ERANGE && eptr != req_url.data() + pos) { |
| params->int_params.push_back(value); |
| auto ret = |
| find(req_url, &nodes_[node->param_childrens[(int)ParamType::INT]], |
| eptr - req_url.data(), params); |
| update_found(ret); |
| params->int_params.pop_back(); |
| } |
| } |
| } |
| |
| if (node->param_childrens[(int)ParamType::UINT]) { |
| char c = req_url[pos]; |
| if ((c >= '0' && c <= '9') || c == '+') { |
| char* eptr; |
| errno = 0; |
| unsigned long long int value = |
| strtoull(req_url.data() + pos, &eptr, 10); |
| if (errno != ERANGE && eptr != req_url.data() + pos) { |
| params->uint_params.push_back(value); |
| auto ret = find(req_url, |
| &nodes_[node->param_childrens[(int)ParamType::UINT]], |
| eptr - req_url.data(), params); |
| update_found(ret); |
| params->uint_params.pop_back(); |
| } |
| } |
| } |
| |
| if (node->param_childrens[(int)ParamType::DOUBLE]) { |
| char c = req_url[pos]; |
| if ((c >= '0' && c <= '9') || c == '+' || c == '-' || c == '.') { |
| char* eptr; |
| errno = 0; |
| double value = strtod(req_url.data() + pos, &eptr); |
| if (errno != ERANGE && eptr != req_url.data() + pos) { |
| params->double_params.push_back(value); |
| auto ret = find( |
| req_url, &nodes_[node->param_childrens[(int)ParamType::DOUBLE]], |
| eptr - req_url.data(), params); |
| update_found(ret); |
| params->double_params.pop_back(); |
| } |
| } |
| } |
| |
| if (node->param_childrens[(int)ParamType::STRING]) { |
| size_t epos = pos; |
| for (; epos < req_url.size(); epos++) { |
| if (req_url[epos] == '/') break; |
| } |
| |
| if (epos != pos) { |
| params->string_params.push_back(req_url.substr(pos, epos - pos)); |
| auto ret = find(req_url, |
| &nodes_[node->param_childrens[(int)ParamType::STRING]], |
| epos, params); |
| update_found(ret); |
| params->string_params.pop_back(); |
| } |
| } |
| |
| if (node->param_childrens[(int)ParamType::PATH]) { |
| size_t epos = req_url.size(); |
| |
| if (epos != pos) { |
| params->string_params.push_back(req_url.substr(pos, epos - pos)); |
| auto ret = |
| find(req_url, &nodes_[node->param_childrens[(int)ParamType::PATH]], |
| epos, params); |
| update_found(ret); |
| params->string_params.pop_back(); |
| } |
| } |
| |
| for (auto& kv : node->children) { |
| const std::string& fragment = kv.first; |
| const Node* child = &nodes_[kv.second]; |
| |
| if (req_url.compare(pos, fragment.size(), fragment) == 0) { |
| auto ret = find(req_url, child, pos + fragment.size(), params); |
| update_found(ret); |
| } |
| } |
| |
| return {found, match_params}; |
| } |
| |
| void add(const std::string& url, unsigned rule_index) { |
| unsigned idx{0}; |
| |
| for (unsigned i = 0; i < url.size(); i++) { |
| char c = url[i]; |
| if (c == '<') { |
| static struct ParamTraits { |
| ParamType type; |
| std::string name; |
| } paramTraits[] = { |
| {ParamType::INT, "<int>"}, {ParamType::UINT, "<uint>"}, |
| {ParamType::DOUBLE, "<float>"}, {ParamType::DOUBLE, "<double>"}, |
| {ParamType::STRING, "<str>"}, {ParamType::STRING, "<string>"}, |
| {ParamType::PATH, "<path>"}, |
| }; |
| |
| for (auto& x : paramTraits) { |
| if (url.compare(i, x.name.size(), x.name) == 0) { |
| if (!nodes_[idx].param_childrens[(int)x.type]) { |
| auto new_node_idx = new_node(); |
| nodes_[idx].param_childrens[(int)x.type] = new_node_idx; |
| } |
| idx = nodes_[idx].param_childrens[(int)x.type]; |
| i += x.name.size(); |
| break; |
| } |
| } |
| |
| i--; |
| } else { |
| std::string piece(&c, 1); |
| if (!nodes_[idx].children.count(piece)) { |
| auto new_node_idx = new_node(); |
| nodes_[idx].children.emplace(piece, new_node_idx); |
| } |
| idx = nodes_[idx].children[piece]; |
| } |
| } |
| if (nodes_[idx].rule_index) |
| throw std::runtime_error("handler already exists for " + url); |
| nodes_[idx].rule_index = rule_index; |
| } |
| |
| private: |
| void debug_node_print(Node* n, int level) { |
| for (int i = 0; i < (int)ParamType::MAX; i++) { |
| if (n->param_childrens[i]) { |
| CROW_LOG_DEBUG << std::string( |
| 2 * level, ' ') /*<< "("<<n->param_childrens[i]<<") "*/; |
| switch ((ParamType)i) { |
| case ParamType::INT: |
| CROW_LOG_DEBUG << "<int>"; |
| break; |
| case ParamType::UINT: |
| CROW_LOG_DEBUG << "<uint>"; |
| break; |
| case ParamType::DOUBLE: |
| CROW_LOG_DEBUG << "<float>"; |
| break; |
| case ParamType::STRING: |
| CROW_LOG_DEBUG << "<str>"; |
| break; |
| case ParamType::PATH: |
| CROW_LOG_DEBUG << "<path>"; |
| break; |
| default: |
| CROW_LOG_DEBUG << "<ERROR>"; |
| break; |
| } |
| |
| debug_node_print(&nodes_[n->param_childrens[i]], level + 1); |
| } |
| } |
| for (auto& kv : n->children) { |
| CROW_LOG_DEBUG << std::string(2 * level, |
| ' ') /*<< "(" << kv.second << ") "*/ |
| << kv.first; |
| debug_node_print(&nodes_[kv.second], level + 1); |
| } |
| } |
| |
| public: |
| void debug_print() { debug_node_print(head(), 0); } |
| |
| private: |
| const Node* head() const { return &nodes_.front(); } |
| |
| Node* head() { return &nodes_.front(); } |
| |
| unsigned new_node() { |
| nodes_.resize(nodes_.size() + 1); |
| return nodes_.size() - 1; |
| } |
| |
| std::vector<Node> nodes_; |
| }; |
| |
| class Router { |
| public: |
| Router() : rules_(2) {} |
| |
| DynamicRule& new_rule_dynamic(const std::string& rule) { |
| auto ruleObject = new DynamicRule(rule); |
| |
| internal_add_rule_object(rule, ruleObject); |
| |
| return *ruleObject; |
| } |
| |
| template <uint64_t N> |
| typename black_magic::arguments<N>::type::template rebind<TaggedRule>& |
| new_rule_tagged(const std::string& rule) { |
| using RuleT = |
| typename black_magic::arguments<N>::type::template rebind<TaggedRule>; |
| auto ruleObject = new RuleT(rule); |
| |
| internal_add_rule_object(rule, ruleObject); |
| |
| return *ruleObject; |
| } |
| |
| void internal_add_rule_object(const std::string& rule, BaseRule* ruleObject) { |
| rules_.emplace_back(ruleObject); |
| trie_.add(rule, rules_.size() - 1); |
| |
| // directory case: |
| // request to `/about' url matches `/about/' rule |
| if (rule.size() > 1 && rule.back() == '/') { |
| std::string rule_without_trailing_slash = rule; |
| rule_without_trailing_slash.pop_back(); |
| rules_.emplace_back(ruleObject); |
| trie_.add(rule_without_trailing_slash, rules_.size() - 1); |
| // trie_.add(rule_without_trailing_slash, RULE_SPECIAL_REDIRECT_SLASH); |
| } |
| } |
| |
| void validate() { |
| trie_.validate(); |
| for (auto& rule : rules_) { |
| if (rule) { |
| auto upgraded = rule->upgrade(); |
| if (upgraded) rule = std::move(upgraded); |
| rule->validate(); |
| } |
| } |
| } |
| |
| template <typename Adaptor> |
| void handle_upgrade(const request& req, response& res, Adaptor&& adaptor) { |
| auto found = trie_.find(req.url); |
| unsigned rule_index = found.first; |
| if (!rule_index) { |
| CROW_LOG_DEBUG << "Cannot match rules " << req.url; |
| res = response(404); |
| res.end(); |
| return; |
| } |
| |
| if (rule_index >= rules_.size()) |
| throw std::runtime_error("Trie internal structure corrupted!"); |
| |
| if (rule_index == RULE_SPECIAL_REDIRECT_SLASH) { |
| CROW_LOG_INFO << "Redirecting to a url with trailing slash: " << req.url; |
| res = response(301); |
| |
| // TODO absolute url building |
| if (req.get_header_value("Host").empty()) { |
| res.add_header("Location", req.url + "/"); |
| } else { |
| res.add_header( |
| "Location", |
| req.is_secure |
| ? "https://" |
| : "http://" + req.get_header_value("Host") + req.url + "/"); |
| } |
| res.end(); |
| return; |
| } |
| |
| if ((rules_[rule_index]->get_methods() & (1 << (uint32_t)req.method)) == |
| 0) { |
| CROW_LOG_DEBUG << "Rule found but method mismatch: " << req.url |
| << " with " << method_name(req.method) << "(" |
| << (uint32_t)req.method << ") / " |
| << rules_[rule_index]->get_methods(); |
| res = response(404); |
| res.end(); |
| return; |
| } |
| |
| CROW_LOG_DEBUG << "Matched rule (upgrade) '" << rules_[rule_index]->rule_ |
| << "' " << (uint32_t)req.method << " / " |
| << rules_[rule_index]->get_methods(); |
| |
| // any uncaught exceptions become 500s |
| try { |
| rules_[rule_index]->handle_upgrade(req, res, std::move(adaptor)); |
| } catch (std::exception& e) { |
| CROW_LOG_ERROR << "An uncaught exception occurred: " << e.what(); |
| res = response(500); |
| res.end(); |
| return; |
| } catch (...) { |
| CROW_LOG_ERROR << "An uncaught exception occurred. The type was unknown " |
| "so no information was available."; |
| res = response(500); |
| res.end(); |
| return; |
| } |
| } |
| |
| void handle(const request& req, response& res) { |
| auto found = trie_.find(req.url); |
| |
| unsigned rule_index = found.first; |
| |
| if (!rule_index) { |
| CROW_LOG_DEBUG << "Cannot match rules " << req.url; |
| res = response(404); |
| res.end(); |
| return; |
| } |
| |
| if (rule_index >= rules_.size()) |
| throw std::runtime_error("Trie internal structure corrupted!"); |
| |
| if (rule_index == RULE_SPECIAL_REDIRECT_SLASH) { |
| CROW_LOG_INFO << "Redirecting to a url with trailing slash: " << req.url; |
| res = response(301); |
| |
| // TODO absolute url building |
| if (req.get_header_value("Host").empty()) { |
| res.add_header("Location", req.url + "/"); |
| } else { |
| res.add_header("Location", |
| (req.is_secure ? "https://" : "http://") + |
| req.get_header_value("Host") + req.url + "/"); |
| } |
| res.end(); |
| return; |
| } |
| |
| if ((rules_[rule_index]->get_methods() & (1 << (uint32_t)req.method)) == |
| 0) { |
| CROW_LOG_DEBUG << "Rule found but method mismatch: " << req.url |
| << " with " << method_name(req.method) << "(" |
| << (uint32_t)req.method << ") / " |
| << rules_[rule_index]->get_methods(); |
| res = response(404); |
| res.end(); |
| return; |
| } |
| |
| CROW_LOG_DEBUG << "Matched rule '" << rules_[rule_index]->rule_ << "' " |
| << (uint32_t)req.method << " / " |
| << rules_[rule_index]->get_methods(); |
| |
| // any uncaught exceptions become 500s |
| try { |
| rules_[rule_index]->handle(req, res, found.second); |
| } catch (std::exception& e) { |
| CROW_LOG_ERROR << "An uncaught exception occurred: " << e.what(); |
| res = response(500); |
| res.end(); |
| return; |
| } catch (...) { |
| CROW_LOG_ERROR << "An uncaught exception occurred. The type was unknown " |
| "so no information was available."; |
| res = response(500); |
| res.end(); |
| return; |
| } |
| } |
| |
| void debug_print() { trie_.debug_print(); } |
| |
| std::vector<const std::string*> get_routes(const std::string& parent) { |
| std::vector<unsigned> x; |
| std::vector<const std::string*> ret; |
| trie_.find_route_indexes(parent, x); |
| for (unsigned index : x) { |
| ret.push_back(&rules_[index]->rule_); |
| } |
| return ret; |
| } |
| |
| private: |
| std::vector<std::unique_ptr<BaseRule>> rules_; |
| Trie trie_; |
| }; |
| } |