#pragma once #include #include #include #include #include #include #include #include "crow/common.h" #include "crow/http_response.h" #include "crow/http_request.h" #include "crow/utility.h" #include "crow/logging.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 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 rule_to_upgrade_; friend class Router; template friend struct RuleParameterTraits; }; namespace detail { namespace routing_handler_call_helper { template struct call_pair { using type = T; static const int pos = Pos; }; template struct call_params { H1& handler; 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) { cparams.handler( cparams.req, cparams.res, cparams.params.template get(Args1::pos)... ); } }; template struct Wrapped { template void set_(Func f, typename std::enable_if< !std::is_same>::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 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 void set_(Func f, typename std::enable_if< std::is_same>::type, const request&>::value && !std::is_same>::type, response&>::value , int>::type = 0) { handler_ = req_handler_wrapper(std::move(f)); /*handler_ = ( [f = std::move(f)] (const request& req, response& res, Args... args){ res = response(f(req, args...)); res.end(); });*/ } template void set_(Func f, typename std::enable_if< std::is_same>::type, const request&>::value && std::is_same>::type, response&>::value , int>::type = 0) { handler_ = std::move(f); } template struct handler_type_helper { using type = std::function; using args_type = black_magic::S...>; }; template struct handler_type_helper { using type = std::function; using args_type = black_magic::S...>; }; template struct handler_type_helper { using type = std::function; using args_type = black_magic::S...>; }; typename handler_type_helper::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::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(req, std::move(adaptor), open_handler_, message_handler_, close_handler_, error_handler_, accept_handler_); } #ifdef CROW_ENABLE_SSL void handle_upgrade(const request& req, response&, SSLAdaptor&& adaptor) override { new crow::websocket::Connection(req, std::move(adaptor), open_handler_, message_handler_, close_handler_, error_handler_, accept_handler_); } #endif template self_t& onopen(Func f) { open_handler_ = f; return *this; } template self_t& onmessage(Func f) { message_handler_ = f; return *this; } template self_t& onclose(Func f) { close_handler_ = f; return *this; } template self_t& onerror(Func f) { error_handler_ = f; return *this; } template self_t& onaccept(Func f) { accept_handler_ = f; return *this; } protected: std::function open_handler_; std::function message_handler_; std::function close_handler_; std::function error_handler_; std::function accept_handler_; }; template 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 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 { 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 void operator()(Func f) { #ifdef CROW_MSVC_WORKAROUND using function_t = utility::function_traits; #else using function_t = utility::function_traits; #endif erased_handler_ = wrap(std::move(f), black_magic::gen_seq()); } // enable_if Arg1 == request && Arg2 == response // enable_if Arg1 == request && Arg2 != resposne // enable_if Arg1 != request #ifdef CROW_MSVC_WORKAROUND template #else template #endif std::function wrap(Func f, black_magic::seq) { #ifdef CROW_MSVC_WORKAROUND using function_t = utility::function_traits; #else using function_t = utility::function_traits; #endif 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...>::value)) { throw std::runtime_error("route_dynamic: Handler type is mismatched with URL parameters: " + rule_); } auto ret = detail::routing_handler_call_helper::Wrapped...>(); ret.template set_< typename function_t::template arg... >(std::move(f)); return ret; } template void operator()(std::string name, Func&& f) { name_ = std::move(name); (*this).template operator()(std::forward(f)); } private: std::function erased_handler_; }; template class TaggedRule : public BaseRule, public RuleParameterTraits> { public: using self_t = TaggedRule; 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 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 parameters"); static_assert(!std::is_same()...))>::value, "Handler function cannot have void return type; valid return types: string, int, crow::resposne, crow::json::wvalue"); 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 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 parameters"); 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_ = ( #ifdef CROW_CAN_USE_CPP14 [f = std::move(f)] #else [f] #endif (const crow::request& req, crow::response& res, Args ... args){ res = response(f(req, args...)); res.end(); }); } 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 parameters"); static_assert(std::is_same(), std::declval(), std::declval()...))>::value, "Handler function with response argument should have void return type"); handler_ = std::move(f); } 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 { detail::routing_handler_call_helper::call< detail::routing_handler_call_helper::call_params< decltype(handler_)>, 0, 0, 0, 0, black_magic::S, black_magic::S<> >()( detail::routing_handler_call_helper::call_params< decltype(handler_)> {handler_, params, req, res} ); } private: std::function handler_; }; const int RULE_SPECIAL_REDIRECT_SLASH = 1; 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 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& 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, "" }, { 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_(2) { } DynamicRule& new_rule_dynamic(const std::string& rule) { auto ruleObject = new DynamicRule(rule); internal_add_rule_object(rule, ruleObject); return *ruleObject; } 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); 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(); 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 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", "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", "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(); } private: std::vector> rules_; Trie trie_; }; }