#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" #include "crow/mustache.h" namespace crow { /// A base class for all rules. /// Used to provide a common interface for code dealing with different types of rules. /// A Rule provides a URL, allowed HTTP methods, and handlers. 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_; } template void foreach_method(F f) { for(uint32_t method = 0, method_bit = 1; method < static_cast(HTTPMethod::InternalMethodCount); method++, method_bit<<=1) { if (methods_ & method_bit) f(method); } } std::string custom_templates_base; const std::string& rule() { return rule_; } protected: uint32_t methods_{1<(HTTPMethod::Get)}; std::string rule_; std::string name_; std::unique_ptr rule_to_upgrade_; friend class Router; friend class Blueprint; 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 CatchallRule { public: CatchallRule(){} template typename std::enable_if>::value, void>::type operator()(Func&& f) { static_assert(!std::is_same::value, "Handler function cannot have void return type; valid return types: string, int, crow::response, crow::returnable"); handler_ = ( #ifdef CROW_CAN_USE_CPP14 [f = std::move(f)] #else [f] #endif (const request&, response& res){ res = response(f()); res.end(); }); } template typename std::enable_if< !black_magic::CallHelper>::value && black_magic::CallHelper>::value, void>::type operator()(Func&& f) { static_assert(!std::is_same()))>::value, "Handler function cannot have void return type; valid return types: string, int, crow::response, crow::returnable"); handler_ = ( #ifdef CROW_CAN_USE_CPP14 [f = std::move(f)] #else [f] #endif (const crow::request& req, crow::response& res){ res = response(f(req)); res.end(); }); } template typename std::enable_if< !black_magic::CallHelper>::value && !black_magic::CallHelper>::value && black_magic::CallHelper>::value, void>::type operator()(Func&& f) { static_assert(std::is_same()))>::value, "Handler function with response argument should have void return type"); handler_ = ( #ifdef CROW_CAN_USE_CPP14 [f = std::move(f)] #else [f] #endif (const crow::request&, crow::response& res){ f(res); }); } template typename std::enable_if< !black_magic::CallHelper>::value && !black_magic::CallHelper>::value && !black_magic::CallHelper>::value, void>::type operator()(Func&& f) { static_assert(std::is_same(), std::declval()))>::value, "Handler function with response argument should have void return type"); handler_ = std::move(f); } bool has_handler() { return (handler_ != nullptr); } protected: friend class Router; private: std::function handler_; }; /// A rule dealing with websockets. /// Provides the interface for the user to put in the necessary handlers for a websocket to work. /// 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_; }; /// Allows the user to assign parameters using functions. /// /// `rule.name("name").methods(HTTPMethod::POST)` template struct RuleParameterTraits { using self_t = T; WebSocketRule& websocket() { auto p =new WebSocketRule(static_cast(this)->rule_); static_cast(this)->rule_to_upgrade_.reset(p); return *p; } self_t& name(std::string name) noexcept { static_cast(this)->name_ = std::move(name); return static_cast(*this); } self_t& methods(HTTPMethod method) { static_cast(this)->methods_ = 1 << static_cast(method); return static_cast(*this); } template self_t& methods(HTTPMethod method, MethodArgs ... args_method) { methods(args_method...); static_cast(this)->methods_ |= 1 << static_cast(method); return static_cast(*this); } }; /// A rule that can change its parameters during runtime. 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 { if (!custom_templates_base.empty()) mustache::set_base(custom_templates_base); else if (mustache::detail::get_template_base_directory_ref() != "templates") mustache::set_base("templates"); 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_; }; /// Default rule created when CROW_ROUTE is called. 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::response, crow::returnable"); 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::response, crow::returnable"); 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 && 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 with response argument should have void return type"); handler_ = ( #ifdef CROW_CAN_USE_CPP14 [f = std::move(f)] #else [f] #endif (const crow::request&, crow::response& res, Args ... args){ f(res, args...); }); } template typename std::enable_if< !black_magic::CallHelper>::value && !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 { if (!custom_templates_base.empty()) mustache::set_base(custom_templates_base); else if (mustache::detail::get_template_base_directory_ref() != "templates") mustache::set_base("templates"); 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; /// A search tree. class Trie { public: struct Node { uint16_t rule_index{}; // Assign the index to the maximum 32 unsigned integer value by default so that any other number (specifically 0) is a valid BP id. uint16_t blueprint_index{INVALID_BP_ID}; std::string key; ParamType param = ParamType::MAX; // MAX = No param. std::vector children; bool IsSimpleNode() const { return !rule_index && blueprint_index == INVALID_BP_ID && children.size() < 2 && param == ParamType::MAX && std::all_of(std::begin(children), std::end(children), [](Node* x){ return x->param == ParamType::MAX; }); } }; Trie() { } ///Check whether or not the trie is empty. bool is_empty() { return head_.children.empty(); } void optimize() { for (auto child: head_.children) { optimizeNode(child); } } private: void optimizeNode(Node* node) { if (node->children.empty()) return; if (node->IsSimpleNode()) { Node* child_temp = node->children[0]; node->key = node->key + child_temp->key; node->rule_index = child_temp->rule_index; node->blueprint_index = child_temp->blueprint_index; node->children = std::move(child_temp->children); delete(child_temp); optimizeNode(node); } else { for(auto& child : node->children) { optimizeNode(child); } } } void debug_node_print(Node* node, int level) { if (node->param != ParamType::MAX) { switch(node->param) { case ParamType::INT: CROW_LOG_DEBUG << std::string(2*level, ' ') << ""; break; case ParamType::UINT: CROW_LOG_DEBUG << std::string(2*level, ' ') << ""; break; case ParamType::DOUBLE: CROW_LOG_DEBUG << std::string(2*level, ' ') << ""; break; case ParamType::STRING: CROW_LOG_DEBUG << std::string(2*level, ' ') << ""; break; case ParamType::PATH: CROW_LOG_DEBUG << std::string(2*level, ' ') << ""; break; default: CROW_LOG_DEBUG << std::string(2*level, ' ') << ""; break; } } else CROW_LOG_DEBUG << std::string(2*level, ' ') << node->key; for(auto& child : node->children) { debug_node_print(child, level+1); } } public: void debug_print() { CROW_LOG_DEBUG << "HEAD"; for (auto& child : head_.children) debug_node_print(child, 1); } void validate() { if (!head_.IsSimpleNode()) throw std::runtime_error("Internal error: Trie header should be simple!"); optimize(); } //Rule_index, Blueprint_index, routing_params std::tuple, routing_params> find(const std::string& req_url, const Node* node = nullptr, unsigned pos = 0, routing_params* params = nullptr, std::vector* blueprints = nullptr) const { //start params as an empty struct routing_params empty; if (params == nullptr) params = ∅ //same for blueprint vector std::vector MT; if (blueprints == nullptr) blueprints = &MT; uint16_t found{}; //The rule index to be found std::vector found_BP; //The Blueprint indices to be found routing_params match_params; //supposedly the final matched parameters //start from the head node if (node == nullptr) node = &head_; auto update_found = [&found, &found_BP, &match_params](std::tuple, routing_params>& ret) { found_BP = std::move(std::get<1>(ret)); if (std::get<0>(ret) && (!found || found > std::get<0>(ret))) { found = std::get<0>(ret); match_params = std::move(std::get<2>(ret)); } }; //if the function was called on a node at the end of the string (the last recursion), return the nodes rule index, and whatever params were passed to the function if (pos == req_url.size()) { found_BP = std::move(*blueprints); return {node->rule_index, *blueprints, *params}; } bool found_fragment = false; for(auto& child : node->children) { if (child->param != ParamType::MAX) { if (child->param == 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) { found_fragment = true; params->int_params.push_back(value); if (child->blueprint_index != INVALID_BP_ID) blueprints->push_back(child->blueprint_index); auto ret = find(req_url, child, eptr - req_url.data(), params, blueprints); update_found(ret); params->int_params.pop_back(); blueprints->pop_back(); } } } else if (child->param == 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) { found_fragment = true; params->uint_params.push_back(value); if (child->blueprint_index != INVALID_BP_ID) blueprints->push_back(child->blueprint_index); auto ret = find(req_url, child, eptr - req_url.data(), params, blueprints); update_found(ret); params->uint_params.pop_back(); blueprints->pop_back(); } } } else if (child->param == 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) { found_fragment = true; params->double_params.push_back(value); if (child->blueprint_index != INVALID_BP_ID) blueprints->push_back(child->blueprint_index); auto ret = find(req_url, child, eptr - req_url.data(), params, blueprints); update_found(ret); params->double_params.pop_back(); blueprints->pop_back(); } } } else if (child->param == ParamType::STRING) { size_t epos = pos; for(; epos < req_url.size(); epos ++) { if (req_url[epos] == '/') break; } if (epos != pos) { found_fragment = true; params->string_params.push_back(req_url.substr(pos, epos-pos)); if (child->blueprint_index != INVALID_BP_ID) blueprints->push_back(child->blueprint_index); auto ret = find(req_url, child, epos, params, blueprints); update_found(ret); params->string_params.pop_back(); blueprints->pop_back(); } } else if (child->param == ParamType::PATH) { size_t epos = req_url.size(); if (epos != pos) { found_fragment = true; params->string_params.push_back(req_url.substr(pos, epos-pos)); if (child->blueprint_index != INVALID_BP_ID) blueprints->push_back(child->blueprint_index); auto ret = find(req_url, child, epos, params, blueprints); update_found(ret); params->string_params.pop_back(); blueprints->pop_back(); } } } else { const std::string& fragment = child->key; if (req_url.compare(pos, fragment.size(), fragment) == 0) { found_fragment = true; if (child->blueprint_index != INVALID_BP_ID) blueprints->push_back(child->blueprint_index); auto ret = find(req_url, child, pos + fragment.size(), params, blueprints); update_found(ret); blueprints->pop_back(); } } } if (!found_fragment) found_BP = std::move(*blueprints); return {found, found_BP, match_params}; //Called after all the recursions have been done } //This functions assumes any blueprint info passed is valid void add(const std::string& url, uint16_t rule_index, unsigned bp_prefix_length = 0, uint16_t blueprint_index = 0xFFFF) { Node* idx = &head_; bool has_blueprint = bp_prefix_length != 0 && blueprint_index != 0xFFFF; 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) { bool found = false; for (Node* child : idx->children) { if (child->param == x.type) { idx = child; i += x.name.size(); found = true; break; } } if (found) break; auto new_node_idx = new_node(idx); new_node_idx->param = x.type; idx = new_node_idx; i += x.name.size(); break; } } i --; } else { //This part assumes the tree is unoptimized (every node has a max 1 character key) bool piece_found = false; for (auto& child : idx->children) { if (child->key[0] == c) { idx = child; piece_found = true; break; } } if (!piece_found) { auto new_node_idx = new_node(idx); new_node_idx->key = c; //The assumption here is that you'd only need to add a blueprint index if the tree didn't have the BP prefix. if (has_blueprint && i == bp_prefix_length) new_node_idx->blueprint_index = blueprint_index; idx = new_node_idx; } } } //check if the last node already has a value (exact url already in Trie) if (idx->rule_index) throw std::runtime_error("handler already exists for " + url); idx->rule_index = rule_index; } size_t get_size() { return get_size(&head_); } size_t get_size(Node* node) { unsigned size = 5 ; //rule_index, blueprint_index, and param size += (node->key.size()); //each character in the key is 1 byte for (auto child: node->children) { size += get_size(child); } return size; } private: Node* new_node(Node* parent) { auto& children = parent->children; children.resize(children.size()+1); children[children.size()-1] = new Node(); return children[children.size()-1]; } static constexpr uint16_t INVALID_BP_ID{0xFFFF}; Node head_; }; /// A blueprint can be considered a smaller section of a Crow app, specifically where the router is conecerned. /// /// You can use blueprints to assign a common prefix to rules' prefix, set custom static and template folders, and set a custom catchall route. /// You can also assign nest blueprints for maximum Compartmentalization. class Blueprint { public: Blueprint(const std::string& prefix): prefix_(prefix){}; Blueprint(const std::string& prefix, const std::string& static_dir): prefix_(prefix), static_dir_(static_dir){}; Blueprint(const std::string& prefix, const std::string& static_dir, const std::string& templates_dir): prefix_(prefix), static_dir_(static_dir), templates_dir_(templates_dir){}; /* Blueprint(Blueprint& other) { prefix_ = std::move(other.prefix_); all_rules_ = std::move(other.all_rules_); } Blueprint(const Blueprint& other) { prefix_ = other.prefix_; all_rules_ = other.all_rules_; } */ Blueprint(Blueprint&& value) { *this = std::move(value); } Blueprint& operator = (const Blueprint& value) = delete; Blueprint& operator = (Blueprint&& value) noexcept { prefix_ = std::move(value.prefix_); all_rules_ = std::move(value.all_rules_); catchall_rule_ = std::move(value.catchall_rule_); return *this; } bool operator == (const Blueprint& value) { return value.prefix() == prefix_; } bool operator != (const Blueprint& value) { return value.prefix() != prefix_; } std::string prefix() const { return prefix_; } std::string static_dir() const { return static_dir_; } DynamicRule& new_rule_dynamic(std::string&& rule) { std::string new_rule = std::move(rule); new_rule = '/' + prefix_ + new_rule; auto ruleObject = new DynamicRule(new_rule); ruleObject->custom_templates_base = templates_dir_; all_rules_.emplace_back(ruleObject); return *ruleObject; } template typename black_magic::arguments::type::template rebind& new_rule_tagged(std::string&& rule) { std::string new_rule = std::move(rule); new_rule = '/' + prefix_ + new_rule; using RuleT = typename black_magic::arguments::type::template rebind; auto ruleObject = new RuleT(new_rule); ruleObject->custom_templates_base = templates_dir_; all_rules_.emplace_back(ruleObject); return *ruleObject; } void register_blueprint(Blueprint& blueprint) { if (blueprints_.empty() || std::find(blueprints_.begin(), blueprints_.end(), &blueprint) == blueprints_.end()) { apply_blueprint(blueprint); blueprints_.emplace_back(&blueprint); } else throw std::runtime_error("blueprint \"" + blueprint.prefix_ + "\" already exists in blueprint \"" + prefix_ + '\"'); } CatchallRule& catchall_rule() { return catchall_rule_; } private: void apply_blueprint(Blueprint& blueprint) { blueprint.prefix_ = prefix_ + '/' + blueprint.prefix_; blueprint.static_dir_ = static_dir_ + '/' + blueprint.static_dir_; blueprint.templates_dir_ = templates_dir_ + '/' + blueprint.templates_dir_; for (auto& rule : blueprint.all_rules_) { std::string new_rule = '/' + prefix_ + rule->rule_; rule->rule_ = new_rule; } for (Blueprint* bp_child : blueprint.blueprints_) { Blueprint& bp_ref = *bp_child; apply_blueprint(bp_ref); } } std::string prefix_; std::string static_dir_; std::string templates_dir_; std::vector> all_rules_; CatchallRule catchall_rule_; std::vector blueprints_; friend class Router; }; /// Handles matching requests to existing rules and upgrade requests. class Router { public: Router() { } DynamicRule& new_rule_dynamic(const std::string& rule) { auto ruleObject = new DynamicRule(rule); all_rules_.emplace_back(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); all_rules_.emplace_back(ruleObject); return *ruleObject; } CatchallRule& catchall_rule() { return catchall_rule_; } void internal_add_rule_object(const std::string& rule, BaseRule* ruleObject, const uint16_t& BP_index, std::vector& blueprints) { bool has_trailing_slash = false; std::string rule_without_trailing_slash; if (rule.size() > 1 && rule.back() == '/') { has_trailing_slash = true; rule_without_trailing_slash = rule; rule_without_trailing_slash.pop_back(); } ruleObject->foreach_method([&](int method) { per_methods_[method].rules.emplace_back(ruleObject); per_methods_[method].trie.add(rule, per_methods_[method].rules.size() - 1, BP_index != 0xFFFF ? blueprints[BP_index]->prefix().length() : 0, BP_index); // directory case: // request to '/about' url matches '/about/' rule if (has_trailing_slash) { per_methods_[method].trie.add(rule_without_trailing_slash, RULE_SPECIAL_REDIRECT_SLASH, BP_index != 0xFFFF ? blueprints_[BP_index]->prefix().length() : 0, BP_index); } }); } void register_blueprint(Blueprint& blueprint) { if (blueprints_.empty() || std::find(blueprints_.begin(), blueprints_.end(), &blueprint) == blueprints_.end()) { blueprints_.emplace_back(&blueprint); } else throw std::runtime_error("blueprint \"" + blueprint.prefix_ + "\" already exists in router"); } void get_recursive_child_methods(Blueprint* blueprint, std::vector& methods) { //we only need to deal with children if the blueprint has absolutely no methods (meaning its index won't be added to the trie) if (blueprint->static_dir_ == "" && blueprint->all_rules_.empty()) { for(Blueprint* bp : blueprint->blueprints_) { get_recursive_child_methods(bp, methods); } } else if (blueprint->static_dir_ != "") methods.emplace_back(HTTPMethod::GET); for (auto& rule: blueprint->all_rules_) { rule->foreach_method([&methods](unsigned method){ HTTPMethod method_final = static_cast(method); if (std::find(methods.begin(), methods.end(), method_final) == methods.end()) methods.emplace_back(method_final); }); } } void validate_bp(std::vector blueprints) { for (unsigned i = 0; i < blueprints.size(); i++) { Blueprint* blueprint = blueprints[i]; if (blueprint->static_dir_ == "" && blueprint->all_rules_.empty()) { std::vector methods; get_recursive_child_methods(blueprint, methods); for (HTTPMethod x : methods) { int i = static_cast(x); per_methods_[i].trie.add(blueprint->prefix(), 0, blueprint->prefix().length(), i); } } for (auto& rule: blueprint->all_rules_) { if (rule) { auto upgraded = rule->upgrade(); if (upgraded) rule = std::move(upgraded); rule->validate(); internal_add_rule_object(rule->rule(), rule.get(), i, blueprints); } } validate_bp(blueprint->blueprints_); } } void validate() { //Take all the routes from the registered blueprints and add them to `all_rules_` to be processed. validate_bp(blueprints_); for(auto& rule:all_rules_) { if (rule) { auto upgraded = rule->upgrade(); if (upgraded) rule = std::move(upgraded); rule->validate(); internal_add_rule_object(rule->rule(), rule.get(), 0xFFFF, blueprints_); } } for(auto& per_method:per_methods_) { per_method.trie.validate(); } } //TODO maybe add actual_method template void handle_upgrade(const request& req, response& res, Adaptor&& adaptor) { if (req.method >= HTTPMethod::InternalMethodCount) return; auto& per_method = per_methods_[static_cast(req.method)]; auto& rules = per_method.rules; unsigned rule_index = std::get<0>(per_method.trie.find(req.url)); if (!rule_index) { for (auto& per_method: per_methods_) { if (std::get<0>(per_method.trie.find(req.url))) { CROW_LOG_DEBUG << "Cannot match method " << req.url << " " << method_name(req.method); res = response(405); res.end(); return; } } CROW_LOG_INFO << "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; } CROW_LOG_DEBUG << "Matched rule (upgrade) '" << rules[rule_index]->rule_ << "' " << static_cast(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 get_found_bp(std::vector& bp_i, std::vector& blueprints, std::vector& found_bps, uint16_t index = 0) { if (index < bp_i.size()) { // This statement makes 3 assertions: // 1. The index is above 0. // 2. The index does not lie outside the given blueprint list. // 3. The next blueprint we're adding has a prefix that starts the same as the already added blueprint + a slash (the rest is irrelevant). // // This is done to prevent a blueprint that has a prefix of "bp_prefix2" to be assumed as a child of one that has "bp_prefix". // // If any of the assertions is untrue, we delete the last item added, and continue using the blueprint list of the blueprint found before, the topmost being the router's list if (index > 0 && bp_i[index] < blueprints.size() && blueprints[bp_i[index]]->prefix().substr(0,found_bps[index-1]->prefix().length()+1).compare(std::string(found_bps[index-1]->prefix()+'/')) == 0) { found_bps.push_back(blueprints[bp_i[index]]); get_found_bp(bp_i, found_bps.back()->blueprints_, found_bps, ++index); } else { if (!found_bps.empty()) found_bps.pop_back(); if (found_bps.empty()) { found_bps.push_back(blueprints_[bp_i[index]]); get_found_bp(bp_i, found_bps.back()->blueprints_, found_bps, ++index); } else { Blueprint* last_element = found_bps.back(); found_bps.push_back(last_element->blueprints_[bp_i[index]]); get_found_bp(bp_i, found_bps.back()->blueprints_, found_bps, ++index); } } } } void handle(const request& req, response& res) { HTTPMethod method_actual = req.method; if (req.method >= HTTPMethod::InternalMethodCount) return; else if (req.method == HTTPMethod::Head) { method_actual = HTTPMethod::Get; res.is_head_response = true; } else if (req.method == HTTPMethod::Options) { std::string allow = "OPTIONS, HEAD, "; if (req.url == "/*") { for(int i = 0; i < static_cast(HTTPMethod::InternalMethodCount); i ++) { if (!per_methods_[i].trie.is_empty()) { allow += method_name(static_cast(i)) + ", "; } } allow = allow.substr(0, allow.size()-2); res = response(204); res.set_header("Allow", allow); res.manual_length_header = true; res.end(); return; } else { for(int i = 0; i < static_cast(HTTPMethod::InternalMethodCount); i ++) { if (std::get<0>(per_methods_[i].trie.find(req.url))) { allow += method_name(static_cast(i)) + ", "; } } if (allow != "OPTIONS, HEAD, ") { allow = allow.substr(0, allow.size()-2); res = response(204); res.set_header("Allow", allow); res.manual_length_header = true; res.end(); return; } else { CROW_LOG_DEBUG << "Cannot match rules " << req.url; res = response(404); res.end(); return; } } } auto& per_method = per_methods_[static_cast(method_actual)]; auto& trie = per_method.trie; auto& rules = per_method.rules; auto found = trie.find(req.url); unsigned rule_index = std::get<0>(found); if (!rule_index) { for (auto& per_method: per_methods_) { if (std::get<0>(per_method.trie.find(req.url))) { CROW_LOG_DEBUG << "Cannot match method " << req.url << " " << method_name(method_actual); res = response(405); res.end(); return; } } std::vector bps_found; get_found_bp(std::get<1>(found), blueprints_, bps_found); bool no_bp_catchall = true; for (int i = bps_found.size()-1; i > 0; i--) { std::vector bpi = std::get<1>(found); if (bps_found[i]->catchall_rule().has_handler()) { no_bp_catchall = false; CROW_LOG_DEBUG << "Cannot match rules " << req.url << ". Redirecting to Blueprint \"" << bps_found[i]->prefix() << "\" Catchall rule"; bps_found[i]->catchall_rule().handler_(req, res); break; } } if (no_bp_catchall) { if (catchall_rule_.has_handler()) { CROW_LOG_DEBUG << "Cannot match rules " << req.url << ". Redirecting to global Catchall rule"; catchall_rule_.handler_(req, res); } else { 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; } CROW_LOG_DEBUG << "Matched rule '" << rules[rule_index]->rule_ << "' " << static_cast(req.method) << " / " << rules[rule_index]->get_methods(); // any uncaught exceptions become 500s try { rules[rule_index]->handle(req, res, std::get<2>(found)); } 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() { for(int i = 0; i < static_cast(HTTPMethod::InternalMethodCount); i ++) { CROW_LOG_DEBUG << method_name(static_cast(i)); per_methods_[i].trie.debug_print(); } } std::vector& blueprints() { return blueprints_; } private: CatchallRule catchall_rule_; struct PerMethod { std::vector rules; Trie trie; // rule index 0, 1 has special meaning; preallocate it to avoid duplication. PerMethod() : rules(2) {} }; std::array(HTTPMethod::InternalMethodCount)> per_methods_; std::vector> all_rules_; std::vector blueprints_; }; }