mirror of
https://github.com/CrowCpp/Crow.git
synced 2024-06-07 21:10:44 +00:00
add support for handlers with request argument, add http method
This commit is contained in:
parent
5cdffdf9f7
commit
1173eba332
2
Makefile
2
Makefile
@ -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
|
||||
|
53
common.h
53
common.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");
|
||||
};
|
||||
|
||||
|
12
example.cpp
12
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();
|
||||
}
|
||||
|
2
flask.h
2
flask.h
@ -7,6 +7,8 @@
|
||||
#include <type_traits>
|
||||
#include <thread>
|
||||
|
||||
#define FLASK_ENABLE_LOGGING
|
||||
|
||||
#include "http_server.h"
|
||||
#include "utility.h"
|
||||
#include "routing.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";
|
||||
|
||||
|
@ -6,6 +6,7 @@ namespace flask
|
||||
{
|
||||
struct request
|
||||
{
|
||||
HTTPMethod method;
|
||||
std::string url;
|
||||
std::unordered_map<std::string, std::string> headers;
|
||||
std::string body;
|
||||
|
5
parser.h
5
parser.h
@ -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
127
routing.h
@ -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
14
test.py
@ -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()
|
||||
|
@ -129,7 +129,7 @@ TEST(RoutingTest)
|
||||
});
|
||||
|
||||
app.validate();
|
||||
app.debug_print();
|
||||
//app.debug_print();
|
||||
|
||||
{
|
||||
request req;
|
||||
|
12
utility.h
12
utility.h
@ -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
|
||||
|
Loading…
Reference in New Issue
Block a user