diff options
-rw-r--r-- | example.cpp | 20 | ||||
-rw-r--r-- | flask.h | 10 | ||||
-rw-r--r-- | http_response.h | 4 | ||||
-rw-r--r-- | routing.h | 205 | ||||
-rw-r--r-- | utility.h | 155 |
5 files changed, 358 insertions, 36 deletions
diff --git a/example.cpp b/example.cpp index c518dc4..9ecf0af 100644 --- a/example.cpp +++ b/example.cpp @@ -1,6 +1,6 @@ #include "flask.h" -#include <iostream> +#include <sstream> int main() { @@ -13,13 +13,23 @@ int main() }); app.route("/about") - ([]{ + ([](){ return "About Flask example."; }); - //app.route("/hello/<int>"); - //([]{ - //return "About Flask example."; + FLASK_ROUTE(app,"/hello/<int>") + ([](int count){ + if (count > 100) + return flask::response(400); + std::ostringstream os; + os << count << " bottles of beer!"; + return flask::response(os.str()); + }); + + // Compile error with message "Handler type is mismatched with URL paramters" + //FLASK_ROUTE(app,"/another/<int>") + //([](int a, int b){ + //return flask::response(500); //}); app.port(8080) @@ -7,11 +7,14 @@ #include <type_traits> #include "http_server.h" +#include "utility.h" #include "routing.h" // TEST #include <iostream> +#define FLASK_ROUTE(app, url) app.route<flask::black_magic::get_parameter_tag(url)>(url) + namespace flask { class Flask @@ -26,6 +29,13 @@ namespace flask return router_.handle(req); } + template <uint64_t Tag> + auto route(std::string&& rule) + -> typename std::result_of<decltype(&Router::new_rule_tagged<Tag>)(Router, std::string&&)>::type + { + return router_.new_rule_tagged<Tag>(std::move(rule)); + } + auto route(std::string&& rule) -> typename std::result_of<decltype(&Router::new_rule)(Router, std::string&&)>::type { diff --git a/http_response.h b/http_response.h index eb7926f..7b862c4 100644 --- a/http_response.h +++ b/http_response.h @@ -29,11 +29,11 @@ namespace flask struct response { std::string body; - int status = 200; + int status{200}; std::unordered_map<std::string, std::string> headers; response() {} explicit response(int status) : status(status) {} response(std::string body) : body(std::move(body)) {} - response(std::string body, int status) : body(std::move(body)), status(status) {} + response(int status, std::string body) : body(std::move(body)), status(status) {} }; } @@ -5,6 +5,8 @@ #include <string> #include <tuple> #include <unordered_map> +#include <memory> +#include <boost/lexical_cast.hpp> #include "http_response.h" @@ -13,17 +15,56 @@ namespace flask { - class Rule + class BaseRule { public: - explicit Rule(std::string rule) + BaseRule(std::string rule) : rule_(std::move(rule)) { } + + virtual ~BaseRule() + { + } + + BaseRule& name(std::string name) + { + name_ = std::move(name); + return *this; + } + + virtual void validate() + { + } + virtual response handle(const request&) + { + return response(400); + } + + protected: + std::string rule_; + std::string name_; + }; + + class Rule : public BaseRule + { + public: + Rule(std::string rule) + : BaseRule(std::move(rule)) + { + } + Rule& name(std::string name) + { + name_ = std::move(name); + return *this; + } + template <typename Func> void operator()(Func&& f) { + static_assert(black_magic::CallHelper<Func, black_magic::S<>>::value, + "Handler type is mismatched with URL paramters"); handler_ = [f = std::move(f)]{ return response(f()); }; @@ -32,41 +73,75 @@ namespace flask template <typename Func> void operator()(std::string name, Func&& f) { + static_assert(black_magic::CallHelper<Func, black_magic::S<>>::value, + "Handler type is mismatched with URL paramters"); name_ = std::move(name); handler_ = [f = std::move(f)]{ return response(f()); }; } - bool match(const request& req) + void validate() { - // FIXME need url parsing - return req.url == rule_; + if (!handler_) + { + throw std::runtime_error(name_ + (!name_.empty() ? ": " : "") + "no handler for url " + rule_); + } } - Rule& name(std::string name) + response handle(const request&) + { + return handler_(); + } + + protected: + std::function<response()> handler_; + }; + + template <typename ... Args> + class TaggedRule : public BaseRule + { + public: + TaggedRule(std::string rule) + : BaseRule(std::move(rule)) + { + } + + TaggedRule<Args...>& name(std::string name) { name_ = std::move(name); return *this; } - void validate() + template <typename Func> + void operator()(Func&& f) { - if (!handler_) - { - throw std::runtime_error(name_ + (!name_.empty() ? ": " : "") + "no handler for url " + rule_); - } + static_assert(black_magic::CallHelper<Func, black_magic::S<Args...>>::value, + "Handler type is mismatched with URL paramters"); + handler_ = [f = std::move(f)](Args ... args){ + return response(f(args...)); + }; + } + + template <typename Func> + void operator()(std::string name, Func&& f) + { + static_assert(black_magic::CallHelper<Func, black_magic::S<Args...>>::value, + "Handler type is mismatched with URL paramters"); + name_ = std::move(name); + handler_ = [f = std::move(f)](Args ... args){ + return response(f(args...)); + }; } response handle(const request&) { - return handler_(); + //return handler_(); + return response(500); } private: - std::string rule_; - std::string name_; - std::function<response()> handler_; + std::function<response(Args...)> handler_; }; constexpr const size_t INVALID_RULE_INDEX{static_cast<size_t>(-1)}; @@ -77,14 +152,41 @@ namespace flask { 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}; + 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; + } }; Trie() : nodes_(1) { } +private: + void optimizeNode(Node* node) + { + for(auto& kv : node->children) + { + Node* child = &nodes_[kv.second]; + optimizeNode(child); + } + } + +public: void optimize() { + optimizeNode(head()); } size_t find(const request& req, const Node* node = nullptr, size_t pos = 0) const @@ -116,16 +218,54 @@ namespace flask void add(const std::string& url, size_t rule_index) { - size_t idx = 0; - for(char c:url) + size_t idx{0}; + + for(size_t i = 0; i < url.size(); i ++) { - std::string piece(&c, 1); - //std::string piece("/"); - if (!nodes_[idx].children.count(piece)) + char c = url[i]; + if (c == '<') { - nodes_[idx].children.emplace(piece, new_node()); + 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) + { + idx = nodes_[idx].child_for_float = new_node(); + i += 7; + } + else if (url.compare(i, 5, "<str>") == 0) + { + idx = nodes_[idx].child_for_str = new_node(); + i += 5; + } + else if (url.compare(i, 6, "<path>") == 0) + { + idx = nodes_[idx].child_for_path = new_node(); + i += 6; + } + else + { + throw std::runtime_error("Invalid url: " + url + " (" + boost::lexical_cast<std::string>(i) + ")"); + } + i --; + } + else + { + std::string piece(&c, 1); + if (!nodes_[idx].children.count(piece)) + { + nodes_[idx].children.emplace(piece, new_node()); + } + idx = nodes_[idx].children[piece]; } - idx = nodes_[idx].children[piece]; } if (nodes_[idx].rule_index != INVALID_RULE_INDEX) throw std::runtime_error("handler already exists for " + url); @@ -154,11 +294,22 @@ namespace flask class Router { public: + 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); + rules_.emplace_back(ruleObject); + trie_.add(rule, rules_.size() - 1); + return *ruleObject; + } + Rule& new_rule(const std::string& rule) { - rules_.emplace_back(rule); + Rule* r(new Rule(rule)); + rules_.emplace_back(r); trie_.add(rule, rules_.size() - 1); - return rules_.back(); + return *r; } void validate() @@ -166,7 +317,7 @@ namespace flask trie_.optimize(); for(auto& rule:rules_) { - rule.validate(); + rule->validate(); } } @@ -180,10 +331,10 @@ namespace flask 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); } private: - std::vector<Rule> rules_; + std::vector<std::unique_ptr<BaseRule>> rules_; Trie trie_; }; } @@ -1,5 +1,8 @@ #pragma once +#include <stdint.h> +#include <stdexcept> + namespace flask { namespace black_magic @@ -13,7 +16,6 @@ namespace flask return i >= len ? throw OutOfRange(i, len) : i; } - // from http://akrzemi1.wordpress.com/2011/05/11/parsing-strings-at-compile-time-part-i/ class const_str { const char * const begin_; @@ -43,9 +45,158 @@ namespace flask return s[p] == '>' ? p : find_closing_tag(s, p+1); } - constexpr int count(const_str s, int i=0) + constexpr bool is_valid(const_str s, unsigned i = 0, int f = 0) + { + return + i == s.size() + ? f == 0 : + f < 0 || f >= 2 + ? false : + s[i] == '<' + ? is_valid(s, i+1, f+1) : + s[i] == '>' + ? is_valid(s, i+1, f-1) : + is_valid(s, i+1, f); + } + + constexpr int count(const_str s, unsigned i=0) { return i == s.size() ? 0 : s[i] == '<' ? 1+count(s,i+1) : count(s,i+1); } + + constexpr bool is_equ_n(const_str a, unsigned ai, const_str b, unsigned bi, unsigned n) + { + return + ai + n > a.size() || bi + n > b.size() + ? false : + n == 0 + ? true : + a[ai] != b[bi] + ? false : + is_equ_n(a,ai+1,b,bi+1,n-1); + } + + constexpr bool is_int(const_str s, unsigned i) + { + return is_equ_n(s, i, "<int>", 0, 5); + } + + constexpr bool is_uint(const_str s, unsigned i) + { + return is_equ_n(s, i, "<uint>", 0, 6); + } + + constexpr bool is_float(const_str s, unsigned i) + { + return is_equ_n(s, i, "<float>", 0, 7) || + is_equ_n(s, i, "<double>", 0, 8); + } + + constexpr bool is_str(const_str s, unsigned i) + { + return is_equ_n(s, i, "<str>", 0, 5); + } + + constexpr bool is_path(const_str s, unsigned i) + { + return is_equ_n(s, i, "<path>", 0, 6); + } + + constexpr uint64_t get_parameter_tag(const_str s, unsigned p = 0) + { + return + p == s.size() + ? 0 : + s[p] == '<' ? ( + is_int(s, p) + ? get_parameter_tag(s, find_closing_tag(s, p)) * 6 + 1 : + is_uint(s, p) + ? get_parameter_tag(s, find_closing_tag(s, p)) * 6 + 2 : + is_float(s, p) + ? get_parameter_tag(s, find_closing_tag(s, p)) * 6 + 3 : + is_str(s, p) + ? get_parameter_tag(s, find_closing_tag(s, p)) * 6 + 4 : + is_path(s, p) + ? get_parameter_tag(s, find_closing_tag(s, p)) * 6 + 5 : + throw std::runtime_error("invalid parameter type") + ) : + get_parameter_tag(s, p+1); + } + + template <typename ... T> + struct S + { + template <typename U> + using push = S<U, T...>; + template <template<typename ... Args> class U> + using rebind = U<T...>; + }; +template <typename F, typename Set> + struct CallHelper; + template <typename F, typename ...Args> + struct CallHelper<F, S<Args...>> + { + template <typename F1, typename ...Args1, typename = + decltype(std::declval<F1>()(std::declval<Args1>()...)) + > + static char __test(int); + + template <typename ...> + static int __test(...); + + static constexpr bool value = sizeof(__test<F, Args...>(0)) == sizeof(char); + }; + + + template <int N> + struct single_tag_to_type + { + }; + + template <> + struct single_tag_to_type<1> + { + using type = int64_t; + }; + + template <> + struct single_tag_to_type<2> + { + using type = uint64_t; + }; + + template <> + struct single_tag_to_type<3> + { + using type = double; + }; + + template <> + struct single_tag_to_type<4> + { + using type = std::string; + }; + + template <> + struct single_tag_to_type<5> + { + using type = std::string; + }; + + + template <uint64_t Tag> + struct arguments + { + using subarguments = typename arguments<Tag/6>::type; + using type = + typename subarguments::template push<typename single_tag_to_type<Tag%6>::type>; + }; + + template <> + struct arguments<0> + { + using type = S<>; + }; + } } |