From 259b9f9be8b1a7ab87f54f46c8ca4ee9b8d4b368 Mon Sep 17 00:00:00 2001 From: qicosmos Date: Fri, 9 Feb 2024 17:42:19 +0800 Subject: [PATCH] improve aspects (#522) --- example/main.cpp | 7 +- include/cinatra/coro_http_reverse_proxy.hpp | 13 +- include/cinatra/coro_http_router.hpp | 188 +++++++++++--------- include/cinatra/coro_http_server.hpp | 31 ++-- tests/test_coro_http_server.cpp | 20 +-- 5 files changed, 132 insertions(+), 127 deletions(-) diff --git a/example/main.cpp b/example/main.cpp index 6cd1a90a..addbc59b 100644 --- a/example/main.cpp +++ b/example/main.cpp @@ -243,7 +243,7 @@ async_simple::coro::Lazy static_file_server() { assert(result.resp_body.size() == 64); } -struct log_t : public base_aspect { +struct log_t { bool before(coro_http_request &, coro_http_response &) { std::cout << "before log" << std::endl; return true; @@ -256,7 +256,7 @@ struct log_t : public base_aspect { } }; -struct get_data : public base_aspect { +struct get_data { bool before(coro_http_request &req, coro_http_response &res) { req.set_aspect_data("hello world"); return true; @@ -272,8 +272,7 @@ async_simple::coro::Lazy use_aspects() { assert(val[0] == "hello world"); resp.set_status_and_content(status_type::ok, "ok"); }, - std::vector>{std::make_shared(), - std::make_shared()}); + log_t{}, get_data{}); server.async_start(); std::this_thread::sleep_for(300ms); // wait for server start diff --git a/include/cinatra/coro_http_reverse_proxy.hpp b/include/cinatra/coro_http_reverse_proxy.hpp index 176a5a12..e93e2eec 100644 --- a/include/cinatra/coro_http_reverse_proxy.hpp +++ b/include/cinatra/coro_http_reverse_proxy.hpp @@ -22,12 +22,11 @@ class reverse_proxy { weights_.push_back(weight); } - template - void start_reverse_proxy( - std::string url_path = "/", bool sync = true, - coro_io::load_blance_algorithm type = - coro_io::load_blance_algorithm::random, - std::vector> aspects = {}) { + template + void start_reverse_proxy(std::string url_path = "/", bool sync = true, + coro_io::load_blance_algorithm type = + coro_io::load_blance_algorithm::random, + Aspects &&...aspects) { if (dest_hosts_.empty()) { throw std::invalid_argument("not config hosts yet!"); } @@ -52,7 +51,7 @@ class reverse_proxy { co_await reply(client, uri.get_path(), req, response); }); }, - std::move(aspects)); + std::forward(aspects)...); start(sync); } diff --git a/include/cinatra/coro_http_router.hpp b/include/cinatra/coro_http_router.hpp index c50b6ab9..a620ab2c 100644 --- a/include/cinatra/coro_http_router.hpp +++ b/include/cinatra/coro_http_router.hpp @@ -28,23 +28,35 @@ constexpr inline bool is_lazy_v = is_template_instant_of>::value; -struct base_aspect { - virtual bool before(coro_http_request& req, coro_http_response& resp) { - return true; - } +template +struct has_before : std::false_type {}; - virtual bool after(coro_http_request& req, coro_http_response& resp) { - return true; - } -}; +template +struct has_before().before( + std::declval(), + std::declval()))>> + : std::true_type {}; + +template +struct has_after : std::false_type {}; + +template +struct has_after().after( + std::declval(), + std::declval()))>> + : std::true_type {}; + +template +constexpr bool has_before_v = has_before::value; + +template +constexpr bool has_after_v = has_after::value; class coro_http_router { public: // eg: "GET hello/" as a key - template - void set_http_handler( - std::string key, Func handler, - std::vector> aspects = {}) { + template + void set_http_handler(std::string key, Func handler, Aspects&&... asps) { constexpr auto method_name = cinatra::method_name(method); std::string whole_str; whole_str.append(method_name).append(" ").append(key); @@ -53,12 +65,34 @@ class coro_http_router { // std::string_view, avoid memcpy when route using return_type = typename util::function_traits::return_type; if constexpr (is_lazy_v) { + std::function(coro_http_request & req, + coro_http_response & resp)> + http_handler; + if constexpr (sizeof...(Aspects) > 0) { + http_handler = [this, handler = std::move(handler), + ... asps = std::forward(asps)]( + coro_http_request& req, + coro_http_response& resp) mutable + -> async_simple::coro::Lazy { + bool ok = true; + (do_before(asps, req, resp, ok), ...); + if (ok) { + co_await handler(req, resp); + + (do_after(asps, req, resp, ok), ...); + } + }; + } + else { + http_handler = std::move(handler); + } + if (whole_str.find(":") != std::string::npos) { std::vector coro_method_names = {}; std::string coro_method_str; coro_method_str.append(method_name); coro_method_names.push_back(coro_method_str); - coro_router_tree_->coro_insert(key, std::move(handler), + coro_router_tree_->coro_insert(key, std::move(http_handler), coro_method_names); } else { @@ -71,7 +105,7 @@ class coro_http_router { } coro_regex_handles_.emplace_back(std::regex(pattern), - std::move(handler)); + std::move(http_handler)); } else { auto [it, ok] = coro_keys_.emplace(std::move(whole_str)); @@ -79,21 +113,36 @@ class coro_http_router { CINATRA_LOG_WARNING << key << " has already registered."; return; } - coro_handles_.emplace(*it, std::move(handler)); - if (!aspects.empty()) { - has_aspects_ = true; - aspects_.emplace(*it, std::move(aspects)); - } + coro_handles_.emplace(*it, std::move(http_handler)); } } } else { + std::function + http_handler; + if constexpr (sizeof...(Aspects) > 0) { + http_handler = [this, handler = std::move(handler), + ... asps = std::forward(asps)]( + coro_http_request& req, + coro_http_response& resp) mutable { + bool ok = true; + (do_before(asps, req, resp, ok), ...); + if (ok) { + handler(req, resp); + (do_after(asps, req, resp, ok), ...); + } + }; + } + else { + http_handler = std::move(handler); + } + if (whole_str.find(':') != std::string::npos) { std::vector method_names = {}; std::string method_str; method_str.append(method_name); method_names.push_back(method_str); - router_tree_->insert(whole_str, std::move(handler), method_names); + router_tree_->insert(whole_str, std::move(http_handler), method_names); } else if (whole_str.find("{") != std::string::npos || whole_str.find(")") != std::string::npos) { @@ -103,7 +152,8 @@ class coro_http_router { replace_all(pattern, "{}", "([^/]+)"); } - regex_handles_.emplace_back(std::regex(pattern), std::move(handler)); + regex_handles_.emplace_back(std::regex(pattern), + std::move(http_handler)); } else { auto [it, ok] = keys_.emplace(std::move(whole_str)); @@ -111,12 +161,33 @@ class coro_http_router { CINATRA_LOG_WARNING << key << " has already registered."; return; } - map_handles_.emplace(*it, std::move(handler)); - if (!aspects.empty()) { - has_aspects_ = true; - aspects_.emplace(*it, std::move(aspects)); - } + map_handles_.emplace(*it, std::move(http_handler)); + } + } + } + + template + void do_before(T& aspect, coro_http_request& req, coro_http_response& resp, + bool& ok) { + if constexpr (has_before_v) { + if (!ok) { + return; } + ok = aspect.before(req, resp); + } + else { + ok = true; + } + } + + template + void do_after(T& aspect, coro_http_request& req, coro_http_response& resp, + bool& ok) { + if constexpr (has_after_v) { + ok = aspect.after(req, resp); + } + else { + ok = true; } } @@ -139,19 +210,7 @@ class coro_http_router { void route(auto handler, auto& req, auto& resp, std::string_view key) { try { - if (has_aspects_) { - auto [it, ok] = handle_aspects(req, resp, key, true); - if (!ok) { - return; - } - (*handler)(req, resp); - if (it != aspects_.end()) { - handle_aspects(req, resp, it->second, false); - } - } - else { - (*handler)(req, resp); - } + (*handler)(req, resp); } catch (const std::exception& e) { CINATRA_LOG_WARNING << "exception in business function, reason: " << e.what(); @@ -165,19 +224,7 @@ class coro_http_router { async_simple::coro::Lazy route_coro(auto handler, auto& req, auto& resp, std::string_view key) { try { - if (has_aspects_) { - auto [it, ok] = handle_aspects(req, resp, key, true); - if (!ok) { - co_return; - } - co_await (*handler)(req, resp); - if (it != aspects_.end()) { - handle_aspects(req, resp, it->second, false); - } - } - else { - co_await (*handler)(req, resp); - } + co_await (*handler)(req, resp); } catch (const std::exception& e) { CINATRA_LOG_WARNING << "exception in business function, reason: " << e.what(); @@ -202,38 +249,6 @@ class coro_http_router { const auto& get_regex_handlers() { return regex_handles_; } - bool handle_aspects(auto& req, auto& resp, auto& aspects, bool before) { - bool r = true; - for (auto& aspect : aspects) { - if (before) { - r = aspect->before(req, resp); - } - else { - r = aspect->after(req, resp); - } - if (!r) { - break; - } - } - return r; - } - - auto handle_aspects(auto& req, auto& resp, std::string_view key, - bool before) { - decltype(aspects_.begin()) it; - if (it = aspects_.find(key); it != aspects_.end()) { - auto& aspects = it->second; - bool r = handle_aspects(req, resp, aspects, before); - if (!r) { - return std::make_pair(aspects_.end(), false); - } - } - - return std::make_pair(it, true); - } - - void handle_after() {} - private: std::set keys_; std::unordered_map< @@ -262,10 +277,5 @@ class coro_http_router { std::regex, std::function( coro_http_request& req, coro_http_response& resp)>>> coro_regex_handles_; - - std::unordered_map>> - aspects_; - bool has_aspects_ = false; }; } // namespace cinatra \ No newline at end of file diff --git a/include/cinatra/coro_http_server.hpp b/include/cinatra/coro_http_server.hpp index 0a4bf66f..850dc0df 100644 --- a/include/cinatra/coro_http_server.hpp +++ b/include/cinatra/coro_http_server.hpp @@ -124,25 +124,24 @@ class coro_http_server { // call it after server async_start or sync_start. uint16_t port() const { return port_; } - template - void set_http_handler( - std::string key, Func handler, - std::vector> aspects = {}) { + template + void set_http_handler(std::string key, Func handler, Aspects &&...asps) { static_assert(sizeof...(method) >= 1, "must set http_method"); if constexpr (sizeof...(method) == 1) { (router_.set_http_handler(std::move(key), std::move(handler), - std::move(aspects)), + std::forward(asps)...), ...); } else { - (router_.set_http_handler(key, handler, aspects), ...); + (router_.set_http_handler(key, handler, + std::forward(asps)...), + ...); } } - template - void set_http_handler( - std::string key, Func handler, Owner &&owner, - std::vector> aspects = {}) { + template + void set_http_handler(std::string key, Func handler, + util::class_type_t &owner, Aspects &&...asps) { static_assert(std::is_member_function_pointer_v, "must be member function"); using return_type = typename util::function_traits::return_type; @@ -152,14 +151,14 @@ class coro_http_server { f = std::bind(handler, owner, std::placeholders::_1, std::placeholders::_2); set_http_handler(std::move(key), std::move(f), - std::move(aspects)); + std::forward(asps)...); } else { std::function f = std::bind(handler, owner, std::placeholders::_1, std::placeholders::_2); set_http_handler(std::move(key), std::move(f), - std::move(aspects)); + std::forward(asps)...); } } @@ -196,9 +195,9 @@ class coro_http_server { void set_transfer_chunked_size(size_t size) { chunked_size_ = size; } - void set_static_res_dir( - std::string_view uri_suffix = "", std::string file_path = "www", - std::vector> aspects = {}) { + template + void set_static_res_dir(std::string_view uri_suffix = "", + std::string file_path = "www", Aspects &&...aspects) { bool has_double_dot = (file_path.find("..") != std::string::npos) || (uri_suffix.find("..") != std::string::npos); if (std::filesystem::path(file_path).has_root_path() || @@ -417,7 +416,7 @@ class coro_http_server { } } }, - aspects); + std::forward(aspects)...); } } diff --git a/tests/test_coro_http_server.cpp b/tests/test_coro_http_server.cpp index a617c891..d43d0bf6 100644 --- a/tests/test_coro_http_server.cpp +++ b/tests/test_coro_http_server.cpp @@ -362,7 +362,7 @@ TEST_CASE("set http handler") { my_object o{}; // member function server2.set_http_handler("/test", &my_object::normal, o); - server2.set_http_handler("/test_lazy", &my_object::lazy, &o); + server2.set_http_handler("/test_lazy", &my_object::lazy, o); CHECK(handlers2.size() == 2); auto coro_func = @@ -526,7 +526,7 @@ TEST_CASE("test alias") { CHECK(result.resp_body == "ok"); } -struct log_t : public base_aspect { +struct log_t { bool before(coro_http_request &, coro_http_response &) { std::cout << "before log" << std::endl; return true; @@ -539,14 +539,14 @@ struct log_t : public base_aspect { } }; -struct check_t : public base_aspect { +struct check_t { bool before(coro_http_request &, coro_http_response &) { std::cout << "check before" << std::endl; return true; } }; -struct get_data : public base_aspect { +struct get_data { bool before(coro_http_request &req, coro_http_response &res) { req.set_aspect_data("hello", "world"); return true; @@ -560,16 +560,14 @@ TEST_CASE("test aspects") { create_file("test_aspect.txt", 64); // in cache create_file("test_file.txt", 200); // not in cache - std::vector> aspects = { - std::make_shared(), std::make_shared()}; - server.set_static_res_dir("", "", aspects); + server.set_static_res_dir("", "", log_t{}, check_t{}); server.set_http_handler( "/", [](coro_http_request &req, coro_http_response &resp) { resp.add_header("aaaa", "bbcc"); resp.set_status_and_content(status_type::ok, "ok"); }, - {std::make_shared(), std::make_shared()}); + log_t{}, check_t{}); server.set_http_handler( "/aspect", @@ -581,7 +579,7 @@ TEST_CASE("test aspects") { resp.set_status_and_content(status_type::ok, "ok"); co_return; }, - {std::make_shared()}); + get_data{}); server.async_start(); std::this_thread::sleep_for(300ms); @@ -1501,8 +1499,8 @@ TEST_CASE("test reverse proxy") { proxy_wrr.add_dest_host("127.0.0.1:9001", 10); proxy_wrr.add_dest_host("127.0.0.1:9002", 5); proxy_wrr.add_dest_host("127.0.0.1:9003", 5); - proxy_wrr.start_reverse_proxy("/wrr", false, - coro_io::load_blance_algorithm::WRR); + proxy_wrr.start_reverse_proxy( + "/wrr", false, coro_io::load_blance_algorithm::WRR, log_t{}, check_t{}); reverse_proxy proxy_rr(10, 8091); proxy_rr.add_dest_host("127.0.0.1:9001");