Skip to content

Commit

Permalink
Aspect (qicosmos#478)
Browse files Browse the repository at this point in the history
  • Loading branch information
qicosmos authored Jan 8, 2024
1 parent 58ddc83 commit cb2d101
Show file tree
Hide file tree
Showing 4 changed files with 172 additions and 16 deletions.
4 changes: 2 additions & 2 deletions include/cinatra/coro_http_connection.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
92 changes: 86 additions & 6 deletions include/cinatra/coro_http_router.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -26,11 +26,23 @@ constexpr inline bool is_lazy_v =
is_template_instant_of<async_simple::coro::Lazy,
std::remove_cvref_t<T>>::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 <http_method method, typename Func>
void set_http_handler(std::string key, Func handler) {
void set_http_handler(
std::string key, Func handler,
std::vector<std::shared_ptr<base_aspect>> aspects = {}) {
constexpr auto method_name = cinatra::method_name(method);
std::string whole_str;
whole_str.append(method_name).append(" ").append(key);
Expand All @@ -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));
Expand All @@ -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));
}
}
}

Expand All @@ -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();
Expand All @@ -86,10 +118,22 @@ class coro_http_router {
}
}

async_simple::coro::Lazy<void> route_coro(auto handler, auto& req,
auto& resp) {
async_simple::coro::Lazy<void> 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();
Expand All @@ -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<std::string> keys_;
std::unordered_map<
Expand All @@ -116,5 +192,9 @@ class coro_http_router {
std::function<async_simple::coro::Lazy<void>(
coro_http_request& req, coro_http_response& resp)>>
coro_handles_;
std::unordered_map<std::string_view,
std::vector<std::shared_ptr<base_aspect>>>
aspects_;
bool has_aspects_ = false;
};
} // namespace cinatra
23 changes: 16 additions & 7 deletions include/cinatra/coro_http_server.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -125,32 +125,41 @@ class coro_http_server {
uint16_t port() const { return port_; }

template <http_method... method, typename Func>
void set_http_handler(std::string key, Func handler) {
void set_http_handler(
std::string key, Func handler,
std::vector<std::shared_ptr<base_aspect>> aspects = {}) {
static_assert(sizeof...(method) >= 1, "must set http_method");
if constexpr (sizeof...(method) == 1) {
(router_.set_http_handler<method>(std::move(key), std::move(handler)),
(router_.set_http_handler<method>(std::move(key), std::move(handler),
std::move(aspects)),
...);
}
else {
(router_.set_http_handler<method>(key, handler), ...);
(router_.set_http_handler<method>(key, handler, aspects), ...);
}
}

template <http_method... method, typename Func>
void set_http_handler(std::string key, Func handler, auto owner) {
template <http_method... method, typename Func, typename Owner>
void set_http_handler(
std::string key, Func handler, Owner &&owner,
std::vector<std::shared_ptr<base_aspect>> aspects = {}) {
static_assert(std::is_member_function_pointer_v<Func>,
"must be member function");
using return_type = typename util::function_traits<Func>::return_type;
if constexpr (is_lazy_v<return_type>) {
std::function<async_simple::coro::Lazy<void>(coro_http_request & req,
coro_http_response & resp)>
f = std::bind(handler, owner, std::placeholders::_1,
std::placeholders::_2);
set_http_handler<method...>(std::move(key), std::move(f));
set_http_handler<method...>(std::move(key), std::move(f),
std::move(aspects));
}
else {
std::function<void(coro_http_request & req, coro_http_response & resp)>
f = std::bind(handler, owner, std::placeholders::_1,
std::placeholders::_2);
set_http_handler<method...>(std::move(key), std::move(f));
set_http_handler<method...>(std::move(key), std::move(f),
std::move(aspects));
}
}

Expand Down
69 changes: 68 additions & 1 deletion tests/test_coro_http_server.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -196,7 +196,7 @@ class my_object {

async_simple::coro::Lazy<void> 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;
}
};
Expand Down Expand Up @@ -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<GET>("/test", &my_object::normal, o);
server2.set_http_handler<GET>("/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<void> {
Expand Down Expand Up @@ -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<GET, POST>(
"/",
[](coro_http_request &req, coro_http_response &resp) {
resp.set_status_and_content(status_type::ok, "ok");
},
{std::make_shared<log_t>(), std::make_shared<check_t>()});

server.set_http_handler<GET, POST>(
"/coro",
[](coro_http_request &req,
coro_http_response &resp) -> async_simple::coro::Lazy<void> {
resp.set_status_and_content(status_type::ok, "ok");
co_return;
},
{std::make_shared<log_t>(), std::make_shared<check_t>()});
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<asio::io_context::work>(out_ctx);
Expand Down

0 comments on commit cb2d101

Please sign in to comment.