add support for handlers with request argument, add http method

This commit is contained in:
ipknHama 2014-04-20 17:45:10 +09:00
parent 5cdffdf9f7
commit 1173eba332
11 changed files with 200 additions and 50 deletions

View File

@ -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

View File

@ -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");
};

View File

@ -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();
}

View File

@ -7,6 +7,8 @@
#include <type_traits>
#include <thread>
#define FLASK_ENABLE_LOGGING
#include "http_server.h"
#include "utility.h"
#include "routing.h"

View File

@ -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";

View File

@ -6,6 +6,7 @@ namespace flask
{
struct request
{
HTTPMethod method;
std::string url;
std::unordered_map<std::string, std::string> headers;
std::string body;

View File

@ -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;

127
routing.h
View File

@ -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>
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 ||
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<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)
{
static_assert(black_magic::CallHelper<Func, black_magic::S<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");
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);
}

14
test.py
View File

@ -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()

View File

@ -129,7 +129,7 @@ TEST(RoutingTest)
});
app.validate();
app.debug_print();
//app.debug_print();
{
request req;

View File

@ -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