diff options
-rw-r--r-- | Makefile | 2 | ||||
-rw-r--r-- | common.h | 53 | ||||
-rw-r--r-- | example.cpp | 12 | ||||
-rw-r--r-- | flask.h | 2 | ||||
-rw-r--r-- | http_connection.h | 20 | ||||
-rw-r--r-- | http_request.h | 1 | ||||
-rw-r--r-- | parser.h | 5 | ||||
-rw-r--r-- | routing.h | 127 | ||||
-rw-r--r-- | test.py | 14 | ||||
-rw-r--r-- | unittest.cpp | 2 | ||||
-rw-r--r-- | utility.h | 12 |
11 files changed, 200 insertions, 50 deletions
@@ -11,7 +11,7 @@ runtest: example pkill example unittest: unittest.cpp routing.h - g++ -Wall -g -std=c++11 -o unittest unittest.cpp + g++ -Wall -g -std=c++11 -o unittest unittest.cpp http-parser/http_parser.c -pthread -lboost_system -lboost_thread -I http-parser/ ./unittest covtest: unittest.cpp routing.h utility.h flask.h http_server.h http_connection.h parser.h http_response.h common.h json.h @@ -1,9 +1,47 @@ #pragma once #include <string> +#include <stdexcept> +#include "utility.h" namespace flask { + enum class HTTPMethod + { + DELETE, + GET, + HEAD, + POST, + PUT, + CONNECT, + OPTIONS, + TRACE, + }; + + std::string method_name(HTTPMethod method) + { + switch(method) + { + case HTTPMethod::DELETE: + return "DELETE"; + case HTTPMethod::GET: + return "GET"; + case HTTPMethod::HEAD: + return "HEAD"; + case HTTPMethod::POST: + return "POST"; + case HTTPMethod::PUT: + return "PUT"; + case HTTPMethod::CONNECT: + return "CONNECT"; + case HTTPMethod::OPTIONS: + return "OPTIONS"; + case HTTPMethod::TRACE: + return "TRACE"; + } + return "invalid"; + } + enum class ParamType { INT, @@ -68,3 +106,18 @@ namespace flask return string_params[index]; } } + +constexpr flask::HTTPMethod operator "" _method(const char* str, size_t len) +{ + return + flask::black_magic::is_equ_p(str, "GET", 3) ? flask::HTTPMethod::GET : + flask::black_magic::is_equ_p(str, "DELETE", 6) ? flask::HTTPMethod::DELETE : + flask::black_magic::is_equ_p(str, "HEAD", 4) ? flask::HTTPMethod::HEAD : + flask::black_magic::is_equ_p(str, "POST", 4) ? flask::HTTPMethod::POST : + flask::black_magic::is_equ_p(str, "PUT", 3) ? flask::HTTPMethod::PUT : + flask::black_magic::is_equ_p(str, "OPTIONS", 7) ? flask::HTTPMethod::OPTIONS : + flask::black_magic::is_equ_p(str, "CONNECT", 7) ? flask::HTTPMethod::CONNECT : + flask::black_magic::is_equ_p(str, "TRACE", 5) ? flask::HTTPMethod::TRACE : + throw std::runtime_error("invalid http method"); +}; + diff --git a/example.cpp b/example.cpp index 16116a8..96ea3d4 100644 --- a/example.cpp +++ b/example.cpp @@ -13,6 +13,17 @@ int main() return "Hello World!"; }); + FLASK_ROUTE(app, "/add_json") + ([](const flask::request& req){ + auto x = flask::json::load(req.body); + if (!x) + return flask::response(400); + int sum = x["a"].i()+x["b"].i(); + std::ostringstream os; + os << sum; + return flask::response{os.str()}; + }); + FLASK_ROUTE(app, "/json") ([]{ flask::json::wvalue x; @@ -41,6 +52,5 @@ int main() //}); app.port(18080) - .multithreaded() .run(); } @@ -7,6 +7,8 @@ #include <type_traits> #include <thread> +#define FLASK_ENABLE_LOGGING + #include "http_server.h" #include "utility.h" #include "routing.h" diff --git a/http_connection.h b/http_connection.h index 7c4da26..6338c6f 100644 --- a/http_connection.h +++ b/http_connection.h @@ -53,8 +53,28 @@ namespace flask }; request req = parser_.to_request(); + if (parser_.http_major == 1 && parser_.http_minor == 0) + { + // HTTP/1.0 + if (!(req.headers.count("connection") && boost::iequals(req.headers["connection"],"Keep-Alive"))) + close_connection_ = true; + } + else + { + // HTTP/1.1 + if (req.headers.count("connection") && req.headers["connection"] == "close") + close_connection_ = true; + } + res = handler_->handle(req); +#ifdef FLASK_ENABLE_LOGGING + std::cerr << "HTTP/" << parser_.http_major << "." << parser_.http_minor << ' '; + std::cerr << method_name(req.method); + std::cerr << " " << res.code << std::endl; + std::cerr << "res body: " << res.body << std::endl; +#endif + static std::string seperator = ": "; static std::string crlf = "\r\n"; diff --git a/http_request.h b/http_request.h index c08d51f..9190dac 100644 --- a/http_request.h +++ b/http_request.h @@ -6,6 +6,7 @@ namespace flask { struct request { + HTTPMethod method; std::string url; std::unordered_map<std::string, std::string> headers; std::string body; @@ -2,6 +2,7 @@ #include <string> #include <unordered_map> +#include <boost/algorithm/string.hpp> #include "http_request.h" @@ -35,6 +36,7 @@ namespace flask case 0: if (!self->header_value.empty()) { + boost::algorithm::to_lower(self->header_field); self->headers.emplace(std::move(self->header_field), std::move(self->header_value)); } self->header_field.assign(at, at+length); @@ -66,6 +68,7 @@ namespace flask HTTPParser* self = static_cast<HTTPParser*>(self_); if (!self->header_field.empty()) { + boost::algorithm::to_lower(self->header_field); self->headers.emplace(std::move(self->header_field), std::move(self->header_value)); } return 0; @@ -127,7 +130,7 @@ namespace flask request to_request() { - return request{std::move(url), std::move(headers), std::move(body)}; + return request{(HTTPMethod)method, std::move(url), std::move(headers), std::move(body)}; } std::string url; @@ -20,7 +20,7 @@ namespace flask class BaseRule { public: - BaseRule(std::string rule) + BaseRule(std::string rule) noexcept : rule_(std::move(rule)) { } @@ -29,7 +29,7 @@ namespace flask { } - BaseRule& name(std::string name) + BaseRule& name(std::string name) noexcept { name_ = std::move(name); return *this; @@ -42,17 +42,19 @@ namespace flask protected: std::string rule_; std::string name_; + + friend class Router; }; class Rule : public BaseRule { public: - Rule(std::string rule) + Rule(std::string rule) noexcept : BaseRule(std::move(rule)) { } - Rule& name(std::string name) + Rule& name(std::string name) noexcept { name_ = std::move(name); return *this; @@ -73,12 +75,8 @@ 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()); - }; + this->operator()<Func>(f); } void validate() @@ -102,62 +100,82 @@ namespace flask class TaggedRule : public BaseRule { private: - template <typename F, int NInt, int NUint, int NDouble, int NString, typename S1, typename S2> struct call + template <typename H1, typename H2> + struct call_params + { + H1& handler; + H2& handler_with_req; + const routing_params& params; + const request& req; + }; + + 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) + response operator()(F cparams) { 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); + black_magic::S<Args1...>, pushed>()(cparams); } }; 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<uint64_t, Args1...>, black_magic::S<Args2...>> { - response operator()(F& handler, const routing_params& params) + response operator()(F cparams) { using pushed = typename black_magic::S<Args2...>::template push_back<call_pair<uint64_t, NUint>>; return call<F, NInt, NUint+1, NDouble, NString, - black_magic::S<Args1...>, pushed>()(handler, params); + black_magic::S<Args1...>, pushed>()(cparams); } }; 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<double, Args1...>, black_magic::S<Args2...>> { - response operator()(F& handler, const routing_params& params) + response operator()(F cparams) { using pushed = typename black_magic::S<Args2...>::template push_back<call_pair<double, NDouble>>; return call<F, NInt, NUint, NDouble+1, NString, - black_magic::S<Args1...>, pushed>()(handler, params); + black_magic::S<Args1...>, pushed>()(cparams); } }; 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<std::string, Args1...>, black_magic::S<Args2...>> { - response operator()(F& handler, const routing_params& params) + response operator()(F cparams) { using pushed = typename black_magic::S<Args2...>::template push_back<call_pair<std::string, NString>>; return call<F, NInt, NUint, NDouble, NString+1, - black_magic::S<Args1...>, pushed>()(handler, params); + black_magic::S<Args1...>, pushed>()(cparams); } }; 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) + response operator()(F cparams) { - return handler( - params.get<typename Args1::type>(Args1::pos)... - ); + if (cparams.handler) + return cparams.handler( + cparams.params.template get<typename Args1::type>(Args1::pos)... + ); + if (cparams.handler_with_req) + return cparams.handler_with_req( + cparams.req, + cparams.params.template get<typename Args1::type>(Args1::pos)... + ); +#ifdef FLASK_ENABLE_LOGGING + std::cerr << "ERROR cannot find handler" << std::endl; +#endif + return response(500); } }; public: @@ -166,7 +184,7 @@ namespace flask { } - TaggedRule<Args...>& name(std::string name) + TaggedRule<Args...>& name(std::string name) noexcept { name_ = std::move(name); return *this; @@ -177,39 +195,67 @@ namespace flask } template <typename Func> - void operator()(Func&& f) + typename std::enable_if<black_magic::CallHelper<Func, black_magic::S<Args...>>::value, void>::type + operator()(Func&& f) { - static_assert(black_magic::CallHelper<Func, black_magic::S<Args...>>::value, + static_assert(black_magic::CallHelper<Func, black_magic::S<Args...>>::value || + black_magic::CallHelper<Func, black_magic::S<flask::request, Args...>>::value + , "Handler type is mismatched with URL paramters"); static_assert(!std::is_same<void, decltype(f(std::declval<Args>()...))>::value, - "Handler function cannot have void return type; valid return types: string, int, flask::resposne"); - handler_ = [f = std::move(f)](Args ... args){ - return response(f(args...)); - }; + "Handler function cannot have void return type; valid return types: string, int, flask::resposne, flask::json::wvalue"); + + handler_ = [f = std::move(f)](Args ... args){ + return response(f(args...)); + }; } template <typename Func> - void operator()(std::string name, Func&& f) + typename std::enable_if<!black_magic::CallHelper<Func, black_magic::S<Args...>>::value, void>::type + operator()(Func&& f) { - static_assert(black_magic::CallHelper<Func, black_magic::S<Args...>>::value, + static_assert(black_magic::CallHelper<Func, black_magic::S<Args...>>::value || + black_magic::CallHelper<Func, black_magic::S<flask::request, Args...>>::value + , "Handler type is mismatched with URL paramters"); - static_assert(!std::is_same<void, decltype(f(std::declval<Args>()...))>::value, - "Handler function cannot have void return type; valid return types: string, int, flask::resposne"); + static_assert(!std::is_same<void, decltype(f(std::declval<flask::request>(), std::declval<Args>()...))>::value, + "Handler function cannot have void return type; valid return types: string, int, flask::resposne, flask::json::wvalue"); + + handler_with_req_ = [f = std::move(f)](const flask::request& request, Args ... args){ + return response(f(request, args...)); + }; + } + + template <typename Func> + void operator()(std::string name, Func&& f) + { name_ = std::move(name); - handler_ = [f = std::move(f)](Args ... args){ - return response(f(args...)); - }; + (*this).operator()<Func>(std::forward(f)); } - response handle(const request&, const routing_params& params) + response handle(const request& req, const routing_params& params) { //return handler_(); - return call<decltype(handler_), 0, 0, 0, 0, black_magic::S<Args...>, black_magic::S<>>()(handler_, params); + return + call< + call_params< + decltype(handler_), + decltype(handler_with_req_)>, + 0, 0, 0, 0, + black_magic::S<Args...>, + black_magic::S<> + >()( + call_params< + decltype(handler_), + decltype(handler_with_req_)> + {handler_, handler_with_req_, params, req} + ); //return response(500); } private: std::function<response(Args...)> handler_; + std::function<response(flask::request, Args...)> handler_with_req_; template <typename T, int Pos> struct call_pair @@ -599,7 +645,10 @@ public: if (rule_index >= rules_.size()) throw std::runtime_error("Trie internal structure corrupted!"); - +#ifdef FLASK_ENABLE_LOGGING + std::cerr << req.url << std::endl; + std::cerr << rules_[rule_index]->rule_ << std::endl; +#endif return rules_[rule_index]->handle(req, found.second); } @@ -1,8 +1,8 @@ import urllib -assert "Hello World!" == urllib.urlopen('http://localhost:8080').read() -assert "About Flask example." == urllib.urlopen('http://localhost:8080/about').read() -assert 404 == urllib.urlopen('http://localhost:8080/list').getcode() -assert "3 bottles of beer!" == urllib.urlopen('http://localhost:8080/hello/3').read() -assert "100 bottles of beer!" == urllib.urlopen('http://localhost:8080/hello/100').read() -assert "" == urllib.urlopen('http://localhost:8080/hello/500').read() -assert 400 == urllib.urlopen('http://localhost:8080/hello/500').getcode() +assert "Hello World!" == urllib.urlopen('http://localhost:18080').read() +assert "About Flask example." == urllib.urlopen('http://localhost:18080/about').read() +assert 404 == urllib.urlopen('http://localhost:18080/list').getcode() +assert "3 bottles of beer!" == urllib.urlopen('http://localhost:18080/hello/3').read() +assert "100 bottles of beer!" == urllib.urlopen('http://localhost:18080/hello/100').read() +assert 400 == urllib.urlopen('http://localhost:18080/hello/500').getcode() +assert "3" == urllib.urlopen('http://localhost:18080/add_json', data='{"a":1,"b":2}').read() diff --git a/unittest.cpp b/unittest.cpp index 5f90551..78cb3a2 100644 --- a/unittest.cpp +++ b/unittest.cpp @@ -129,7 +129,7 @@ TEST(RoutingTest) }); app.validate(); - app.debug_print(); + //app.debug_print(); { request req; @@ -62,6 +62,18 @@ namespace flask is_valid(s, i+1, f); } + constexpr bool is_equ_p(const char* a, const char* b, unsigned n) + { + return + *a == 0 || *b == 0 + ? false : + n == 0 + ? true : + *a != *b + ? false : + is_equ_p(a+1, b+1, n-1); + } + constexpr bool is_equ_n(const_str a, unsigned ai, const_str b, unsigned bi, unsigned n) { return |