From 2748e35430b9a4aaf64dfbd626d819f0fc5eedd2 Mon Sep 17 00:00:00 2001 From: ipknHama Date: Sun, 7 Sep 2014 04:30:53 +0900 Subject: [PATCH] basic middleware test: before_handler --- include/crow.h | 16 ++++----- include/http_connection.h | 64 ++++++++++++++++++++++++++++++---- include/http_request.h | 2 +- include/http_response.h | 13 ++++--- include/http_server.h | 11 ++++-- include/middleware.h | 15 ++++++++ tests/CMakeLists.txt | 6 +++- tests/unittest.cpp | 72 ++++++++++++++++++++++++++++++++++++++- 8 files changed, 174 insertions(+), 25 deletions(-) diff --git a/include/crow.h b/include/crow.h index a018f316c..a4b82df2d 100644 --- a/include/crow.h +++ b/include/crow.h @@ -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 - #define CROW_ROUTE(app, url) app.route(url) namespace crow @@ -28,13 +25,14 @@ namespace crow { public: using self_t = Crow; + using server_t = Server; Crow() { } void handle(const request& req, response& res) { - return router_.handle(req, res); + router_.handle(req, res); } template @@ -71,7 +69,7 @@ namespace crow void run() { validate(); - Server server(this, port_, concurrency_); + server_t server(this, port_, concurrency_); server.run(); } @@ -84,19 +82,17 @@ namespace crow // middleware using context_t = detail::context; template - T& get_middleware_context(request& req) + typename T::context& get_middleware_context(const request& req) { static_assert(black_magic::contains::value, "App doesn't have the specified middleware type."); auto& ctx = *reinterpret_cast(req.middleware_context); - return ctx.get(); + return ctx.template get(); } private: uint16_t port_ = 80; uint16_t concurrency_ = 1; - std::tuple middlewares_; - Router router_; }; template diff --git a/include/http_connection.h b/include/http_connection.h index 907485fb6..a8631d9fb 100644 --- a/include/http_connection.h +++ b/include/http_connection.h @@ -2,9 +2,9 @@ #include #include #include +#include #include #include -#include #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 + bool middleware_call_helper(Container& middlewares, request& req, response& res, Context& ctx) + { + // TODO cut ctx to partial_context<0..N-1> + std::get(middlewares).before_handle(req, res, ctx.template get(), ctx); + if (res.is_completed()) + { + std::get(middlewares).after_handle(req, res, ctx.template get(), ctx); + return true; + } + if (middleware_call_helper(middlewares, req, res, ctx)) + { + std::get(middlewares).after_handle(req, res, ctx.template get(), ctx); + return true; + } + return false; + } + + template + 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 + template 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 + ) : 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 buffer_; + boost::array buffer_; HTTPParser parser_; response res; @@ -348,6 +396,10 @@ namespace crow bool is_reading{}; bool is_writing{}; + bool need_to_call_after_handlers_; + + std::tuple& middlewares_; + detail::context ctx_; }; } diff --git a/include/http_request.h b/include/http_request.h index 77b3ecb4d..af623c6ce 100644 --- a/include/http_request.h +++ b/include/http_request.h @@ -11,6 +11,6 @@ namespace crow std::unordered_map headers; std::string body; - void* middleware_context; + void* middleware_context{}; }; } diff --git a/include/http_response.h b/include/http_response.h index ae9254397..bc468b79f 100644 --- a/include/http_response.h +++ b/include/http_response.h @@ -5,11 +5,11 @@ namespace crow { - template + template class Connection; struct response { - template + template 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; } } diff --git a/include/http_server.h b/include/http_server.h index 6dc845aeb..484aa83df 100644 --- a/include/http_server.h +++ b/include/http_server.h @@ -18,7 +18,7 @@ namespace crow using namespace boost; using tcp = asio::ip::tcp; - template + template 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 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(pick_io_service(), handler_, server_name_); + auto p = new Connection(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_; + }; } diff --git a/include/middleware.h b/include/middleware.h index 270e026e7..5e358afcb 100644 --- a/include/middleware.h +++ b/include/middleware.h @@ -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 + // 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 + // void after_handle(request& req, response& res, context& ctx, AllContext& all_ctx) + // called after handling the request. + class CookieParser { struct context diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 6beb0bee2..5ea48f15e 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -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 \ diff --git a/tests/unittest.cpp b/tests/unittest.cpp index 17bc53cf5..c84e3ed08 100644 --- a/tests/unittest.cpp +++ b/tests/unittest.cpp @@ -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 + void before_handle(request& req, response& res, context& ctx, AllContext& all_ctx) + {} + + template + void after_handle(request& req, response& res, context& ctx, AllContext& all_ctx) + {} +}; + +TEST(middleware_simple) +{ + App app; + CROW_ROUTE(app, "/")([&](const crow::request& req) + { + app.get_middleware_context(req); + return ""; + }); +} + +struct IntSettingMiddleware +{ + struct context { int val; }; + + template + void before_handle(request& req, response& res, context& ctx, AllContext& all_ctx) + { + ctx.val = 1; + } + + template + void after_handle(request& req, response& res, context& ctx, AllContext& all_ctx) + { + ctx.val = 2; + } +}; + +TEST(middleware_context) +{ + static char buf[2048]; + App app; + Server 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(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();