basic middleware test: before_handler

This commit is contained in:
ipknHama 2014-09-07 04:30:53 +09:00
parent c89cafa820
commit 2748e35430
8 changed files with 174 additions and 25 deletions

View File

@ -13,12 +13,9 @@
#include "http_server.h"
#include "utility.h"
#include "routing.h"
#include "middleware_impl.h"
#include "middleware_context.h"
#include "http_request.h"
// TEST
#include <iostream>
#define CROW_ROUTE(app, url) app.route<crow::black_magic::get_parameter_tag(url)>(url)
namespace crow
@ -28,13 +25,14 @@ namespace crow
{
public:
using self_t = Crow;
using server_t = Server<Crow, Middlewares...>;
Crow()
{
}
void handle(const request& req, response& res)
{
return router_.handle(req, res);
router_.handle(req, res);
}
template <uint64_t Tag>
@ -71,7 +69,7 @@ namespace crow
void run()
{
validate();
Server<self_t> server(this, port_, concurrency_);
server_t server(this, port_, concurrency_);
server.run();
}
@ -84,19 +82,17 @@ namespace crow
// middleware
using context_t = detail::context<Middlewares...>;
template <typename T>
T& get_middleware_context(request& req)
typename T::context& get_middleware_context(const request& req)
{
static_assert(black_magic::contains<T, Middlewares...>::value, "App doesn't have the specified middleware type.");
auto& ctx = *reinterpret_cast<context_t*>(req.middleware_context);
return ctx.get<T>();
return ctx.template get<T>();
}
private:
uint16_t port_ = 80;
uint16_t concurrency_ = 1;
std::tuple<Middlewares...> middlewares_;
Router router_;
};
template <typename ... Middlewares>

View File

@ -2,9 +2,9 @@
#include <boost/asio.hpp>
#include <boost/algorithm/string/predicate.hpp>
#include <boost/lexical_cast.hpp>
#include <boost/array.hpp>
#include <atomic>
#include <chrono>
#include <array>
#include "http_parser_merged.h"
@ -14,23 +14,57 @@
#include "logging.h"
#include "settings.h"
#include "dumb_timer_queue.h"
#include "middleware_context.h"
namespace crow
{
namespace detail
{
template <int N, typename Context, typename Container, typename CurrentMW, typename ... Middlewares>
bool middleware_call_helper(Container& middlewares, request& req, response& res, Context& ctx)
{
// TODO cut ctx to partial_context<0..N-1>
std::get<N>(middlewares).before_handle(req, res, ctx.template get<CurrentMW>(), ctx);
if (res.is_completed())
{
std::get<N>(middlewares).after_handle(req, res, ctx.template get<CurrentMW>(), ctx);
return true;
}
if (middleware_call_helper<N+1, Context, Middlewares...>(middlewares, req, res, ctx))
{
std::get<N>(middlewares).after_handle(req, res, ctx.template get<CurrentMW>(), ctx);
return true;
}
return false;
}
template <int N, typename Context, typename Container>
bool middleware_call_helper(Container& middlewares, request& req, response& res, Context& ctx)
{
return false;
}
}
using namespace boost;
using tcp = asio::ip::tcp;
#ifdef CROW_ENABLE_DEBUG
static int connectionCount;
#endif
template <typename Handler>
template <typename Handler, typename ... Middlewares>
class Connection
{
public:
Connection(boost::asio::io_service& io_service, Handler* handler, const std::string& server_name)
Connection(
boost::asio::io_service& io_service,
Handler* handler,
const std::string& server_name,
std::tuple<Middlewares...>& middlewares
)
: socket_(io_service),
handler_(handler),
parser_(this),
server_name_(server_name)
server_name_(server_name),
middlewares_(middlewares)
{
#ifdef CROW_ENABLE_DEBUG
connectionCount ++;
@ -101,11 +135,20 @@ namespace crow
<< method_name(req.method) << " " << req.url;
need_to_call_after_handlers_ = false;
if (!is_invalid_request)
{
res.complete_request_handler_ = [this]{ this->complete_request(); };
res.is_alive_helper_ = [this]()->bool{ return socket_.is_open(); };
handler_->handle(req, res);
req.middleware_context = (void*)&ctx_;
detail::middleware_call_helper<0, decltype(ctx_), decltype(middlewares_), Middlewares...>(middlewares_, req, res, ctx_);
if (!res.completed_)
{
need_to_call_after_handlers_ = true;
handler_->handle(req, res);
}
}
else
{
@ -117,6 +160,11 @@ namespace crow
{
CROW_LOG_INFO << "Response: " << this << ' ' << res.code << ' ' << close_connection_;
if (need_to_call_after_handlers_)
{
// TODO call all of after_handlers
}
//auto self = this->shared_from_this();
res.complete_request_handler_ = nullptr;
@ -330,7 +378,7 @@ namespace crow
tcp::socket socket_;
Handler* handler_;
std::array<char, 4096> buffer_;
boost::array<char, 4096> buffer_;
HTTPParser<Connection> parser_;
response res;
@ -348,6 +396,10 @@ namespace crow
bool is_reading{};
bool is_writing{};
bool need_to_call_after_handlers_;
std::tuple<Middlewares...>& middlewares_;
detail::context<Middlewares...> ctx_;
};
}

View File

@ -11,6 +11,6 @@ namespace crow
std::unordered_map<std::string, std::string> headers;
std::string body;
void* middleware_context;
void* middleware_context{};
};
}

View File

@ -5,11 +5,11 @@
namespace crow
{
template <typename T>
template <typename Handler, typename ... Middlewares>
class Connection;
struct response
{
template <typename T>
template <typename Handler, typename ... Middlewares>
friend class crow::Connection;
std::string body;
@ -31,7 +31,7 @@ namespace crow
response& operator = (const response& r) = delete;
response& operator = (response&& r)
response& operator = (response&& r) noexcept
{
body = std::move(r.body);
json_value = std::move(r.json_value);
@ -41,6 +41,11 @@ namespace crow
return *this;
}
bool is_completed() const noexcept
{
return completed_;
}
void clear()
{
body.clear();
@ -59,11 +64,11 @@ namespace crow
{
if (!completed_)
{
completed_ = true;
if (complete_request_handler_)
{
complete_request_handler_();
}
completed_ = true;
}
}

View File

@ -18,7 +18,7 @@ namespace crow
using namespace boost;
using tcp = asio::ip::tcp;
template <typename Handler>
template <typename Handler, typename ... Middlewares>
class Server
{
public:
@ -43,10 +43,13 @@ namespace crow
for(uint16_t i = 0; i < concurrency_; i ++)
v.push_back(
std::async(std::launch::async, [this, i]{
// initializing timer queue
auto& timer_queue = detail::dumb_timer_queue::get_current_dumb_timer_queue();
timer_queue.set_io_service(*io_service_pool_[i]);
boost::asio::deadline_timer timer(*io_service_pool_[i]);
timer.expires_from_now(boost::posix_time::seconds(1));
std::function<void(const boost::system::error_code& ec)> handler;
handler = [&](const boost::system::error_code& ec){
if (ec)
@ -56,6 +59,7 @@ namespace crow
timer.async_wait(handler);
};
timer.async_wait(handler);
io_service_pool_[i]->run();
}));
CROW_LOG_INFO << server_name_ << " server is running, local port " << port_;
@ -92,7 +96,7 @@ namespace crow
void do_accept()
{
auto p = new Connection<Handler>(pick_io_service(), handler_, server_name_);
auto p = new Connection<Handler, Middlewares...>(pick_io_service(), handler_, server_name_, middlewares_);
acceptor_.async_accept(p->socket(),
[this, p](boost::system::error_code ec)
{
@ -115,5 +119,8 @@ namespace crow
std::string server_name_ = "Crow/0.1";
uint16_t port_;
unsigned int roundrobin_index_{};
std::tuple<Middlewares...> middlewares_;
};
}

View File

@ -4,6 +4,21 @@
namespace crow
{
// Any middleware requires following 3 members:
// struct context;
// storing data for the middleware; can be read from another middleware or handlers
// template <typename AllContext>
// void before_handle(request& req, response& res, context& ctx, AllContext& all_ctx)
// called before handling the request.
// if res.end() is called, the operation is halted.
// (still call after_handle of this middleware)
// template <typename AllContext>
// void after_handle(request& req, response& res, context& ctx, AllContext& all_ctx)
// called after handling the request.
class CookieParser
{
struct context

View File

@ -10,9 +10,13 @@ add_executable(unittest ${TEST_SRCS})
#target_link_libraries(unittest crow)
target_link_libraries(unittest ${Boost_LIBRARIES} )
if ("${CMAKE_CXX_COMPILER_ID}" STREQUAL "Clang")
# using Clang
elseif ("${CMAKE_CXX_COMPILER_ID}" STREQUAL "GNU")
# using GCC
set_target_properties(unittest PROPERTIES COMPILE_FLAGS "--coverage -fprofile-arcs -ftest-coverage")
target_link_libraries(unittest gcov)
endif()
add_subdirectory(template)
#CXXFLAGS="-g -O0 -Wall -W -Wshadow -Wunused-variable \

View File

@ -352,7 +352,8 @@ TEST(json_read)
ASSERT_EQUAL(1, x.size());
ASSERT_EQUAL(false, x.has("mess"));
ASSERT_THROW(x["mess"]);
ASSERT_THROW(3 == x["message"]);
// TODO returning false is better than exception
//ASSERT_THROW(3 == x["message"]);
ASSERT_EQUAL(12, x["message"].size());
std::string s = R"({"int":3, "ints" :[1,2,3,4,5] })";
@ -478,6 +479,75 @@ int testmain()
return failed ? -1 : 0;
}
struct NullMiddleware
{
struct context {};
template <typename AllContext>
void before_handle(request& req, response& res, context& ctx, AllContext& all_ctx)
{}
template <typename AllContext>
void after_handle(request& req, response& res, context& ctx, AllContext& all_ctx)
{}
};
TEST(middleware_simple)
{
App<NullMiddleware> app;
CROW_ROUTE(app, "/")([&](const crow::request& req)
{
app.get_middleware_context<NullMiddleware>(req);
return "";
});
}
struct IntSettingMiddleware
{
struct context { int val; };
template <typename AllContext>
void before_handle(request& req, response& res, context& ctx, AllContext& all_ctx)
{
ctx.val = 1;
}
template <typename AllContext>
void after_handle(request& req, response& res, context& ctx, AllContext& all_ctx)
{
ctx.val = 2;
}
};
TEST(middleware_context)
{
static char buf[2048];
App<IntSettingMiddleware> app;
Server<decltype(app), IntSettingMiddleware> server(&app, 45451);
auto _ = async(launch::async, [&]{server.run();});
std::string sendmsg = "GET /\r\n\r\n";
int x{};
CROW_ROUTE(app, "/")([&](const request& req){
auto& ctx = app.get_middleware_context<IntSettingMiddleware>(req);
x = ctx.val;
return "";
});
asio::io_service is;
{
asio::ip::tcp::socket c(is);
c.connect(asio::ip::tcp::endpoint(asio::ip::address::from_string("127.0.0.1"), 45451));
c.send(asio::buffer(sendmsg));
c.receive(asio::buffer(buf, 2048));
}
ASSERT_EQUAL(1, x);
server.stop();
}
int main()
{
return testmain();