From bf1c8747a431d01086239deefc0117792c38aa29 Mon Sep 17 00:00:00 2001 From: qicosmos Date: Sat, 20 Sep 2025 08:12:32 +0800 Subject: [PATCH] update --- examples/server/qps.h | 7 +- include/rest_rpc/asio_util.hpp | 34 +++++ include/rest_rpc/client.hpp | 174 +++++++++++++++++++++++++ include/rest_rpc/codec.h | 6 + include/rest_rpc/error_code.h | 75 +++++++++++ include/rest_rpc/io_context_pool.hpp | 16 +++ include/rest_rpc/logger.hpp | 72 ++++++++++ include/rest_rpc/meta_util.hpp | 8 +- include/rest_rpc/rest_rpc_protocol.hpp | 35 +++++ include/rest_rpc/rest_rpc_server.hpp | 173 ++++++++++++++++++++++++ include/rest_rpc/rpc_connection.hpp | 72 ++++++++++ include/rest_rpc/rpc_router.hpp | 168 ++++++++++++++++++++++++ include/rest_rpc/string_resize.hpp | 126 ++++++++++++++++++ include/rest_rpc/traits.h | 174 +++++++++++++++++++++++++ include/rest_rpc/use_asio.hpp | 3 + tests/CMakeLists.txt | 3 + tests/doctest/doctest.h | 2 +- tests/test_rest_rpc1.cpp | 114 ++++++++++++++++ 18 files changed, 1258 insertions(+), 4 deletions(-) create mode 100644 include/rest_rpc/asio_util.hpp create mode 100644 include/rest_rpc/client.hpp create mode 100644 include/rest_rpc/error_code.h create mode 100644 include/rest_rpc/logger.hpp create mode 100644 include/rest_rpc/rest_rpc_protocol.hpp create mode 100644 include/rest_rpc/rest_rpc_server.hpp create mode 100644 include/rest_rpc/rpc_connection.hpp create mode 100644 include/rest_rpc/rpc_router.hpp create mode 100644 include/rest_rpc/string_resize.hpp create mode 100644 include/rest_rpc/traits.h create mode 100644 tests/test_rest_rpc1.cpp diff --git a/examples/server/qps.h b/examples/server/qps.h index 5a700bf..d29d5e1 100644 --- a/examples/server/qps.h +++ b/examples/server/qps.h @@ -10,7 +10,12 @@ public: qps() : counter_(0) { thd_ = std::thread([this] { while (!stop_) { - std::cout << "qps: " << counter_.load(std::memory_order_acquire) + auto val = counter_.load(std::memory_order_acquire); + if(val==0) { + continue; + } + + std::cout << "qps: " << val << '\n'; std::this_thread::sleep_for(std::chrono::seconds(1)); // counter_.store(0, std::memory_order_release); diff --git a/include/rest_rpc/asio_util.hpp b/include/rest_rpc/asio_util.hpp new file mode 100644 index 0000000..d8cdd2a --- /dev/null +++ b/include/rest_rpc/asio_util.hpp @@ -0,0 +1,34 @@ +#pragma once +#include "use_asio.hpp" + +namespace rest_rpc { +inline auto async_start(auto executor, auto &&coro) { + using R = typename std::remove_cvref_t::value_type; + static_assert(std::is_void_v); + asio::co_spawn(executor, std::move(coro), asio::detached); +} + +inline auto async_future(auto executor, auto &&coro) { + return asio::co_spawn(executor, std::move(coro), asio::use_future); +} + +inline auto sync_wait(auto executor, auto &&coro) { + return async_future(executor, std::move(coro)).get(); +} + +inline auto async_start(auto executor, auto &&coro, auto callback) { + using R = typename std::remove_cvref_t::value_type; + if constexpr (std::is_void_v) { + asio::co_spawn( + executor, std::move(coro), + asio::any_completion_handler( + [cb = std::move(callback)](std::exception_ptr e) { cb(e); })); + } else { + asio::co_spawn(executor, std::move(coro), + asio::any_completion_handler( + [cb = std::move(callback)](std::exception_ptr e, R r) { + cb(e, std::move(r)); + })); + } +} +} // namespace rest_rpc \ No newline at end of file diff --git a/include/rest_rpc/client.hpp b/include/rest_rpc/client.hpp new file mode 100644 index 0000000..e4744ce --- /dev/null +++ b/include/rest_rpc/client.hpp @@ -0,0 +1,174 @@ +#pragma once +#include "asio_util.hpp" +#include "codec.h" +#include "error_code.h" +#include "io_context_pool.hpp" +#include "logger.hpp" +#include "meta_util.hpp" +#include "rest_rpc_protocol.hpp" +#include "traits.h" +#include "use_asio.hpp" +#include +#include +using namespace asio::experimental::awaitable_operators; + +namespace rest_rpc { +template struct call_result { + rpc_errc ec; + R value; +}; + +class client { +public: + client() : socket_(get_global_executor()) {} + + auto get_executor() { return socket_.get_executor(); } + + asio::awaitable connect( + std::string_view host, std::string_view port, + std::chrono::steady_clock::duration duration = std::chrono::seconds(5)) { + asio::ip::tcp::resolver resolver(socket_.get_executor()); + + auto r = co_await (watchdog(duration) || + resolver.async_resolve( + host, port, asio::as_tuple(asio::use_awaitable))); + if (r.index() == 0) { + REST_LOG_ERROR << "resolve timeout"; + co_return make_error_code(rpc_errc::resolve_timeout); + } + + auto [ec, endpoints] = std::get<1>(r); + if (ec) { + REST_LOG_ERROR << "resolve failed"; + co_return ec; + } + auto it = endpoints.begin(); + + if (it == endpoints.end()) { + REST_LOG_ERROR << "resolve failed"; + co_return std::make_error_code(std::errc::bad_address); + } + + auto endpoint = it->endpoint(); + auto conn_r = co_await ( + watchdog(duration) || + socket_.async_connect(endpoint, asio::as_tuple(asio::use_awaitable))); + if (conn_r.index() == 0) { + REST_LOG_ERROR << "connect timeout"; + co_return make_error_code(rpc_errc::connection_timeout); + } + + auto [conn_ec] = std::get<1>(conn_r); + if (conn_ec) { + REST_LOG_ERROR << "connect failed"; + co_return conn_ec; + } + + co_return std::error_code{}; + } + + asio::awaitable connect( + std::string_view address, + std::chrono::steady_clock::duration duration = std::chrono::seconds(5)) { + std::string_view host; + std::string_view port; + size_t pos = address.find(':'); + if (pos != std::string::npos) { + host = address.substr(0, pos); + port = address.substr(pos + 1); + } + + return connect(host, port, duration); + } + + template + asio::awaitable< + call_result::return_type>> + call(Args &&...args) { + return call_for(std::chrono::seconds(5), std::forward(args)...); + } + + template + asio::awaitable< + call_result::return_type>> + call_for(auto duration, Args &&...args) { + using args_tuple = typename function_traits::tuple_type; + static_assert(std::is_constructible_v, + "called rpc function and arguments are not match"); + + using R = typename function_traits::return_type; + auto r = co_await (watchdog(duration) || + call_impl(std::forward(args)...)); + if (r.index() == 0) { + co_return call_result{rpc_errc::request_timeout}; + } + co_return std::get<1>(r); + } + +private: + template + asio::awaitable< + call_result::return_type>> + call_impl(Args &&...args) { + rest_rpc_header header{}; + header.magic = 39; + header.function_id = get_key(); + rpc_service::msgpack_codec codec; + auto buf = codec.pack_args(std::forward(args)...); + header.body_len = buf.size(); + prepare_for_send(header); + + std::vector buffers; + buffers.reserve(2); + buffers.push_back(asio::buffer(&header, sizeof(rest_rpc_header))); + buffers.push_back(asio::buffer(buf.data(), buf.size())); + + using R = typename function_traits::return_type; + call_result result{}; + std::error_code ec; + size_t size; + std::tie(ec, size) = co_await asio::async_write( + socket_, buffers, asio::as_tuple(asio::use_awaitable)); + if (ec) { + result.ec = rpc_errc::write_error; + co_return result; + } + + rest_rpc_header resp_header; + std::tie(ec, size) = co_await asio::async_read( + socket_, asio::buffer(&resp_header, sizeof(rest_rpc_header)), + asio::as_tuple(asio::use_awaitable)); + if (ec) { + result.ec = rpc_errc::write_error; + co_return result; + } + if (resp_header.magic != 39) { + result.ec = rpc_errc::protocol_error; + co_return result; + } + parse_recieved(resp_header); + + detail::resize(body_, resp_header.body_len); + std::tie(ec, size) = co_await asio::async_read( + socket_, asio::buffer(body_), asio::as_tuple(asio::use_awaitable)); + if (ec) { + REST_LOG_WARNING << "read body error: " << ec.message(); + result.ec = rpc_errc::read_error; + co_return result; + } + result.ec = (rpc_errc)body_[0]; + result.value = codec.unpack(body_.data() + 1, resp_header.body_len - 1); + co_return result; + } + + asio::awaitable watchdog(auto duration) { + asio::steady_timer timer(socket_.get_executor()); + timer.expires_after(duration); + auto [ec] = co_await timer.async_wait(asio::as_tuple(asio::use_awaitable)); + co_return ec; + } + + tcp_socket socket_; + std::string body_; +}; +} // namespace rest_rpc \ No newline at end of file diff --git a/include/rest_rpc/codec.h b/include/rest_rpc/codec.h index 5c95b80..07c1717 100644 --- a/include/rest_rpc/codec.h +++ b/include/rest_rpc/codec.h @@ -16,6 +16,12 @@ struct msgpack_codec { return buffer; } + template static std::string pack_to_string(Arg &arg) { + buffer_type buffer(init_size); + msgpack::pack(buffer, arg); + return std::string(buffer.data(), buffer.size()); + } + template ::value>::type> static std::string pack_args_str(Arg arg, Args &&...args) { diff --git a/include/rest_rpc/error_code.h b/include/rest_rpc/error_code.h new file mode 100644 index 0000000..042f764 --- /dev/null +++ b/include/rest_rpc/error_code.h @@ -0,0 +1,75 @@ +#pragma once +#include +#include + +namespace rest_rpc { +enum class rpc_errc : std::int8_t { + ok = 0, + no_such_function, + no_such_key, + invalid_req_type, + function_exception, + function_unknown_exception, + invalid_argument, + write_error, + read_error, + socket_closed, + resolve_timeout, + connection_timeout, + request_timeout, + protocol_error +}; + +class rpc_error_category : public std::error_category { +public: + const char *name() const noexcept override { return "rest_rpc_error"; } + + std::string message(int ev) const override { + switch (static_cast(ev)) { + case rpc_errc::ok: + return "ok"; + case rpc_errc::no_such_function: + return "no such function"; + case rpc_errc::no_such_key: + return "resolve failed"; + case rpc_errc::invalid_req_type: + return "invalid request type"; + case rpc_errc::function_exception: + return "logic function exception happend"; + case rpc_errc::function_unknown_exception: + return "unknown function exception happend"; + case rpc_errc::invalid_argument: + return "invalid argument"; + case rpc_errc::write_error: + return "write failed"; + case rpc_errc::read_error: + return "read failed"; + case rpc_errc::socket_closed: + return "socket closed"; + case rpc_errc::resolve_timeout: + return "resolve timeout"; + case rpc_errc::connection_timeout: + return "connect timeout"; + case rpc_errc::request_timeout: + return "request timeout"; + case rpc_errc::protocol_error: + return "protocol error"; + default: + return "unknown error"; + } + } +}; + +inline rest_rpc::rpc_error_category &category() { + static rest_rpc::rpc_error_category instance; + return instance; +} + +inline std::error_code make_error_code(rpc_errc e) { + return {static_cast(e), category()}; +} + +inline bool operator==(const std::error_code &code, rpc_errc ec) { + return code.value() == (int)ec; +} +} // namespace rest_rpc \ No newline at end of file diff --git a/include/rest_rpc/io_context_pool.hpp b/include/rest_rpc/io_context_pool.hpp index 2e8ca9a..4f82c25 100644 --- a/include/rest_rpc/io_context_pool.hpp +++ b/include/rest_rpc/io_context_pool.hpp @@ -45,6 +45,11 @@ public: asio::io_context &get_io_context() { return *get_io_context_ptr(); } + auto get_executor() { + auto &ctx = get_io_context(); + return ctx.get_executor(); + } + private: std::vector> io_contexts_; std::vector> @@ -53,4 +58,15 @@ private: std::once_flag stop_flag_; std::atomic next_ = 0; }; + +inline auto +get_global_executor(unsigned pool_size = std::thread::hardware_concurrency()) { + static auto g_io_context_pool = std::make_shared(pool_size); + [[maybe_unused]] static bool run_helper = [](auto pool) { + std::thread thrd{[pool] { pool->run(); }}; + thrd.detach(); + return true; + }(g_io_context_pool); + return g_io_context_pool->get_executor(); +} } // namespace rest_rpc \ No newline at end of file diff --git a/include/rest_rpc/logger.hpp b/include/rest_rpc/logger.hpp new file mode 100644 index 0000000..b44d322 --- /dev/null +++ b/include/rest_rpc/logger.hpp @@ -0,0 +1,72 @@ +#pragma once +#include + +namespace rest_rpc { +struct null_logger_t { + template const null_logger_t &operator<<(T &&) const { + return *this; + } +}; +struct cout_logger_t { + template const cout_logger_t &operator<<(T &&t) const { + std::cout << std::forward(t); + return *this; + } + ~cout_logger_t() { std::cout << std::endl; } +}; +struct cerr_logger_t { + template const cerr_logger_t &operator<<(T &&t) const { + std::cerr << std::forward(t); + return *this; + } + ~cerr_logger_t() { std::cerr << std::endl; } +}; + +constexpr inline rest_rpc::null_logger_t NULL_LOGGER; +} // namespace rest_rpc + +#ifdef REST_LOG_ERROR +#else +#define REST_LOG_ERROR \ + rest_rpc::cerr_logger_t {} +#endif + +#ifdef REST_LOG_WARNING +#else +#ifndef NDEBUG +#define REST_LOG_WARNING \ + rest_rpc::cerr_logger_t {} +#else +#define REST_LOG_WARNING rest_rpc::NULL_LOGGER +#endif +#endif + +#ifdef REST_LOG_INFO +#else +#ifndef NDEBUG +#define REST_LOG_INFO \ + rest_rpc::cout_logger_t {} +#else +#define REST_LOG_INFO rest_rpc::NULL_LOGGER +#endif +#endif + +#ifdef REST_LOG_DEBUG +#else +#ifndef NDEBUG +#define REST_LOG_DEBUG \ + rest_rpc::cout_logger_t {} +#else +#define REST_LOG_DEBUG rest_rpc::NULL_LOGGER +#endif +#endif + +#ifdef REST_LOG_TRACE +#else +#ifndef NDEBUG +#define REST_LOG_TRACE \ + rest_rpc::cout_logger_t {} +#else +#define REST_LOG_TRACE rest_rpc::NULL_LOGGER +#endif +#endif \ No newline at end of file diff --git a/include/rest_rpc/meta_util.hpp b/include/rest_rpc/meta_util.hpp index abbb2b8..a62f02a 100644 --- a/include/rest_rpc/meta_util.hpp +++ b/include/rest_rpc/meta_util.hpp @@ -3,6 +3,7 @@ #include "cplusplus_14.h" #include +#include namespace rest_rpc { @@ -31,12 +32,15 @@ public: using stl_function_type = std::function; typedef Ret (*pointer)(Arg, Args...); - typedef std::tuple tuple_type; + typedef std::tuple>, + std::remove_const_t>...> + tuple_type; typedef std::tuple< nonstd::remove_const_t>...> bare_tuple_type; using args_tuple = - std::tuple>, nonstd::remove_const_t>...>; using args_tuple_2nd = std::tuple +#ifdef _WIN32 +#include +#else +#include +#endif + +namespace rest_rpc { +inline constexpr uint8_t REST_MAGIC_NUM = 39; +struct rest_rpc_header { + uint8_t magic; + uint8_t version; + uint8_t serialize_type; + uint8_t msg_type; + uint32_t function_id; + uint64_t seq_num; + uint64_t body_len; + uint64_t attach_length; +}; + +inline void prepare_for_send(rest_rpc_header &header) { + header.function_id = htonl(header.function_id); + header.seq_num = htonll(header.seq_num); + header.body_len = htonll(header.body_len); + header.attach_length = htonll(header.attach_length); +} + +inline void parse_recieved(rest_rpc_header &header) { + header.function_id = ntohl(header.function_id); + header.seq_num = ntohll(header.seq_num); + header.body_len = ntohll(header.body_len); + header.attach_length = ntohll(header.attach_length); +} +} // namespace rest_rpc \ No newline at end of file diff --git a/include/rest_rpc/rest_rpc_server.hpp b/include/rest_rpc/rest_rpc_server.hpp new file mode 100644 index 0000000..265baea --- /dev/null +++ b/include/rest_rpc/rest_rpc_server.hpp @@ -0,0 +1,173 @@ +#pragma once +#include "io_context_pool.hpp" +#include "logger.hpp" +#include "rpc_connection.hpp" +#include "use_asio.hpp" +#include +#include +namespace rest_rpc { +class rest_rpc_server { +public: + rest_rpc_server(std::string address, + size_t num_thread = std::thread::hardware_concurrency()) + : io_context_pool_(num_thread), + acceptor_(io_context_pool_.get_io_context()) { + size_t pos = address.find(':'); + if (pos != std::string::npos) { + host_ = address.substr(0, pos); + port_ = address.substr(pos + 1); + } + } + + rest_rpc_server(std::string host, std::string port, + size_t num_thread = std::thread::hardware_concurrency()) + : io_context_pool_(num_thread), + acceptor_(io_context_pool_.get_io_context()), host_(std::move(host)), + port_(std::move(port)) {} + ~rest_rpc_server() { stop(); } + + std::error_code start() { return start_impl(false); } + + std::error_code async_start() { return start_impl(true); } + + void stop() { + if (has_stop_.load(std::memory_order_acquire)) { + return; + } + + std::call_once(stop_flag_, [this] { + has_stop_.store(true, std::memory_order_release); + asio::dispatch(acceptor_.get_executor(), [this]() { + asio::error_code ec; + (void)acceptor_.cancel(ec); + (void)acceptor_.close(ec); + }); + + io_context_pool_.stop(); + }); + + if (thd_.joinable()) { + thd_.join(); + } + } + + bool has_stopped() const { return has_stop_.load(std::memory_order_acquire); } + + template + void register_handler(std::string_view name, const Function &f, + Self *self = nullptr) { + router_.register_handler(name, f, self); + } + + template + void register_handler(Self *self = nullptr) { + router_.register_handler(self); + } + + void remove_handler(std::string_view name) { router_.remove_handler(name); } + + template void remove_handler() { router_.remove_handler(); } + +private: + std::error_code listen() { + using asio::ip::tcp; + asio::error_code ec; + asio::ip::tcp::resolver resolver(acceptor_.get_executor()); + auto endpoints = resolver.resolve(host_, port_, ec); + if (ec) { + return ec; + } + + auto it = endpoints.begin(); + + if (it == endpoints.end()) { + return std::make_error_code(std::errc::bad_address); + } + + auto endpoint = it->endpoint(); + acceptor_.open(endpoint.protocol(), ec); + if (ec) { + return ec; + } + +#ifdef __GNUC__ + acceptor_.set_option(tcp::acceptor::reuse_address(true), ec); +#endif + acceptor_.bind(endpoint, ec); + if (ec) { + std::error_code ignore; + acceptor_.cancel(ignore); + acceptor_.close(ignore); + return ec; + } +#ifdef _MSC_VER + acceptor_.set_option(tcp::acceptor::reuse_address(true)); +#endif + acceptor_.listen(asio::socket_base::max_listen_connections, ec); + if (ec) { + std::error_code ignore; + acceptor_.cancel(ignore); + acceptor_.close(ignore); + return ec; + } + return ec; + } + + asio::awaitable accept() { + uint64_t conn_id = 0; + while (true) { + tcp_socket socket(io_context_pool_.get_io_context()); + auto [ec] = co_await acceptor_.async_accept( + socket, asio::as_tuple(asio::use_awaitable)); + if (ec == asio::error::operation_aborted || + ec == asio::error::bad_descriptor) { + REST_LOG_WARNING << "acceptor error: " << ec.message(); + co_return; + } + + REST_LOG_INFO << "new connction comming..."; + auto conn = + std::make_shared(std::move(socket), conn_id, router_); + conns_.emplace(conn_id++, conn); + co_spawn(socket.get_executor(), conn->start(), asio::detached); + } + } + + std::error_code start_impl(bool async) { + if (has_stop_.load(std::memory_order_acquire)) { + return std::make_error_code(std::errc::operation_canceled); + } + + static std::error_code ec{}; + std::call_once(start_flag_, [this, async] { + ec = listen(); + if (ec) { + return; + } + + thd_ = std::thread([this] { io_context_pool_.run(); }); + + if (async) { + asio::co_spawn(acceptor_.get_executor(), accept(), asio::detached); + } else { + auto future = asio::co_spawn(acceptor_.get_executor(), accept(), + asio::use_future); + future.wait(); + } + }); + + return ec; + } + + io_context_pool io_context_pool_; + std::thread thd_; + asio::ip::tcp::acceptor acceptor_; + std::string host_; + std::string port_; + std::once_flag start_flag_; + std::once_flag stop_flag_; + std::atomic has_stop_ = false; + std::unordered_map> conns_; + rpc_router router_; +}; +} // namespace rest_rpc \ No newline at end of file diff --git a/include/rest_rpc/rpc_connection.hpp b/include/rest_rpc/rpc_connection.hpp new file mode 100644 index 0000000..3e8ca05 --- /dev/null +++ b/include/rest_rpc/rpc_connection.hpp @@ -0,0 +1,72 @@ +#pragma once +#include "logger.hpp" +#include "rest_rpc_protocol.hpp" +#include "rpc_router.hpp" +#include "string_resize.hpp" +#include "use_asio.hpp" + +namespace rest_rpc { +class rpc_connection { +public: + rpc_connection(tcp_socket socket, uint64_t conn_id, rpc_router &router) + : socket_(std::move(socket)), conn_id_(conn_id), router_(router) {} + + asio::awaitable start() { + rest_rpc_header header; + + while (true) { + std::error_code ec; + size_t size; + std::tie(ec, size) = co_await asio::async_read( + socket_, asio::buffer(&header, sizeof(rest_rpc_header)), + asio::as_tuple(asio::use_awaitable)); + if (ec) { + REST_LOG_INFO << "read head error: " << ec.message(); + break; + } + + parse_recieved(header); + if (header.magic != REST_MAGIC_NUM) { + REST_LOG_ERROR << "protocol error"; + break; + } + + detail::resize(body_, header.body_len); + std::tie(ec, size) = co_await asio::async_read( + socket_, asio::buffer(body_), asio::as_tuple(asio::use_awaitable)); + if (ec) { + REST_LOG_WARNING << "read body error: " << ec.message(); + break; + } + + // route + auto result = router_.route(header.function_id, body_); + rest_rpc_header resp_header{}; + resp_header.magic = 39; + resp_header.body_len = result.result.size() + 1; + prepare_for_send(resp_header); + std::vector buffers; + buffers.reserve(3); + buffers.push_back(asio::buffer(&resp_header, sizeof(rest_rpc_header))); + buffers.push_back(asio::buffer(&result.ec, 1)); + buffers.push_back(asio::buffer(result.result)); + std::tie(ec, size) = co_await asio::async_write( + socket_, buffers, asio::as_tuple(asio::use_awaitable)); + if (ec) { + REST_LOG_WARNING << "write error: " << ec.message(); + break; + } + } + + co_return; + } + + uint64_t id() const { return conn_id_; } + +private: + tcp_socket socket_; + uint64_t conn_id_; + std::string body_; + rpc_router &router_; +}; +} // namespace rest_rpc \ No newline at end of file diff --git a/include/rest_rpc/rpc_router.hpp b/include/rest_rpc/rpc_router.hpp new file mode 100644 index 0000000..a39cc26 --- /dev/null +++ b/include/rest_rpc/rpc_router.hpp @@ -0,0 +1,168 @@ +#pragma once +#include "codec.h" +#include "error_code.h" +#include "function_name.h" +#include "md5.hpp" +#include "meta_util.hpp" +#include +#include +#include +#include + +namespace rest_rpc { +struct rpc_result { + rpc_errc ec = rpc_errc::ok; + std::string result; +}; + +template constexpr uint32_t get_key() { + constexpr auto name = get_func_name(); + constexpr uint32_t key = MD5::MD5Hash32(name.data(), name.length()); + return key; +} + +class rpc_router { +public: + template + void register_handler(std::string_view name, const Function &f, + Self *self = nullptr) { + uint32_t key = MD5::MD5Hash32(name.data(), name.length()); + register_handler_impl(key, name, f, self); + } + + template + void register_handler(Self *self = nullptr) { + constexpr auto name = get_func_name(); + return register_handler(name, func, self); + } + + void remove_handler(std::string_view name) { + uint32_t key = MD5::MD5Hash32(name.data(), name.length()); + this->map_invokers_.erase(key); + key2func_name_.erase(key); + } + + template void remove_handler() { + constexpr std::string_view name = get_func_name(); + remove_handler(name); + } + + std::string get_name_by_key(uint32_t key) { + auto it = key2func_name_.find(key); + if (it != key2func_name_.end()) { + return it->second; + } + return std::to_string(key); + } + + rpc_result route(uint32_t key, std::string_view data) { + rpc_result route_result{}; + std::string result; + try { + rpc_service::msgpack_codec codec; + auto it = map_invokers_.find(key); + if (it == map_invokers_.end()) { + result = "unknown function: " + get_name_by_key(key); + route_result.ec = rpc_errc::no_such_function; + } else { + it->second(data, route_result.ec, result); + route_result.ec = rpc_errc::ok; + } + } catch (const std::exception &ex) { + rpc_service::msgpack_codec codec; + result = std::string("exception occur when call").append(ex.what()); + route_result.ec = rpc_errc::function_exception; + } catch (...) { + rpc_service::msgpack_codec codec; + result = std::string("unknown exception occur when call ") + .append(get_name_by_key(key)); + route_result.ec = rpc_errc::function_unknown_exception; + } + + route_result.result = std::move(result); + + return route_result; + } + +private: + template + auto register_handler_impl(uint32_t key, std::string_view name, + const Function &f, Self *self = nullptr) { + if (key2func_name_.find(key) != key2func_name_.end()) { + throw std::invalid_argument("duplicate registration key !"); + } else { + key2func_name_.emplace(key, name); + if constexpr (std::is_void_v) { + return register_nonmember_func(key, f); + } else { + return register_member_func(key, f, self); + } + } + } + + template + void register_nonmember_func(uint32_t key, Function f) { + this->map_invokers_[key] = [f = std::move(f)](std::string_view str, + rpc_errc &ec, + std::string &result) mutable { + using args_tuple = typename function_traits::tuple_type; + using R = typename function_traits::return_type; + rpc_service::msgpack_codec codec; + try { + auto tp = codec.unpack(str.data(), str.size()); + if constexpr (std::is_void_v) { + std::apply(f, tp); + } else { + auto r = std::apply(f, tp); + result = rpc_service::msgpack_codec::pack_to_string(r); + } + } catch (std::invalid_argument &e) { + ec = rpc_errc::invalid_argument; + result = e.what(); + } catch (const std::exception &e) { + ec = rpc_errc::function_exception; + result = e.what(); + } + }; + } + + template + void register_member_func(uint32_t key, const Function &f, Self *self) { + this->map_invokers_[key] = [f, self](std::string_view str, rpc_errc &ec, + std::string &result) { + using args_tuple = typename function_traits::tuple_type; + using R = typename function_traits::return_type; + rpc_service::msgpack_codec codec; + try { + auto tp = codec.unpack(str.data(), str.size()); + + if constexpr (std::is_void_v) { + std::apply( + [self, &f](auto &&...args) { + return (*self.*f)(std::forward(args)...); + }, + tp); + } else { + auto r = std::apply( + [self, &f](auto &&...args) { + return (*self.*f)(std::forward(args)...); + }, + tp); + result = rpc_service::msgpack_codec::pack_to_string(r); + } + } catch (std::invalid_argument &e) { + ec = rpc_errc::invalid_argument; + result = e.what(); + } catch (const std::exception &e) { + ec = rpc_errc::function_exception; + result = e.what(); + } + }; + } + + std::unordered_map> + map_invokers_; + std::unordered_map key2func_name_; +}; +} // namespace rest_rpc \ No newline at end of file diff --git a/include/rest_rpc/string_resize.hpp b/include/rest_rpc/string_resize.hpp new file mode 100644 index 0000000..91c2ec3 --- /dev/null +++ b/include/rest_rpc/string_resize.hpp @@ -0,0 +1,126 @@ +#pragma once +#include +#include +#include + +namespace rest_rpc::detail { + +#if __cpp_lib_string_resize_and_overwrite >= 202110L +template +inline void resize(std::basic_string &str, std::size_t sz) { + str.resize_and_overwrite(sz, [sz](ch *, std::size_t) { return sz; }); +} +#elif (defined(_MSC_VER) && _MSC_VER <= 1920) +// old msvc don't support visit private, discard it. + +#else + +template class string_thief { +public: + friend void string_set_length_hacker(std::string &self, std::size_t sz) { +#if defined(_MSVC_STL_VERSION) + (self.*func_ptr)._Myval2._Mysize = sz; +#else +#if defined(_LIBCPP_VERSION) + (self.*func_ptr)(sz); +#else +#if (_GLIBCXX_USE_CXX11_ABI == 0) && defined(__GLIBCXX__) + (self.*func_ptr)()->_M_set_length_and_sharable(sz); +#else +#if defined(__GLIBCXX__) + (self.*func_ptr)(sz); +#endif +#endif +#endif +#endif + } +}; + +#if defined(__GLIBCXX__) // libstdc++ +#if (_GLIBCXX_USE_CXX11_ABI == 0) +template class string_thief; +#else +template class string_thief; +#endif +#elif defined(_LIBCPP_VERSION) +template class string_thief; +#elif defined(_MSVC_STL_VERSION) +template class string_thief; +#endif + +void string_set_length_hacker(std::string &, std::size_t); + +template +inline void resize(std::basic_string &raw_str, std::size_t sz) { + std::string &str = *reinterpret_cast(&raw_str); +#if defined(__GLIBCXX__) || defined(_LIBCPP_VERSION) || \ + defined(_MSVC_STL_VERSION) + if (sz > str.capacity()) { + str.reserve(sz); + } + string_set_length_hacker(str, sz); + str[sz] = '\0'; +#else + raw_str.resize(sz); +#endif +} + +#endif + +#if (defined(_MSC_VER) && _MSC_VER <= 1920) +#else +void vector_set_length_hacker(std::vector &self, std::size_t sz); + +template class vector_thief { +public: + friend void vector_set_length_hacker(std::vector &self, + std::size_t sz) { +#if defined(_MSVC_STL_VERSION) + (self.*func_ptr)._Myval2._Mylast = self.data() + sz; +#else +#if defined(_LIBCPP_VERSION) +#if _LIBCPP_VERSION < 14000 + ((*(std::__vector_base> *)(&self)).*func_ptr) = + self.data() + sz; +#else + (self.*func_ptr) = self.data() + sz; +#endif +#else +#if defined(__GLIBCXX__) + ((*(std::_Vector_base> *)(&self)).*func_ptr) + ._M_finish = self.data() + sz; +#endif +#endif +#endif + } +}; + +#if defined(__GLIBCXX__) // libstdc++ +template class vector_thief::_M_impl), + &std::vector::_M_impl>; +#elif defined(_LIBCPP_VERSION) +template class vector_thief::__end_), + &std::vector::__end_>; +#elif defined(_MSVC_STL_VERSION) +template class vector_thief::_Mypair), + &std::vector::_Mypair>; +#endif + +template +inline void resize(std::vector &raw_vec, std::size_t sz) { +#if defined(__GLIBCXX__) || \ + (defined(_LIBCPP_VERSION) && defined(_LIBCPP_HAS_NO_ASAN)) || \ + defined(_MSVC_STL_VERSION) + std::vector &vec = *reinterpret_cast *>(&raw_vec); + vec.reserve(sz); + vector_set_length_hacker(vec, sz); +#else + raw_vec.resize(sz); +#endif +} +#endif +}; // namespace rest_rpc::detail \ No newline at end of file diff --git a/include/rest_rpc/traits.h b/include/rest_rpc/traits.h new file mode 100644 index 0000000..44a3d5e --- /dev/null +++ b/include/rest_rpc/traits.h @@ -0,0 +1,174 @@ +#pragma once +#include +#include + +namespace rest_rpc::util { +template struct function_traits; + +template +struct function_traits { + using parameters_type = std::tuple...>; + using return_type = Return; +}; + +template +struct function_traits { + using parameters_type = std::tuple...>; + using return_type = Return; +}; + +template +struct function_traits { + using parameters_type = std::tuple...>; + using return_type = Return; +}; + +template +struct function_traits { + using parameters_type = std::tuple...>; + using return_type = Return; +}; + +template +struct function_traits { + using parameters_type = std::tuple...>; + using return_type = Return; + using class_type = This; +}; + +template +struct function_traits { + using parameters_type = std::tuple...>; + using return_type = Return; + using class_type = This; +}; + +template +struct function_traits { + using parameters_type = std::tuple...>; + using return_type = Return; + using class_type = This; +}; + +template +struct function_traits { + using parameters_type = std::tuple...>; + using return_type = Return; + using class_type = This; +}; + +template struct function_traits { + using parameters_type = void; + using return_type = Return; +}; + +template struct function_traits { + using parameters_type = void; + using return_type = Return; +}; + +template struct function_traits { + using parameters_type = void; + using return_type = Return; +}; + +template struct function_traits { + using parameters_type = void; + using return_type = Return; +}; + +template struct function_traits { + using parameters_type = void; + using return_type = Return; +}; + +template struct function_traits { + using parameters_type = void; + using return_type = Return; +}; + +template +struct function_traits { + using parameters_type = void; + using return_type = Return; + using class_type = This; +}; + +template +struct function_traits { + using parameters_type = void; + using return_type = Return; + using class_type = This; +}; + +template +struct function_traits { + using parameters_type = void; + using return_type = Return; + using class_type = This; +}; + +template +struct function_traits { + using parameters_type = void; + using return_type = Return; + using class_type = This; +}; + +// Support function object and lambda expression +template +struct function_traits : function_traits {}; + +template +using function_parameters_t = + typename function_traits>::parameters_type; + +template +using last_parameters_type_t = + std::tuple_element_t> - 1, + function_parameters_t>; + +template +using function_return_type_t = + typename function_traits>::return_type; + +template +using class_type_t = + typename function_traits>::class_type; + +template +struct is_invocable + : std::is_constructible< + std::function...)>, + std::reference_wrapper::type>> {}; + +template +inline constexpr bool is_invocable_v = is_invocable::value; + +template struct remove_first { using type = T; }; + +template +struct remove_first> { + using type = std::tuple; +}; + +template using remove_first_t = typename remove_first::type; + +template inline auto get_args() { + if constexpr (has_conn) { + using args_type = remove_first_t; + return args_type{}; + } else { + return T{}; + } +} + +template class Ref> +struct is_specialization : std::false_type {}; + +template