From 7a7863feea897c648dfd4b765708034badfb181f Mon Sep 17 00:00:00 2001 From: helintongh Date: Mon, 1 Jan 2024 22:29:47 +0800 Subject: [PATCH 1/6] feat: add radix tree router and regex router --- include/cinatra/coro_http_connection.hpp | 54 ++++- include/cinatra/coro_http_request.hpp | 13 ++ include/cinatra/coro_http_router.hpp | 76 +++++++- include/cinatra/coro_radix_tree.hpp | 238 +++++++++++++++++++++++ tests/test_coro_http_server.cpp | 42 ++++ 5 files changed, 411 insertions(+), 12 deletions(-) create mode 100644 include/cinatra/coro_radix_tree.hpp diff --git a/include/cinatra/coro_http_connection.hpp b/include/cinatra/coro_http_connection.hpp index e293ad05..70f73a98 100644 --- a/include/cinatra/coro_http_connection.hpp +++ b/include/cinatra/coro_http_connection.hpp @@ -195,8 +195,58 @@ class coro_http_connection co_await router_.route_coro(coro_handler, request_, response_); } else { - // not found - response_.set_status(status_type::not_found); + bool is_exist = false; + std::function + handler; + params_t params = {}; + std::string method_str; + method_str.assign(parser_.method().data(), parser_.method().length()); + std::string url_path; + url_path.assign(parser_.url().data(), parser_.url().length()); + + std::tie(is_exist, handler, request_.params_) = + router_.get_router_tree()->get(url_path, method_str); + if (is_exist) { + (handler)(request_, response_); + } + else { + bool is_matched_regex_router = false; + // coro regex router + auto coro_regex_handlers = router_.get_coro_regex_handlers(); + if (coro_regex_handlers.size() != 0) { + for (auto &pair : coro_regex_handlers) { + std::string coro_regex_key; + coro_regex_key.assign(key.data(), key.size()); + + if (std::regex_match(coro_regex_key, request_.matches_, + std::get<0>(pair))) { + auto coro_handler = std::get<1>(pair); + co_await (coro_handler)(request_, response_); + is_matched_regex_router = true; + } + } + } + // regex router + if (!is_matched_regex_router) { + auto regex_handlers = router_.get_regex_handlers(); + if (regex_handlers.size() != 0) { + for (auto &pair : regex_handlers) { + std::string regex_key; + regex_key.assign(key.data(), key.size()); + if (std::regex_match(regex_key, request_.matches_, + std::get<0>(pair))) { + auto handler = std::get<1>(pair); + (handler)(request_, response_); + is_matched_regex_router = true; + } + } + } + } + // not found + if (!is_matched_regex_router) + response_.set_status(status_type::not_found); + } } } diff --git a/include/cinatra/coro_http_request.hpp b/include/cinatra/coro_http_request.hpp index 07211d95..8920449d 100644 --- a/include/cinatra/coro_http_request.hpp +++ b/include/cinatra/coro_http_request.hpp @@ -1,10 +1,20 @@ #pragma once +#include + #include "async_simple/coro/Lazy.h" #include "define.h" #include "http_parser.hpp" #include "ws_define.h" namespace cinatra { + +typedef std::pair paramters_t; + +struct params_t { + std::vector parameters; + int size; +}; + class coro_http_connection; class coro_http_request { public: @@ -118,6 +128,9 @@ class coro_http_request { return true; } + params_t params_; + std::smatch matches_; + private: http_parser& parser_; std::string_view body_; diff --git a/include/cinatra/coro_http_router.hpp b/include/cinatra/coro_http_router.hpp index e269a23c..2f832954 100644 --- a/include/cinatra/coro_http_router.hpp +++ b/include/cinatra/coro_http_router.hpp @@ -10,7 +10,9 @@ #include "cinatra/cinatra_log_wrapper.hpp" #include "cinatra/coro_http_request.hpp" +#include "cinatra/coro_radix_tree.hpp" #include "cinatra/response_cv.hpp" +#include "cinatra/utils.hpp" #include "coro_http_response.hpp" #include "ylt/util/type_traits.h" @@ -39,20 +41,56 @@ 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) { - auto [it, ok] = coro_keys_.emplace(std::move(whole_str)); - if (!ok) { - CINATRA_LOG_WARNING << key << " has already registered."; - return; + if (whole_str.find("{") != std::string::npos || + whole_str.find(")") != std::string::npos) { + std::string pattern = whole_str; + std::unordered_map params; + params.clear(); + + if (pattern.find("{}") != std::string::npos) { + replace_all(pattern, "{}", "([^/]+)"); + } + + coro_regex_handles_.emplace_back(std::regex(pattern), + std::move(handler)); + } + else { + auto [it, ok] = coro_keys_.emplace(std::move(whole_str)); + if (!ok) { + CINATRA_LOG_WARNING << key << " has already registered."; + return; + } + coro_handles_.emplace(*it, std::move(handler)); } - coro_handles_.emplace(*it, std::move(handler)); } else { - auto [it, ok] = keys_.emplace(std::move(whole_str)); - if (!ok) { - CINATRA_LOG_WARNING << key << " has already registered."; - return; + 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(key, std::move(handler), method_names); + } + else if (whole_str.find("{") != std::string::npos || + whole_str.find(")") != std::string::npos) { + std::string pattern = whole_str; + std::unordered_map params; + params.clear(); + + if (pattern.find("{}") != std::string::npos) { + replace_all(pattern, "{}", "([^/]+)"); + } + + regex_handles_.emplace_back(std::regex(pattern), std::move(handler)); + } + else { + auto [it, ok] = keys_.emplace(std::move(whole_str)); + if (!ok) { + CINATRA_LOG_WARNING << key << " has already registered."; + return; + } + map_handles_.emplace(*it, std::move(handler)); } - map_handles_.emplace(*it, std::move(handler)); } } @@ -104,6 +142,12 @@ class coro_http_router { const auto& get_coro_handlers() const { return coro_handles_; } + radix_tree* get_router_tree() { return router_tree_; } + + const auto& get_coro_regex_handlers() { return coro_regex_handles_; } + + const auto& get_regex_handlers() { return regex_handles_; } + private: std::set keys_; std::unordered_map< @@ -116,5 +160,17 @@ class coro_http_router { std::function( coro_http_request& req, coro_http_response& resp)>> coro_handles_; + + radix_tree* router_tree_ = new radix_tree(); + + std::vector>> + regex_handles_; + + std::vector( + coro_http_request& req, coro_http_response& resp)>>> + coro_regex_handles_; }; } // namespace cinatra \ No newline at end of file diff --git a/include/cinatra/coro_radix_tree.hpp b/include/cinatra/coro_radix_tree.hpp new file mode 100644 index 00000000..4580f328 --- /dev/null +++ b/include/cinatra/coro_radix_tree.hpp @@ -0,0 +1,238 @@ +#pragma once + +#include + +#include +#include +#include +#include +#include + +#include "cinatra/coro_http_request.hpp" +#include "coro_http_response.hpp" +#include "ylt/util/type_traits.h" + +namespace cinatra { +constexpr char type_asterisk = '*'; +constexpr char type_colon = ':'; +constexpr char type_slash = '/'; + +typedef std::tuple< + bool, std::function, + params_t> + parse_result; + +struct handler_t { + std::string method; + std::function handler; +}; + +struct radix_tree_node { + std::string path; + std::vector handlers; + std::string indices; + std::vector children; + int max_params; + + radix_tree_node() = default; + radix_tree_node(const std::string &path) { this->path = path; } + ~radix_tree_node() { + for (auto &c : this->children) delete c; + } + + std::function + get_handler(const std::string &method) { + for (auto &h : this->handlers) { + if (h.method == method) { + return h.handler; + } + } + return nullptr; + } + + int add_handler( + std::function + handler, + const std::vector &methods) { + for (auto &m : methods) { + auto old_handler = this->get_handler(m); + this->handlers.push_back(handler_t{m, handler}); + } + return 0; + } + + radix_tree_node *insert_child(char index, radix_tree_node *child) { + auto i = this->get_index_position(index); + this->indices.insert(this->indices.begin() + i, index); + this->children.insert(this->children.begin() + i, child); + return child; + } + + radix_tree_node *get_child(char index) { + auto i = this->get_index_position(index); + return this->indices[i] != index ? nullptr : this->children[i]; + } + + int get_index_position(char target) { + int low = 0, high = this->indices.size(), mid; + + while (low < high) { + mid = low + ((high - low) >> 1); + if (this->indices[mid] < target) + low = mid + 1; + else + high = mid; + } + return low; + } +}; + +class radix_tree { + public: + radix_tree() { this->root = new radix_tree_node(); } + + ~radix_tree() { delete this->root; } + + int insert( + const std::string &path, + std::function + handler, + const std::vector &methods) { + auto root = this->root; + int i = 0, n = path.size(), param_count = 0, code = 0; + while (i < n) { + if (!root->indices.empty() && + (root->indices[0] == type_asterisk || path[i] == type_asterisk || + (path[i] != type_colon && root->indices[0] == type_colon) || + (path[i] == type_colon && root->indices[0] != type_colon) || + (path[i] == type_colon && root->indices[0] == type_colon && + path.substr(i + 1, find_pos(path, type_slash, i) - i - 1) != + root->children[0]->path))) { + code = -1; + break; + } + + auto child = root->get_child(path[i]); + if (!child) { + auto p = find_pos(path, type_colon, i); + + if (p == n) { + p = find_pos(path, type_asterisk, i); + + root = root->insert_child(path[i], + new radix_tree_node(path.substr(i, p - i))); + + if (p < n) { + root = root->insert_child(type_asterisk, + new radix_tree_node(path.substr(p + 1))); + ++param_count; + } + + code = root->add_handler(handler, methods); + break; + } + + root = root->insert_child(path[i], + new radix_tree_node(path.substr(i, p - i))); + + i = find_pos(path, type_slash, p); + + root = root->insert_child( + type_colon, new radix_tree_node(path.substr(p + 1, i - p - 1))); + ++param_count; + + if (i == n) { + code = root->add_handler(handler, methods); + break; + } + } + else { + root = child; + + if (path[i] == type_colon) { + ++param_count; + i += root->path.size() + 1; + + if (i == n) { + code = root->add_handler(handler, methods); + break; + } + } + else { + auto j = 0UL; + auto m = root->path.size(); + + for (; i < n && j < m && path[i] == root->path[j]; ++i, ++j) { + } + + if (j < m) { + auto child = new radix_tree_node(root->path.substr(j)); + child->handlers = root->handlers; + child->indices = root->indices; + child->children = root->children; + + root->path = root->path.substr(0, j); + root->handlers = {}; + root->indices = child->path[0]; + root->children = {child}; + } + + if (i == n) { + code = root->add_handler(handler, methods); + break; + } + } + } + } + + if (param_count > this->root->max_params) + this->root->max_params = param_count; + + return code; + } + + parse_result get(const std::string &path, const std::string &method) { + params_t params = {{}, 0}; + auto root = this->root; + + int i = 0, n = path.size(), p; + + while (i < n) { + if (root->indices.empty()) + return parse_result(); + + if (root->indices[0] == type_colon) { + root = root->children[0]; + + p = find_pos(path, type_slash, i); + params.parameters.push_back( + paramters_t{root->path, path.substr(i, p - i)}); + params.size++; + i = p; + } + else if (root->indices[0] == type_asterisk) { + root = root->children[0]; + params.parameters.push_back(paramters_t{root->path, path.substr(i)}); + params.size++; + break; + } + else { + root = root->get_child(path[i]); + if (!root || path.substr(i, root->path.size()) != root->path) + return parse_result(); + i += root->path.size(); + } + } + + return parse_result{true, root->get_handler(method), params}; + } + + private: + int find_pos(const std::string &str, char target, int start) { + auto i = str.find(target, start); + return i == -1 ? str.size() : i; + } + + radix_tree_node *root; +}; +} // namespace cinatra \ No newline at end of file diff --git a/tests/test_coro_http_server.cpp b/tests/test_coro_http_server.cpp index 21d56a04..99f43425 100644 --- a/tests/test_coro_http_server.cpp +++ b/tests/test_coro_http_server.cpp @@ -960,6 +960,48 @@ TEST_CASE("test http download server") { } } +TEST_CASE("test restful api") { + cinatra::coro_http_server server(1, 9001); + server.set_http_handler( + "/test/:id/test2/:name", + [](coro_http_request &req, coro_http_response &response) { + CHECK(req.params_.parameters[0].first == "id"); + CHECK(req.params_.parameters[0].second == "11"); + CHECK(req.params_.parameters[1].first == "name"); + CHECK(req.params_.parameters[1].second == "cpp"); + response.set_status_and_content(status_type::ok, "ok"); + }); + + server.set_http_handler( + "/test2/{}/test3/{}", + [](coro_http_request &req, + coro_http_response &resp) -> async_simple::coro::Lazy { + co_await coro_io::post([&]() { + // coroutine in other thread. + CHECK(req.matches_.str(1) == "name"); + CHECK(req.matches_.str(2) == "test"); + resp.set_status_and_content(cinatra::status_type::ok, "hello world"); + }); + co_return; + }); + + server.set_http_handler( + R"(/numbers/(\d+)/test/(\d+))", + [](coro_http_request &req, coro_http_response &response) { + CHECK(req.matches_.str(1) == "100"); + CHECK(req.matches_.str(2) == "200"); + response.set_status_and_content(status_type::ok, "number regex ok"); + }); + + server.async_start(); + std::this_thread::sleep_for(200ms); + + coro_http_client client; + client.get("http://127.0.0.1:9001/test/11/test2/cpp"); + client.get("http://127.0.0.1:9001/test2/name/test3/test"); + client.get("http://127.0.0.1:9001/numbers/100/test/200"); +} + DOCTEST_MSVC_SUPPRESS_WARNING_WITH_PUSH(4007) int main(int argc, char **argv) { return doctest::Context(argc, argv).run(); } DOCTEST_MSVC_SUPPRESS_WARNING_POP \ No newline at end of file From 5ddab2b3a1c6ca25fffeeea1f818ba85e4a23a80 Mon Sep 17 00:00:00 2001 From: helintongh Date: Mon, 1 Jan 2024 22:51:37 +0800 Subject: [PATCH 2/6] fix: fix memory leak --- include/cinatra/coro_http_router.hpp | 1 + 1 file changed, 1 insertion(+) diff --git a/include/cinatra/coro_http_router.hpp b/include/cinatra/coro_http_router.hpp index 2f832954..d129b9eb 100644 --- a/include/cinatra/coro_http_router.hpp +++ b/include/cinatra/coro_http_router.hpp @@ -30,6 +30,7 @@ constexpr inline bool is_lazy_v = class coro_http_router { public: + ~coro_http_router() { delete router_tree_; } // eg: "GET hello/" as a key template void set_http_handler(std::string key, Func handler) { From a14d1cc5e97b41890e7bf6c32baf3a75d0065c6a Mon Sep 17 00:00:00 2001 From: helintong Date: Tue, 9 Jan 2024 19:05:18 +0800 Subject: [PATCH 3/6] feat: coro add radix tree router --- include/cinatra/coro_http_connection.hpp | 74 ++++---- include/cinatra/coro_http_request.hpp | 9 +- include/cinatra/coro_http_router.hpp | 57 +++--- include/cinatra/coro_radix_tree.hpp | 219 ++++++++++++++++++++--- tests/test_coro_http_server.cpp | 123 +++++++++++-- 5 files changed, 387 insertions(+), 95 deletions(-) diff --git a/include/cinatra/coro_http_connection.hpp b/include/cinatra/coro_http_connection.hpp index 70f73a98..1e3d1ee3 100644 --- a/include/cinatra/coro_http_connection.hpp +++ b/include/cinatra/coro_http_connection.hpp @@ -199,53 +199,65 @@ class coro_http_connection std::function handler; - params_t params = {}; std::string method_str; method_str.assign(parser_.method().data(), parser_.method().length()); std::string url_path; url_path.assign(parser_.url().data(), parser_.url().length()); - std::tie(is_exist, handler, request_.params_) = router_.get_router_tree()->get(url_path, method_str); if (is_exist) { - (handler)(request_, response_); + router_.route(&handler, request_, response_); } else { - bool is_matched_regex_router = false; - // coro regex router - auto coro_regex_handlers = router_.get_coro_regex_handlers(); - if (coro_regex_handlers.size() != 0) { - for (auto &pair : coro_regex_handlers) { - std::string coro_regex_key; - coro_regex_key.assign(key.data(), key.size()); - - if (std::regex_match(coro_regex_key, request_.matches_, - std::get<0>(pair))) { - auto coro_handler = std::get<1>(pair); - co_await (coro_handler)(request_, response_); - is_matched_regex_router = true; - } - } + bool is_coro_exist = false; + std::function( + coro_http_request & req, coro_http_response & resp)> + coro_handler; + + std::tie(is_coro_exist, coro_handler, request_.params_) = + router_.get_coro_router_tree()->get_coro(url_path, method_str); + + if (is_coro_exist) { + std::cout << "come exit here!\n"; + co_await router_.route_coro(&coro_handler, request_, response_); } - // regex router - if (!is_matched_regex_router) { - auto regex_handlers = router_.get_regex_handlers(); - if (regex_handlers.size() != 0) { - for (auto &pair : regex_handlers) { - std::string regex_key; - regex_key.assign(key.data(), key.size()); - if (std::regex_match(regex_key, request_.matches_, + else { + bool is_matched_regex_router = false; + // coro regex router + auto coro_regex_handlers = router_.get_coro_regex_handlers(); + if (coro_regex_handlers.size() != 0) { + for (auto &pair : coro_regex_handlers) { + std::string coro_regex_key; + coro_regex_key.assign(key.data(), key.size()); + + if (std::regex_match(coro_regex_key, request_.matches_, std::get<0>(pair))) { - auto handler = std::get<1>(pair); - (handler)(request_, response_); + auto coro_handler = std::get<1>(pair); + co_await (coro_handler)(request_, response_); is_matched_regex_router = true; } } } + // regex router + if (!is_matched_regex_router) { + auto regex_handlers = router_.get_regex_handlers(); + if (regex_handlers.size() != 0) { + for (auto &pair : regex_handlers) { + std::string regex_key; + regex_key.assign(key.data(), key.size()); + if (std::regex_match(regex_key, request_.matches_, + std::get<0>(pair))) { + auto handler = std::get<1>(pair); + (handler)(request_, response_); + is_matched_regex_router = true; + } + } + } + } + // not found + if (!is_matched_regex_router) + response_.set_status(status_type::not_found); } - // not found - if (!is_matched_regex_router) - response_.set_status(status_type::not_found); } } } diff --git a/include/cinatra/coro_http_request.hpp b/include/cinatra/coro_http_request.hpp index 8920449d..036fc188 100644 --- a/include/cinatra/coro_http_request.hpp +++ b/include/cinatra/coro_http_request.hpp @@ -8,13 +8,6 @@ namespace cinatra { -typedef std::pair paramters_t; - -struct params_t { - std::vector parameters; - int size; -}; - class coro_http_connection; class coro_http_request { public: @@ -128,7 +121,7 @@ class coro_http_request { return true; } - params_t params_; + std::unordered_map params_; std::smatch matches_; private: diff --git a/include/cinatra/coro_http_router.hpp b/include/cinatra/coro_http_router.hpp index d129b9eb..a28b5e15 100644 --- a/include/cinatra/coro_http_router.hpp +++ b/include/cinatra/coro_http_router.hpp @@ -30,7 +30,6 @@ constexpr inline bool is_lazy_v = class coro_http_router { public: - ~coro_http_router() { delete router_tree_; } // eg: "GET hello/" as a key template void set_http_handler(std::string key, Func handler) { @@ -42,26 +41,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) { - if (whole_str.find("{") != std::string::npos || - whole_str.find(")") != std::string::npos) { - std::string pattern = whole_str; - std::unordered_map params; - params.clear(); - - if (pattern.find("{}") != std::string::npos) { - replace_all(pattern, "{}", "([^/]+)"); - } - - coro_regex_handles_.emplace_back(std::regex(pattern), - 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_method_names); } else { - auto [it, ok] = coro_keys_.emplace(std::move(whole_str)); - if (!ok) { - CINATRA_LOG_WARNING << key << " has already registered."; - return; + if (whole_str.find("{") != std::string::npos || + whole_str.find(")") != std::string::npos) { + std::string pattern = whole_str; + + if (pattern.find("{}") != std::string::npos) { + replace_all(pattern, "{}", "([^/]+)"); + } + + coro_regex_handles_.emplace_back(std::regex(pattern), + std::move(handler)); + } + else { + auto [it, ok] = coro_keys_.emplace(std::move(whole_str)); + if (!ok) { + CINATRA_LOG_WARNING << key << " has already registered."; + return; + } + coro_handles_.emplace(*it, std::move(handler)); } - coro_handles_.emplace(*it, std::move(handler)); } } else { @@ -75,8 +82,6 @@ class coro_http_router { else if (whole_str.find("{") != std::string::npos || whole_str.find(")") != std::string::npos) { std::string pattern = whole_str; - std::unordered_map params; - params.clear(); if (pattern.find("{}") != std::string::npos) { replace_all(pattern, "{}", "([^/]+)"); @@ -143,7 +148,11 @@ class coro_http_router { const auto& get_coro_handlers() const { return coro_handles_; } - radix_tree* get_router_tree() { return router_tree_; } + std::shared_ptr get_router_tree() { return router_tree_; } + + std::shared_ptr get_coro_router_tree() { + return coro_router_tree_; + } const auto& get_coro_regex_handlers() { return coro_regex_handles_; } @@ -162,7 +171,11 @@ class coro_http_router { coro_http_request& req, coro_http_response& resp)>> coro_handles_; - radix_tree* router_tree_ = new radix_tree(); + std::shared_ptr router_tree_ = + std::make_shared(radix_tree()); + + std::shared_ptr coro_router_tree_ = + std::make_shared(radix_tree()); std::vector, - params_t> + std::unordered_map> parse_result; +typedef std::tuple( + coro_http_request &req, coro_http_response &resp)>, + std::unordered_map> + coro_result; + struct handler_t { std::string method; std::function handler; }; +struct coro_handler_t { + std::string method; + std::function(coro_http_request &req, + coro_http_response &resp)> + coro_handler; +}; + struct radix_tree_node { std::string path; std::vector handlers; + std::vector coro_handlers; std::string indices; - std::vector children; + std::vector> children; int max_params; radix_tree_node() = default; radix_tree_node(const std::string &path) { this->path = path; } - ~radix_tree_node() { - for (auto &c : this->children) delete c; - } + ~radix_tree_node() {} std::function get_handler(const std::string &method) { @@ -50,6 +62,17 @@ struct radix_tree_node { return nullptr; } + std::function(coro_http_request &req, + coro_http_response &resp)> + get_coro_handler(const std::string &method) { + for (auto &h : this->coro_handlers) { + if (h.method == method) { + return h.coro_handler; + } + } + return nullptr; + } + int add_handler( std::function handler, @@ -61,14 +84,26 @@ struct radix_tree_node { return 0; } - radix_tree_node *insert_child(char index, radix_tree_node *child) { + int add_coro_handler(std::function( + coro_http_request &req, coro_http_response &resp)> + coro_handler, + const std::vector &methods) { + for (auto &m : methods) { + auto old_coro_handler = this->get_coro_handler(m); + this->coro_handlers.push_back(coro_handler_t{m, coro_handler}); + } + return 0; + } + + std::shared_ptr insert_child( + char index, std::shared_ptr child) { auto i = this->get_index_position(index); this->indices.insert(this->indices.begin() + i, index); this->children.insert(this->children.begin() + i, child); return child; } - radix_tree_node *get_child(char index) { + std::shared_ptr get_child(char index) { auto i = this->get_index_position(index); return this->indices[i] != index ? nullptr : this->children[i]; } @@ -89,9 +124,11 @@ struct radix_tree_node { class radix_tree { public: - radix_tree() { this->root = new radix_tree_node(); } + radix_tree() { + this->root = std::make_shared(radix_tree_node()); + } - ~radix_tree() { delete this->root; } + ~radix_tree() {} int insert( const std::string &path, @@ -119,12 +156,13 @@ class radix_tree { if (p == n) { p = find_pos(path, type_asterisk, i); - root = root->insert_child(path[i], - new radix_tree_node(path.substr(i, p - i))); + root = root->insert_child(path[i], std::make_shared( + path.substr(i, p - i))); if (p < n) { - root = root->insert_child(type_asterisk, - new radix_tree_node(path.substr(p + 1))); + root = root->insert_child( + type_asterisk, + std::make_shared(path.substr(p + 1))); ++param_count; } @@ -132,13 +170,14 @@ class radix_tree { break; } - root = root->insert_child(path[i], - new radix_tree_node(path.substr(i, p - i))); + root = root->insert_child( + path[i], std::make_shared(path.substr(i, p - i))); i = find_pos(path, type_slash, p); root = root->insert_child( - type_colon, new radix_tree_node(path.substr(p + 1, i - p - 1))); + type_colon, + std::make_shared(path.substr(p + 1, i - p - 1))); ++param_count; if (i == n) { @@ -166,7 +205,8 @@ class radix_tree { } if (j < m) { - auto child = new radix_tree_node(root->path.substr(j)); + std::shared_ptr child( + std::make_shared(root->path.substr(j))); child->handlers = root->handlers; child->indices = root->indices; child->children = root->children; @@ -191,8 +231,109 @@ class radix_tree { return code; } + int coro_insert(const std::string &path, + std::function( + coro_http_request &req, coro_http_response &resp)> + coro_handler, + const std::vector &methods) { + auto root = this->root; + int i = 0, n = path.size(), param_count = 0, code = 0; + while (i < n) { + if (!root->indices.empty() && + (root->indices[0] == type_asterisk || path[i] == type_asterisk || + (path[i] != type_colon && root->indices[0] == type_colon) || + (path[i] == type_colon && root->indices[0] != type_colon) || + (path[i] == type_colon && root->indices[0] == type_colon && + path.substr(i + 1, find_pos(path, type_slash, i) - i - 1) != + root->children[0]->path))) { + code = -1; + break; + } + + auto child = root->get_child(path[i]); + if (!child) { + auto p = find_pos(path, type_colon, i); + + if (p == n) { + p = find_pos(path, type_asterisk, i); + + root = root->insert_child(path[i], std::make_shared( + path.substr(i, p - i))); + + if (p < n) { + root = root->insert_child( + type_asterisk, + std::make_shared(path.substr(p + 1))); + ++param_count; + } + + code = root->add_coro_handler(coro_handler, methods); + break; + } + + root = root->insert_child( + path[i], std::make_shared(path.substr(i, p - i))); + + i = find_pos(path, type_slash, p); + + root = root->insert_child( + type_colon, + std::make_shared(path.substr(p + 1, i - p - 1))); + ++param_count; + + if (i == n) { + code = root->add_coro_handler(coro_handler, methods); + break; + } + } + else { + root = child; + + if (path[i] == type_colon) { + ++param_count; + i += root->path.size() + 1; + + if (i == n) { + code = root->add_coro_handler(coro_handler, methods); + break; + } + } + else { + auto j = 0UL; + auto m = root->path.size(); + + for (; i < n && j < m && path[i] == root->path[j]; ++i, ++j) { + } + + if (j < m) { + std::shared_ptr child( + std::make_shared(root->path.substr(j))); + child->handlers = root->handlers; + child->indices = root->indices; + child->children = root->children; + + root->path = root->path.substr(0, j); + root->handlers = {}; + root->indices = child->path[0]; + root->children = {child}; + } + + if (i == n) { + code = root->add_coro_handler(coro_handler, methods); + break; + } + } + } + } + + if (param_count > this->root->max_params) + this->root->max_params = param_count; + + return code; + } + parse_result get(const std::string &path, const std::string &method) { - params_t params = {{}, 0}; + std::unordered_map params; auto root = this->root; int i = 0, n = path.size(), p; @@ -205,15 +346,12 @@ class radix_tree { root = root->children[0]; p = find_pos(path, type_slash, i); - params.parameters.push_back( - paramters_t{root->path, path.substr(i, p - i)}); - params.size++; + params[root->path] = path.substr(i, p - i); i = p; } else if (root->indices[0] == type_asterisk) { root = root->children[0]; - params.parameters.push_back(paramters_t{root->path, path.substr(i)}); - params.size++; + params[root->path] = path.substr(i); break; } else { @@ -227,12 +365,45 @@ class radix_tree { return parse_result{true, root->get_handler(method), params}; } + coro_result get_coro(const std::string &path, const std::string &method) { + std::unordered_map params; + auto root = this->root; + + int i = 0, n = path.size(), p; + + while (i < n) { + if (root->indices.empty()) + return coro_result(); + + if (root->indices[0] == type_colon) { + root = root->children[0]; + + p = find_pos(path, type_slash, i); + params[root->path] = path.substr(i, p - i); + i = p; + } + else if (root->indices[0] == type_asterisk) { + root = root->children[0]; + params[root->path] = path.substr(i); + break; + } + else { + root = root->get_child(path[i]); + if (!root || path.substr(i, root->path.size()) != root->path) + return coro_result(); + i += root->path.size(); + } + } + + return coro_result{true, root->get_coro_handler(method), params}; + } + private: int find_pos(const std::string &str, char target, int start) { auto i = str.find(target, start); return i == -1 ? str.size() : i; } - radix_tree_node *root; + std::shared_ptr root; }; } // namespace cinatra \ No newline at end of file diff --git a/tests/test_coro_http_server.cpp b/tests/test_coro_http_server.cpp index 99f43425..b1f8cf77 100644 --- a/tests/test_coro_http_server.cpp +++ b/tests/test_coro_http_server.cpp @@ -962,15 +962,6 @@ TEST_CASE("test http download server") { TEST_CASE("test restful api") { cinatra::coro_http_server server(1, 9001); - server.set_http_handler( - "/test/:id/test2/:name", - [](coro_http_request &req, coro_http_response &response) { - CHECK(req.params_.parameters[0].first == "id"); - CHECK(req.params_.parameters[0].second == "11"); - CHECK(req.params_.parameters[1].first == "name"); - CHECK(req.params_.parameters[1].second == "cpp"); - response.set_status_and_content(status_type::ok, "ok"); - }); server.set_http_handler( "/test2/{}/test3/{}", @@ -997,11 +988,123 @@ TEST_CASE("test restful api") { std::this_thread::sleep_for(200ms); coro_http_client client; - client.get("http://127.0.0.1:9001/test/11/test2/cpp"); client.get("http://127.0.0.1:9001/test2/name/test3/test"); client.get("http://127.0.0.1:9001/numbers/100/test/200"); } +TEST_CASE("test radix tree restful api") { + cinatra::coro_http_server server(1, 9001); + + server.set_http_handler( + "/", [](coro_http_request &req, coro_http_response &response) { + response.set_status_and_content(status_type::ok, "ok"); + }); + + server.set_http_handler( + "/user/:id", [](coro_http_request &req, coro_http_response &response) { + CHECK(req.params_["id"] == "cinatra"); + response.set_status_and_content(status_type::ok, "ok"); + }); + + server.set_http_handler( + "/user/:id/subscriptions", + [](coro_http_request &req, coro_http_response &response) { + CHECK(req.params_["id"] == "subid"); + response.set_status_and_content(status_type::ok, "ok"); + }); + + server.set_http_handler( + "/users/:userid/subscriptions/:subid", + [](coro_http_request &req, coro_http_response &response) { + CHECK(req.params_["userid"] == "ultramarines"); + CHECK(req.params_["subid"] == "guilliman"); + response.set_status_and_content(status_type::ok, "ok"); + }); + + server.set_http_handler( + "/values/:x/:y/:z", + [](coro_http_request &req, coro_http_response &response) { + CHECK(req.params_["x"] == "guilliman"); + CHECK(req.params_["y"] == "cawl"); + CHECK(req.params_["z"] == "yvraine"); + response.set_status_and_content(status_type::ok, "ok"); + }); + + server.async_start(); + std::this_thread::sleep_for(200ms); + + coro_http_client client; + client.get("http://127.0.0.1:9001/user/cinatra"); + client.get("http://127.0.0.1:9001/user/subid/subscriptions"); + client.get("http://127.0.0.1:9001/user/ultramarines/subscriptions/guilliman"); + client.get("http://127.0.0.1:9001/value/guilliman/cawl/yvraine"); +} + +TEST_CASE("test coro radix tree restful api") { + cinatra::coro_http_server server(1, 9001); + + server.set_http_handler( + "/", + [](coro_http_request &req, + coro_http_response &response) -> async_simple::coro::Lazy { + co_await coro_io::post([&]() { + response.set_status_and_content(status_type::ok, "ok"); + }); + }); + + server.set_http_handler( + "/user/:id", + [](coro_http_request &req, + coro_http_response &response) -> async_simple::coro::Lazy { + co_await coro_io::post([&]() { + CHECK(req.params_["id"] == "cinatra"); + response.set_status_and_content(status_type::ok, "ok"); + }); + }); + + server.set_http_handler( + "/user/:id/subscriptions", + [](coro_http_request &req, + coro_http_response &response) -> async_simple::coro::Lazy { + co_await coro_io::post([&] { + CHECK(req.params_["id"] == "subid"); + response.set_status_and_content(status_type::ok, "ok"); + }); + }); + + server.set_http_handler( + "/users/:userid/subscriptions/:subid", + [](coro_http_request &req, + coro_http_response &response) -> async_simple::coro::Lazy { + co_await coro_io::post([&] { + CHECK(req.params_["userid"] == "ultramarines"); + CHECK(req.params_["subid"] == "guilliman"); + response.set_status_and_content(status_type::ok, "ok"); + }); + }); + + server.set_http_handler( + "/values/:x/:y/:z", + [](coro_http_request &req, + coro_http_response &response) -> async_simple::coro::Lazy { + co_await coro_io::post([&] { + CHECK(req.params_["x"] == "guilliman"); + CHECK(req.params_["y"] == "cawl"); + CHECK(req.params_["z"] == "yvraine"); + response.set_status_and_content(status_type::ok, "ok"); + }); + }); + + server.async_start(); + std::this_thread::sleep_for(200ms); + + coro_http_client client; + client.get("http://127.0.0.1:9001/user/cinatra"); + client.get("http://127.0.0.1:9001/user/subid/subscriptions"); + client.get("http://127.0.0.1:9001/user/ultramarines/subscriptions/guilliman"); + client.get("http://127.0.0.1:9001/value/guilliman/cawl/yvraine"); +} + DOCTEST_MSVC_SUPPRESS_WARNING_WITH_PUSH(4007) int main(int argc, char **argv) { return doctest::Context(argc, argv).run(); } DOCTEST_MSVC_SUPPRESS_WARNING_POP \ No newline at end of file From 4b0ece81aa38e18f8eb259eb75cc0d7f88198292 Mon Sep 17 00:00:00 2001 From: helintong Date: Tue, 9 Jan 2024 19:18:18 +0800 Subject: [PATCH 4/6] feat: conflict solve --- include/cinatra/coro_http_router.hpp | 20 ++++++++------------ 1 file changed, 8 insertions(+), 12 deletions(-) diff --git a/include/cinatra/coro_http_router.hpp b/include/cinatra/coro_http_router.hpp index b947dbe2..5e94153c 100644 --- a/include/cinatra/coro_http_router.hpp +++ b/include/cinatra/coro_http_router.hpp @@ -80,14 +80,12 @@ class coro_http_router { 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(handler)); - if (!aspects.empty()) { - has_aspects_ = true; - aspects_.emplace(*it, std::move(aspects)); - } } else { if (whole_str.find(':') != std::string::npos) { @@ -114,12 +112,10 @@ class coro_http_router { return; } map_handles_.emplace(*it, std::move(handler)); - } - - map_handles_.emplace(*it, std::move(handler)); - if (!aspects.empty()) { - has_aspects_ = true; - aspects_.emplace(*it, std::move(aspects)); + if (!aspects.empty()) { + has_aspects_ = true; + aspects_.emplace(*it, std::move(aspects)); + } } } } From 9f662bb3f9a01df28b9c05715db28c846a8f9035 Mon Sep 17 00:00:00 2001 From: helintong Date: Tue, 9 Jan 2024 19:21:00 +0800 Subject: [PATCH 5/6] fix: conflect slove --- include/cinatra/coro_http_connection.hpp | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/include/cinatra/coro_http_connection.hpp b/include/cinatra/coro_http_connection.hpp index 504aee64..b2c5ed0e 100644 --- a/include/cinatra/coro_http_connection.hpp +++ b/include/cinatra/coro_http_connection.hpp @@ -206,7 +206,7 @@ class coro_http_connection std::tie(is_exist, handler, request_.params_) = router_.get_router_tree()->get(url_path, method_str); if (is_exist) { - router_.route(&handler, request_, response_); + (handler)(request_, response_); } else { bool is_coro_exist = false; @@ -218,8 +218,7 @@ class coro_http_connection router_.get_coro_router_tree()->get_coro(url_path, method_str); if (is_coro_exist) { - std::cout << "come exit here!\n"; - co_await router_.route_coro(&coro_handler, request_, response_); + co_await (coro_handler)(request_, response_); } else { bool is_matched_regex_router = false; From 914a34e71ba3b57edc7ba0faf76b28058b1af732 Mon Sep 17 00:00:00 2001 From: helintong Date: Wed, 10 Jan 2024 10:35:17 +0800 Subject: [PATCH 6/6] feat: clang-format fix --- include/cinatra/coro_http_request.hpp | 5 +---- include/cinatra/coro_http_router.hpp | 4 ---- 2 files changed, 1 insertion(+), 8 deletions(-) diff --git a/include/cinatra/coro_http_request.hpp b/include/cinatra/coro_http_request.hpp index 7b67df71..94cda89b 100644 --- a/include/cinatra/coro_http_request.hpp +++ b/include/cinatra/coro_http_request.hpp @@ -1,8 +1,7 @@ #pragma once -#include #include - +#include #include "async_simple/coro/Lazy.h" #include "define.h" @@ -11,8 +10,6 @@ namespace cinatra { - - inline std::vector split_sv(std::string_view s, std::string_view delimiter) { size_t start = 0; diff --git a/include/cinatra/coro_http_router.hpp b/include/cinatra/coro_http_router.hpp index 5e94153c..fc0503a6 100644 --- a/include/cinatra/coro_http_router.hpp +++ b/include/cinatra/coro_http_router.hpp @@ -192,7 +192,6 @@ class coro_http_router { const auto& get_coro_handlers() const { return coro_handles_; } - std::shared_ptr get_router_tree() { return router_tree_; } std::shared_ptr get_coro_router_tree() { @@ -235,7 +234,6 @@ class coro_http_router { void handle_after() {} - private: std::set keys_; std::unordered_map< @@ -249,7 +247,6 @@ class coro_http_router { coro_http_request& req, coro_http_response& resp)>> coro_handles_; - std::shared_ptr router_tree_ = std::make_shared(radix_tree()); @@ -270,6 +267,5 @@ class coro_http_router { std::vector>> aspects_; bool has_aspects_ = false; - }; } // namespace cinatra \ No newline at end of file