diff options
Diffstat (limited to 'routing.h')
-rw-r--r-- | routing.h | 197 |
1 files changed, 134 insertions, 63 deletions
@@ -2,12 +2,12 @@ #include <cstdint> #include <utility> -#include <string> #include <tuple> #include <unordered_map> #include <memory> #include <boost/lexical_cast.hpp> +#include "common.h" #include "http_response.h" //TEST @@ -36,7 +36,8 @@ namespace flask virtual void validate() { } - virtual response handle(const request&) + + virtual response handle(const request&, const routing_params&) { return response(400); } @@ -89,7 +90,7 @@ namespace flask } } - response handle(const request&) + response handle(const request&, const routing_params&) { return handler_(); } @@ -101,6 +102,33 @@ namespace flask template <typename ... Args> class TaggedRule : public BaseRule { + private: + 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...>> + { + response operator()(F& handler, const routing_params& params) + { + using pushed = typename black_magic::S<Args2...>::template push_back<call_pair<int64_t, NInt>>; + return call<F, NInt+1, NUint, NDouble, NString, + black_magic::S<Args1...>, pushed>()(handler, params); + } + }; + + 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...>> + { + response operator()(F& handler, const routing_params& params) + { + return handler( + params.get<typename Args1::type>(Args1::pos)... + ); + //return response(500); + } + }; public: TaggedRule(std::string rule) : BaseRule(std::move(rule)) @@ -134,38 +162,41 @@ namespace flask }; } - response handle(const request&) + response handle(const request&, const routing_params& params) { //return handler_(); - return response(500); + return call<decltype(handler_), 0, 0, 0, 0, black_magic::S<Args...>, black_magic::S<>>()(handler_, params); + //return response(500); } private: std::function<response(Args...)> handler_; + + template <typename T, int Pos> + struct call_pair + { + using type = T; + static const int pos = Pos; + }; + }; - constexpr const size_t INVALID_RULE_INDEX{static_cast<size_t>(-1)}; class Trie { public: struct Node { - std::unordered_map<std::string, size_t> children; - size_t rule_index{INVALID_RULE_INDEX}; - size_t child_for_int{INVALID_RULE_INDEX}; - size_t child_for_uint{INVALID_RULE_INDEX}; - size_t child_for_float{INVALID_RULE_INDEX}; - size_t child_for_str{INVALID_RULE_INDEX}; - size_t child_for_path{INVALID_RULE_INDEX}; + std::unordered_map<std::string, unsigned> children; + unsigned rule_index{}; + std::array<unsigned, (int)ParamType::MAX> param_childrens{}; bool IsSimpleNode() const { return - rule_index == INVALID_RULE_INDEX && - child_for_int == INVALID_RULE_INDEX && - child_for_uint == INVALID_RULE_INDEX && - child_for_float == INVALID_RULE_INDEX && - child_for_str == INVALID_RULE_INDEX && - child_for_path == INVALID_RULE_INDEX; + !rule_index && + std::all_of( + std::begin(param_childrens), + std::end(param_childrens), + [](unsigned x){ return !x; }); } }; @@ -183,20 +214,59 @@ private: } } -public: void optimize() { optimizeNode(head()); } - size_t find(const request& req, const Node* node = nullptr, size_t pos = 0) const +public: + void validate() + { + if (!head()->IsSimpleNode()) + throw std::runtime_error("Internal error: Trie header should be simple!"); + optimize(); + } + + std::pair<unsigned, routing_params> find(const request& req, const Node* node = nullptr, unsigned pos = 0, routing_params* params = nullptr) const { - size_t found = INVALID_RULE_INDEX; + 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; + 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 = 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(); + } + } + } for(auto& kv : node->children) { @@ -205,55 +275,53 @@ public: if (req.url.compare(pos, fragment.size(), fragment) == 0) { - size_t ret = find(req, child, pos + fragment.size()); - if (ret != INVALID_RULE_INDEX && (found == INVALID_RULE_INDEX || found > ret)) - { - found = ret; - } + auto ret = find(req, child, pos + fragment.size(), params); + update_found(ret); } } - return found; + return {found, std::move(match_params)}; } - void add(const std::string& url, size_t rule_index) + void add(const std::string& url, unsigned rule_index) { - size_t idx{0}; + unsigned idx{0}; - for(size_t i = 0; i < url.size(); i ++) + for(unsigned i = 0; i < url.size(); i ++) { char c = url[i]; if (c == '<') { - if (url.compare(i, 5, "<int>") == 0) - { - idx = nodes_[idx].child_for_int = new_node(); - i += 5; - } - else if (url.compare(i, 6, "<uint>") == 0) - { - idx = nodes_[idx].child_for_uint = new_node(); - i += 6; - } - else if (url.compare(i, 7, "<float>") == 0 || - url.compare(i, 8, "<double>") == 0) + bool found = false; + + static struct ParamTraits { - idx = nodes_[idx].child_for_float = new_node(); - i += 7; - } - else if (url.compare(i, 5, "<str>") == 0) + ParamType type; + std::string name; + } paramTraits[] = { - idx = nodes_[idx].child_for_str = new_node(); - i += 5; - } - else if (url.compare(i, 6, "<path>") == 0) + { ParamType::INT, "<int>" }, + { ParamType::UINT, "<uint>" }, + { ParamType::DOUBLE, "<float>" }, + { ParamType::DOUBLE, "<double>" }, + { ParamType::STRING, "<str>" }, + { ParamType::PATH, "<path>" }, + }; + + for(auto it = begin(paramTraits); it != end(paramTraits); ++it) { - idx = nodes_[idx].child_for_path = new_node(); - i += 6; + if (url.compare(i, it->name.size(), it->name) == 0) + { + idx = nodes_[idx].param_childrens[(int)it->type] = new_node(); + i += it->name.size(); + found = true; + break; + } } - else + + if (!found) { - throw std::runtime_error("Invalid url: " + url + " (" + boost::lexical_cast<std::string>(i) + ")"); + throw std::runtime_error("Invalid parameter type: " + url + " (" + boost::lexical_cast<std::string>(i) + ")"); } i --; } @@ -267,7 +335,7 @@ public: idx = nodes_[idx].children[piece]; } } - if (nodes_[idx].rule_index != INVALID_RULE_INDEX) + if (nodes_[idx].rule_index) throw std::runtime_error("handler already exists for " + url); nodes_[idx].rule_index = rule_index; } @@ -282,7 +350,7 @@ public: return &nodes_.front(); } - size_t new_node() + unsigned new_node() { nodes_.resize(nodes_.size()+1); return nodes_.size() - 1; @@ -294,6 +362,7 @@ public: class Router { public: + Router() : rules_(1) {} template <uint64_t N> typename black_magic::arguments<N>::type::template rebind<TaggedRule>& new_rule_tagged(const std::string& rule) { @@ -314,24 +383,26 @@ public: void validate() { - trie_.optimize(); + trie_.validate(); for(auto& rule:rules_) { - rule->validate(); + if (rule) + rule->validate(); } } response handle(const request& req) { - size_t rule_index = trie_.find(req); + auto found = trie_.find(req); + unsigned rule_index = found.first; - if (rule_index == INVALID_RULE_INDEX) + if (!rule_index) return response(404); if (rule_index >= rules_.size()) throw std::runtime_error("Trie internal structure corrupted!"); - return rules_[rule_index]->handle(req); + return rules_[rule_index]->handle(req, found.second); } private: std::vector<std::unique_ptr<BaseRule>> rules_; |