From cb2d10131143c9c0d1e8af903c23365ff89fdf0e Mon Sep 17 00:00:00 2001 From: qicosmos Date: Mon, 8 Jan 2024 17:13:23 +0800 Subject: [PATCH] Aspect (#478) --- include/cinatra/coro_http_connection.hpp | 4 +- include/cinatra/coro_http_router.hpp | 92 ++++++++++++++++++++++-- include/cinatra/coro_http_server.hpp | 23 ++++-- tests/test_coro_http_server.cpp | 69 +++++++++++++++++- 4 files changed, 172 insertions(+), 16 deletions(-) diff --git a/include/cinatra/coro_http_connection.hpp b/include/cinatra/coro_http_connection.hpp index e293ad05..8f87ba89 100644 --- a/include/cinatra/coro_http_connection.hpp +++ b/include/cinatra/coro_http_connection.hpp @@ -188,11 +188,11 @@ class coro_http_connection } if (auto handler = router_.get_handler(key); handler) { - router_.route(handler, request_, response_); + router_.route(handler, request_, response_, key); } else { if (auto coro_handler = router_.get_coro_handler(key); coro_handler) { - co_await router_.route_coro(coro_handler, request_, response_); + co_await router_.route_coro(coro_handler, request_, response_, key); } else { // not found diff --git a/include/cinatra/coro_http_router.hpp b/include/cinatra/coro_http_router.hpp index e269a23c..94bba8ac 100644 --- a/include/cinatra/coro_http_router.hpp +++ b/include/cinatra/coro_http_router.hpp @@ -26,11 +26,23 @@ 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; + } + + virtual bool after(coro_http_request& req, coro_http_response& resp) { + return true; + } +}; + class coro_http_router { public: // eg: "GET hello/" as a key template - void set_http_handler(std::string key, Func handler) { + void set_http_handler( + std::string key, Func handler, + std::vector> aspects = {}) { constexpr auto method_name = cinatra::method_name(method); std::string whole_str; whole_str.append(method_name).append(" ").append(key); @@ -45,6 +57,10 @@ class coro_http_router { return; } coro_handles_.emplace(*it, std::move(handler)); + if (!aspects.empty()) { + has_aspects_ = true; + aspects_.emplace(*it, std::move(aspects)); + } } else { auto [it, ok] = keys_.emplace(std::move(whole_str)); @@ -53,6 +69,10 @@ class coro_http_router { return; } map_handles_.emplace(*it, std::move(handler)); + if (!aspects.empty()) { + has_aspects_ = true; + aspects_.emplace(*it, std::move(aspects)); + } } } @@ -73,9 +93,21 @@ class coro_http_router { return nullptr; } - void route(auto handler, auto& req, auto& resp) { + void route(auto handler, auto& req, auto& resp, std::string_view key) { try { - (*handler)(req, resp); + 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); + } } catch (const std::exception& e) { CINATRA_LOG_WARNING << "exception in business function, reason: " << e.what(); @@ -86,10 +118,22 @@ class coro_http_router { } } - async_simple::coro::Lazy route_coro(auto handler, auto& req, - auto& resp) { + async_simple::coro::Lazy route_coro(auto handler, auto& req, auto& resp, + std::string_view key) { try { - co_await (*handler)(req, resp); + 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); + } } catch (const std::exception& e) { CINATRA_LOG_WARNING << "exception in business function, reason: " << e.what(); @@ -104,6 +148,38 @@ class coro_http_router { const auto& get_coro_handlers() const { return coro_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< @@ -116,5 +192,9 @@ class coro_http_router { std::function( coro_http_request& req, coro_http_response& resp)>> coro_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 57be14b6..796f83ad 100644 --- a/include/cinatra/coro_http_server.hpp +++ b/include/cinatra/coro_http_server.hpp @@ -125,32 +125,41 @@ class coro_http_server { uint16_t port() const { return port_; } template - void set_http_handler(std::string key, Func handler) { + void set_http_handler( + std::string key, Func handler, + std::vector> aspects = {}) { static_assert(sizeof...(method) >= 1, "must set http_method"); if constexpr (sizeof...(method) == 1) { - (router_.set_http_handler(std::move(key), std::move(handler)), + (router_.set_http_handler(std::move(key), std::move(handler), + std::move(aspects)), ...); } else { - (router_.set_http_handler(key, handler), ...); + (router_.set_http_handler(key, handler, aspects), ...); } } - template - void set_http_handler(std::string key, Func handler, auto owner) { + template + void set_http_handler( + std::string key, Func handler, Owner &&owner, + std::vector> aspects = {}) { + static_assert(std::is_member_function_pointer_v, + "must be member function"); using return_type = typename util::function_traits::return_type; if constexpr (is_lazy_v) { std::function(coro_http_request & req, coro_http_response & resp)> f = std::bind(handler, owner, std::placeholders::_1, std::placeholders::_2); - set_http_handler(std::move(key), std::move(f)); + set_http_handler(std::move(key), std::move(f), + std::move(aspects)); } else { std::function f = std::bind(handler, owner, std::placeholders::_1, std::placeholders::_2); - set_http_handler(std::move(key), std::move(f)); + set_http_handler(std::move(key), std::move(f), + std::move(aspects)); } } diff --git a/tests/test_coro_http_server.cpp b/tests/test_coro_http_server.cpp index 020f0c4a..10eac662 100644 --- a/tests/test_coro_http_server.cpp +++ b/tests/test_coro_http_server.cpp @@ -196,7 +196,7 @@ class my_object { async_simple::coro::Lazy lazy(coro_http_request &req, coro_http_response &response) { - response.set_status_and_content(status_type::ok, "ok"); + response.set_status_and_content(status_type::ok, "ok lazy"); co_return; } }; @@ -238,6 +238,12 @@ TEST_CASE("set http handler") { auto &handlers2 = server2.get_router().get_handlers(); CHECK(handlers2.size() == 1); + my_object o{}; + // member function + server2.set_http_handler("/test", &my_object::normal, o); + server2.set_http_handler("/test_lazy", &my_object::lazy, &o); + CHECK(handlers2.size() == 2); + auto coro_func = [](coro_http_request &req, coro_http_response &response) -> async_simple::coro::Lazy { @@ -380,6 +386,67 @@ TEST_CASE("get post") { server.stop(); } +struct log_t : public base_aspect { + bool before(coro_http_request &, coro_http_response &) { + std::cout << "before log" << std::endl; + return true; + } + + bool after(coro_http_request &, coro_http_response &res) { + std::cout << "after log" << std::endl; + res.add_header("aaaa", "bbcc"); + return true; + } +}; + +struct check_t : public base_aspect { + bool before(coro_http_request &, coro_http_response &) { + std::cout << "check before" << std::endl; + return true; + } +}; + +TEST_CASE("test aspects") { + coro_http_server server(1, 9001); + server.set_http_handler( + "/", + [](coro_http_request &req, coro_http_response &resp) { + resp.set_status_and_content(status_type::ok, "ok"); + }, + {std::make_shared(), std::make_shared()}); + + server.set_http_handler( + "/coro", + [](coro_http_request &req, + coro_http_response &resp) -> async_simple::coro::Lazy { + resp.set_status_and_content(status_type::ok, "ok"); + co_return; + }, + {std::make_shared(), std::make_shared()}); + server.async_start(); + + coro_http_client client{}; + auto result = client.get("http://127.0.0.1:9001/"); + + auto check = [](auto &result) { + bool has_str = false; + for (auto [k, v] : result.resp_headers) { + if (k == "aaaa") { + if (v == "bbcc") { + has_str = true; + } + break; + } + } + CHECK(has_str); + }; + + check(result); + + result = client.get("http://127.0.0.1:9001/coro"); + check(result); +} + TEST_CASE("use out context") { asio::io_context out_ctx; auto work = std::make_unique(out_ctx);