#pragma once #include #include #include #include #include #include #include "common.h" #include "http_response.h" #include "http_request.h" #include "utility.h" #include "logging.h" namespace crow { class BaseRule { public: virtual ~BaseRule() { } virtual void validate() = 0; virtual void handle(const request&, response&, const routing_params&) = 0; protected: }; template class TaggedRule : public BaseRule { private: template struct call_params { H1& handler; H2& handler_with_req; H3& handler_with_req_res; const routing_params& params; const request& req; response& res; }; template struct call { }; template struct call, black_magic::S> { void operator()(F cparams) { using pushed = typename black_magic::S::template push_back>; call, pushed>()(cparams); } }; template struct call, black_magic::S> { void operator()(F cparams) { using pushed = typename black_magic::S::template push_back>; call, pushed>()(cparams); } }; template struct call, black_magic::S> { void operator()(F cparams) { using pushed = typename black_magic::S::template push_back>; call, pushed>()(cparams); } }; template struct call, black_magic::S> { void operator()(F cparams) { using pushed = typename black_magic::S::template push_back>; call, pushed>()(cparams); } }; template struct call, black_magic::S> { void operator()(F cparams) { if (cparams.handler) { cparams.res = cparams.handler( cparams.params.template get(Args1::pos)... ); cparams.res.end(); return; } if (cparams.handler_with_req) { cparams.res = cparams.handler_with_req( cparams.req, cparams.params.template get(Args1::pos)... ); cparams.res.end(); return; } if (cparams.handler_with_req_res) { cparams.handler_with_req_res( cparams.req, cparams.res, cparams.params.template get(Args1::pos)... ); return; } CROW_LOG_DEBUG << "ERROR cannot find handler"; // we already found matched url; this is server error cparams.res = response(500); } }; public: using self_t = TaggedRule; TaggedRule(std::string rule) : rule_(std::move(rule)) { } self_t& name(std::string name) noexcept { name_ = std::move(name); return *this; } self_t& methods(HTTPMethod method) { methods_ = 1<<(int)method; } template self_t& methods(HTTPMethod method, MethodArgs ... args_method) { methods(args_method...); methods_ |= 1<<(int)method; } void validate() { if (!handler_ && !handler_with_req_ && !handler_with_req_res_) { throw std::runtime_error(name_ + (!name_.empty() ? ": " : "") + "no handler for url " + rule_); } } template typename std::enable_if>::value, void>::type operator()(Func&& f) { static_assert(black_magic::CallHelper>::value || black_magic::CallHelper>::value , "Handler type is mismatched with URL paramters"); static_assert(!std::is_same()...))>::value, "Handler function cannot have void return type; valid return types: string, int, crow::resposne, crow::json::wvalue"); handler_ = [f = std::move(f)](Args ... args){ return response(f(args...)); }; handler_with_req_ = nullptr; handler_with_req_res_ = nullptr; } template typename std::enable_if< !black_magic::CallHelper>::value && black_magic::CallHelper>::value, void>::type operator()(Func&& f) { static_assert(black_magic::CallHelper>::value || black_magic::CallHelper>::value, "Handler type is mismatched with URL paramters"); static_assert(!std::is_same(), std::declval()...))>::value, "Handler function cannot have void return type; valid return types: string, int, crow::resposne, crow::json::wvalue"); handler_with_req_ = [f = std::move(f)](const crow::request& req, Args ... args){ return response(f(req, args...)); }; handler_ = nullptr; handler_with_req_res_ = nullptr; } template typename std::enable_if< !black_magic::CallHelper>::value && !black_magic::CallHelper>::value, void>::type operator()(Func&& f) { static_assert(black_magic::CallHelper>::value || black_magic::CallHelper>::value || black_magic::CallHelper>::value , "Handler type is mismatched with URL paramters"); static_assert(std::is_same(), std::declval(), std::declval()...))>::value, "Handler function with response argument should have void return type"); handler_with_req_res_ = std::move(f); //[f = std::move(f)](const crow::request& req, crow::response& res, Args ... args){ // f(req, response, args...); //}; handler_ = nullptr; handler_with_req_ = nullptr; } template void operator()(std::string name, Func&& f) { name_ = std::move(name); (*this).template operator()(std::forward(f)); } void handle(const request& req, response& res, const routing_params& params) override { call< call_params< decltype(handler_), decltype(handler_with_req_), decltype(handler_with_req_res_)>, 0, 0, 0, 0, black_magic::S, black_magic::S<> >()( call_params< decltype(handler_), decltype(handler_with_req_), decltype(handler_with_req_res_)> {handler_, handler_with_req_, handler_with_req_res_, params, req, res} ); } private: std::function handler_; std::function handler_with_req_; std::function handler_with_req_res_; std::string rule_; std::string name_; uint32_t methods_{1<<(int)HTTPMethod::GET}; template struct call_pair { using type = T; static const int pos = Pos; }; friend class Router; }; class Trie { public: struct Node { unsigned rule_index{}; std::array param_childrens{}; std::unordered_map 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(); } std::pair find(const request& req, 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& 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, &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, &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, &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, &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, &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, 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, "" }, { ParamType::UINT, "" }, { ParamType::DOUBLE, "" }, { ParamType::DOUBLE, "" }, { ParamType::STRING, "" }, { ParamType::STRING, "" }, { ParamType::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, ' ') /*<< "("<param_childrens[i]<<") "*/; switch((ParamType)i) { case ParamType::INT: CROW_LOG_DEBUG << ""; break; case ParamType::UINT: CROW_LOG_DEBUG << ""; break; case ParamType::DOUBLE: CROW_LOG_DEBUG << ""; break; case ParamType::STRING: CROW_LOG_DEBUG << ""; break; case ParamType::PATH: CROW_LOG_DEBUG << ""; break; default: CROW_LOG_DEBUG << ""; 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 nodes_; }; class Router { public: Router() : rules_(1) {} template typename black_magic::arguments::type::template rebind& new_rule_tagged(const std::string& rule) { using RuleT = typename black_magic::arguments::type::template rebind; auto ruleObject = new RuleT(rule); rules_.emplace_back(ruleObject); trie_.add(rule, rules_.size() - 1); return *ruleObject; } void validate() { trie_.validate(); for(auto& rule:rules_) { if (rule) rule->validate(); } } void handle(const request& req, response& res) { auto found = trie_.find(req); 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!"); CROW_LOG_DEBUG << "Matched rule '" << ((TaggedRule<>*)rules_[rule_index].get())->rule_ << "'"; rules_[rule_index]->handle(req, res, found.second); } void debug_print() { trie_.debug_print(); } private: std::vector> rules_; Trie trie_; }; }