mirror of
https://github.com/qicosmos/rest_rpc.git
synced 2026-08-29 08:34:47 +08:00
update
This commit is contained in:
@@ -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<decltype(coro)>::value_type;
|
||||
static_assert(std::is_void_v<R>);
|
||||
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<decltype(coro)>::value_type;
|
||||
if constexpr (std::is_void_v<R>) {
|
||||
asio::co_spawn(
|
||||
executor, std::move(coro),
|
||||
asio::any_completion_handler<void(std::exception_ptr)>(
|
||||
[cb = std::move(callback)](std::exception_ptr e) { cb(e); }));
|
||||
} else {
|
||||
asio::co_spawn(executor, std::move(coro),
|
||||
asio::any_completion_handler<void(std::exception_ptr, R)>(
|
||||
[cb = std::move(callback)](std::exception_ptr e, R r) {
|
||||
cb(e, std::move(r));
|
||||
}));
|
||||
}
|
||||
}
|
||||
} // namespace rest_rpc
|
||||
@@ -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 <asio/experimental/awaitable_operators.hpp>
|
||||
#include <asio/steady_timer.hpp>
|
||||
using namespace asio::experimental::awaitable_operators;
|
||||
|
||||
namespace rest_rpc {
|
||||
template <typename R> 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<std::error_code> 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<std::error_code> 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 <auto func, typename... Args>
|
||||
asio::awaitable<
|
||||
call_result<typename function_traits<decltype(func)>::return_type>>
|
||||
call(Args &&...args) {
|
||||
return call_for<func>(std::chrono::seconds(5), std::forward<Args>(args)...);
|
||||
}
|
||||
|
||||
template <auto func, typename... Args>
|
||||
asio::awaitable<
|
||||
call_result<typename function_traits<decltype(func)>::return_type>>
|
||||
call_for(auto duration, Args &&...args) {
|
||||
using args_tuple = typename function_traits<decltype(func)>::tuple_type;
|
||||
static_assert(std::is_constructible_v<args_tuple, Args...>,
|
||||
"called rpc function and arguments are not match");
|
||||
|
||||
using R = typename function_traits<decltype(func)>::return_type;
|
||||
auto r = co_await (watchdog(duration) ||
|
||||
call_impl<func>(std::forward<Args>(args)...));
|
||||
if (r.index() == 0) {
|
||||
co_return call_result<R>{rpc_errc::request_timeout};
|
||||
}
|
||||
co_return std::get<1>(r);
|
||||
}
|
||||
|
||||
private:
|
||||
template <auto func, typename... Args>
|
||||
asio::awaitable<
|
||||
call_result<typename function_traits<decltype(func)>::return_type>>
|
||||
call_impl(Args &&...args) {
|
||||
rest_rpc_header header{};
|
||||
header.magic = 39;
|
||||
header.function_id = get_key<func>();
|
||||
rpc_service::msgpack_codec codec;
|
||||
auto buf = codec.pack_args(std::forward<Args>(args)...);
|
||||
header.body_len = buf.size();
|
||||
prepare_for_send(header);
|
||||
|
||||
std::vector<asio::const_buffer> 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<decltype(func)>::return_type;
|
||||
call_result<R> 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<R>(body_.data() + 1, resp_header.body_len - 1);
|
||||
co_return result;
|
||||
}
|
||||
|
||||
asio::awaitable<std::error_code> 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
|
||||
@@ -16,6 +16,12 @@ struct msgpack_codec {
|
||||
return buffer;
|
||||
}
|
||||
|
||||
template <typename Arg> 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 <typename Arg, typename... Args,
|
||||
typename = typename std::enable_if<std::is_enum<Arg>::value>::type>
|
||||
static std::string pack_args_str(Arg arg, Args &&...args) {
|
||||
|
||||
@@ -0,0 +1,75 @@
|
||||
#pragma once
|
||||
#include <string>
|
||||
#include <system_error>
|
||||
|
||||
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<rpc_errc>(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<int>(e), category()};
|
||||
}
|
||||
|
||||
inline bool operator==(const std::error_code &code, rpc_errc ec) {
|
||||
return code.value() == (int)ec;
|
||||
}
|
||||
} // namespace rest_rpc
|
||||
@@ -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<std::shared_ptr<asio::io_context>> io_contexts_;
|
||||
std::vector<asio::executor_work_guard<asio::io_context::executor_type>>
|
||||
@@ -53,4 +58,15 @@ private:
|
||||
std::once_flag stop_flag_;
|
||||
std::atomic<size_t> next_ = 0;
|
||||
};
|
||||
|
||||
inline auto
|
||||
get_global_executor(unsigned pool_size = std::thread::hardware_concurrency()) {
|
||||
static auto g_io_context_pool = std::make_shared<io_context_pool>(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
|
||||
@@ -0,0 +1,72 @@
|
||||
#pragma once
|
||||
#include <iostream>
|
||||
|
||||
namespace rest_rpc {
|
||||
struct null_logger_t {
|
||||
template <typename T> const null_logger_t &operator<<(T &&) const {
|
||||
return *this;
|
||||
}
|
||||
};
|
||||
struct cout_logger_t {
|
||||
template <typename T> const cout_logger_t &operator<<(T &&t) const {
|
||||
std::cout << std::forward<T>(t);
|
||||
return *this;
|
||||
}
|
||||
~cout_logger_t() { std::cout << std::endl; }
|
||||
};
|
||||
struct cerr_logger_t {
|
||||
template <typename T> const cerr_logger_t &operator<<(T &&t) const {
|
||||
std::cerr << std::forward<T>(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
|
||||
@@ -3,6 +3,7 @@
|
||||
|
||||
#include "cplusplus_14.h"
|
||||
#include <functional>
|
||||
#include <type_traits>
|
||||
|
||||
namespace rest_rpc {
|
||||
|
||||
@@ -31,12 +32,15 @@ public:
|
||||
using stl_function_type = std::function<function_type>;
|
||||
typedef Ret (*pointer)(Arg, Args...);
|
||||
|
||||
typedef std::tuple<Arg, Args...> tuple_type;
|
||||
typedef std::tuple<std::remove_const_t<std::remove_reference_t<Arg>>,
|
||||
std::remove_const_t<std::remove_reference_t<Args>>...>
|
||||
tuple_type;
|
||||
typedef std::tuple<
|
||||
nonstd::remove_const_t<nonstd::remove_reference_t<Args>>...>
|
||||
bare_tuple_type;
|
||||
using args_tuple =
|
||||
std::tuple<std::string, Arg,
|
||||
std::tuple<std::string,
|
||||
nonstd::remove_const_t<nonstd::remove_reference_t<Arg>>,
|
||||
nonstd::remove_const_t<nonstd::remove_reference_t<Args>>...>;
|
||||
using args_tuple_2nd =
|
||||
std::tuple<std::string,
|
||||
|
||||
@@ -0,0 +1,35 @@
|
||||
#pragma once
|
||||
#include <cstdint>
|
||||
#ifdef _WIN32
|
||||
#include <winsock2.h>
|
||||
#else
|
||||
#include <arpa/inet.h>
|
||||
#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
|
||||
@@ -0,0 +1,173 @@
|
||||
#pragma once
|
||||
#include "io_context_pool.hpp"
|
||||
#include "logger.hpp"
|
||||
#include "rpc_connection.hpp"
|
||||
#include "use_asio.hpp"
|
||||
#include <string>
|
||||
#include <thread>
|
||||
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 <typename Function, typename Self = void>
|
||||
void register_handler(std::string_view name, const Function &f,
|
||||
Self *self = nullptr) {
|
||||
router_.register_handler(name, f, self);
|
||||
}
|
||||
|
||||
template <auto func, typename Self = void>
|
||||
void register_handler(Self *self = nullptr) {
|
||||
router_.register_handler<func>(self);
|
||||
}
|
||||
|
||||
void remove_handler(std::string_view name) { router_.remove_handler(name); }
|
||||
|
||||
template <auto func> void remove_handler() { router_.remove_handler<func>(); }
|
||||
|
||||
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<void> 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<rpc_connection>(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<bool> has_stop_ = false;
|
||||
std::unordered_map<uint64_t, std::shared_ptr<rpc_connection>> conns_;
|
||||
rpc_router router_;
|
||||
};
|
||||
} // namespace rest_rpc
|
||||
@@ -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<void> 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<asio::const_buffer> 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
|
||||
@@ -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 <cstdint>
|
||||
#include <functional>
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
|
||||
namespace rest_rpc {
|
||||
struct rpc_result {
|
||||
rpc_errc ec = rpc_errc::ok;
|
||||
std::string result;
|
||||
};
|
||||
|
||||
template <auto func> constexpr uint32_t get_key() {
|
||||
constexpr auto name = get_func_name<func>();
|
||||
constexpr uint32_t key = MD5::MD5Hash32(name.data(), name.length());
|
||||
return key;
|
||||
}
|
||||
|
||||
class rpc_router {
|
||||
public:
|
||||
template <typename Function, typename Self = void>
|
||||
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 <auto func, typename Self = void>
|
||||
void register_handler(Self *self = nullptr) {
|
||||
constexpr auto name = get_func_name<func>();
|
||||
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 <auto func> void remove_handler() {
|
||||
constexpr std::string_view name = get_func_name<func>();
|
||||
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 <typename Function, typename Self = void>
|
||||
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<Self>) {
|
||||
return register_nonmember_func(key, f);
|
||||
} else {
|
||||
return register_member_func(key, f, self);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
template <typename Function>
|
||||
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<Function>::tuple_type;
|
||||
using R = typename function_traits<Function>::return_type;
|
||||
rpc_service::msgpack_codec codec;
|
||||
try {
|
||||
auto tp = codec.unpack<args_tuple>(str.data(), str.size());
|
||||
if constexpr (std::is_void_v<R>) {
|
||||
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 <typename Function, typename Self>
|
||||
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<Function>::tuple_type;
|
||||
using R = typename function_traits<Function>::return_type;
|
||||
rpc_service::msgpack_codec codec;
|
||||
try {
|
||||
auto tp = codec.unpack<args_tuple>(str.data(), str.size());
|
||||
|
||||
if constexpr (std::is_void_v<R>) {
|
||||
std::apply(
|
||||
[self, &f](auto &&...args) {
|
||||
return (*self.*f)(std::forward<decltype(args)>(args)...);
|
||||
},
|
||||
tp);
|
||||
} else {
|
||||
auto r = std::apply(
|
||||
[self, &f](auto &&...args) {
|
||||
return (*self.*f)(std::forward<decltype(args)>(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<uint32_t, std::function<void(std::string_view, rpc_errc &,
|
||||
std::string &)>>
|
||||
map_invokers_;
|
||||
std::unordered_map<uint32_t, std::string> key2func_name_;
|
||||
};
|
||||
} // namespace rest_rpc
|
||||
@@ -0,0 +1,126 @@
|
||||
#pragma once
|
||||
#include <cstddef>
|
||||
#include <string>
|
||||
#include <utility>
|
||||
|
||||
namespace rest_rpc::detail {
|
||||
|
||||
#if __cpp_lib_string_resize_and_overwrite >= 202110L
|
||||
template <typename ch>
|
||||
inline void resize(std::basic_string<ch> &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 <typename Function, Function func_ptr> 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<decltype(&std::string::_M_rep),
|
||||
&std::string::_M_rep>;
|
||||
#else
|
||||
template class string_thief<decltype(&std::string::_M_set_length),
|
||||
&std::string::_M_set_length>;
|
||||
#endif
|
||||
#elif defined(_LIBCPP_VERSION)
|
||||
template class string_thief<decltype(&std::string::__set_size),
|
||||
&std::string::__set_size>;
|
||||
#elif defined(_MSVC_STL_VERSION)
|
||||
template class string_thief<decltype(&std::string::_Mypair),
|
||||
&std::string::_Mypair>;
|
||||
#endif
|
||||
|
||||
void string_set_length_hacker(std::string &, std::size_t);
|
||||
|
||||
template <typename ch>
|
||||
inline void resize(std::basic_string<ch> &raw_str, std::size_t sz) {
|
||||
std::string &str = *reinterpret_cast<std::string *>(&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<char> &self, std::size_t sz);
|
||||
|
||||
template <typename Function, Function func_ptr> class vector_thief {
|
||||
public:
|
||||
friend void vector_set_length_hacker(std::vector<char> &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<char, std::allocator<char>> *)(&self)).*func_ptr) =
|
||||
self.data() + sz;
|
||||
#else
|
||||
(self.*func_ptr) = self.data() + sz;
|
||||
#endif
|
||||
#else
|
||||
#if defined(__GLIBCXX__)
|
||||
((*(std::_Vector_base<char, std::allocator<char>> *)(&self)).*func_ptr)
|
||||
._M_finish = self.data() + sz;
|
||||
#endif
|
||||
#endif
|
||||
#endif
|
||||
}
|
||||
};
|
||||
|
||||
#if defined(__GLIBCXX__) // libstdc++
|
||||
template class vector_thief<decltype(&std::vector<char>::_M_impl),
|
||||
&std::vector<char>::_M_impl>;
|
||||
#elif defined(_LIBCPP_VERSION)
|
||||
template class vector_thief<decltype(&std::vector<char>::__end_),
|
||||
&std::vector<char>::__end_>;
|
||||
#elif defined(_MSVC_STL_VERSION)
|
||||
template class vector_thief<decltype(&std::vector<char>::_Mypair),
|
||||
&std::vector<char>::_Mypair>;
|
||||
#endif
|
||||
|
||||
template <typename ch>
|
||||
inline void resize(std::vector<ch> &raw_vec, std::size_t sz) {
|
||||
#if defined(__GLIBCXX__) || \
|
||||
(defined(_LIBCPP_VERSION) && defined(_LIBCPP_HAS_NO_ASAN)) || \
|
||||
defined(_MSVC_STL_VERSION)
|
||||
std::vector<char> &vec = *reinterpret_cast<std::vector<char> *>(&raw_vec);
|
||||
vec.reserve(sz);
|
||||
vector_set_length_hacker(vec, sz);
|
||||
#else
|
||||
raw_vec.resize(sz);
|
||||
#endif
|
||||
}
|
||||
#endif
|
||||
}; // namespace rest_rpc::detail
|
||||
@@ -0,0 +1,174 @@
|
||||
#pragma once
|
||||
#include <functional>
|
||||
#include <memory>
|
||||
|
||||
namespace rest_rpc::util {
|
||||
template <typename Function> struct function_traits;
|
||||
|
||||
template <typename Return, typename... Arguments>
|
||||
struct function_traits<Return (*)(Arguments...)> {
|
||||
using parameters_type = std::tuple<std::remove_cvref_t<Arguments>...>;
|
||||
using return_type = Return;
|
||||
};
|
||||
|
||||
template <typename Return, typename... Arguments>
|
||||
struct function_traits<Return (*)(Arguments...) noexcept> {
|
||||
using parameters_type = std::tuple<std::remove_cvref_t<Arguments>...>;
|
||||
using return_type = Return;
|
||||
};
|
||||
|
||||
template <typename Return, typename... Arguments>
|
||||
struct function_traits<Return(Arguments...)> {
|
||||
using parameters_type = std::tuple<std::remove_cvref_t<Arguments>...>;
|
||||
using return_type = Return;
|
||||
};
|
||||
|
||||
template <typename Return, typename... Arguments>
|
||||
struct function_traits<Return(Arguments...) noexcept> {
|
||||
using parameters_type = std::tuple<std::remove_cvref_t<Arguments>...>;
|
||||
using return_type = Return;
|
||||
};
|
||||
|
||||
template <typename This, typename Return, typename... Arguments>
|
||||
struct function_traits<Return (This::*)(Arguments...)> {
|
||||
using parameters_type = std::tuple<std::remove_cvref_t<Arguments>...>;
|
||||
using return_type = Return;
|
||||
using class_type = This;
|
||||
};
|
||||
|
||||
template <typename This, typename Return, typename... Arguments>
|
||||
struct function_traits<Return (This::*)(Arguments...) noexcept> {
|
||||
using parameters_type = std::tuple<std::remove_cvref_t<Arguments>...>;
|
||||
using return_type = Return;
|
||||
using class_type = This;
|
||||
};
|
||||
|
||||
template <typename This, typename Return, typename... Arguments>
|
||||
struct function_traits<Return (This::*)(Arguments...) const> {
|
||||
using parameters_type = std::tuple<std::remove_cvref_t<Arguments>...>;
|
||||
using return_type = Return;
|
||||
using class_type = This;
|
||||
};
|
||||
|
||||
template <typename This, typename Return, typename... Arguments>
|
||||
struct function_traits<Return (This::*)(Arguments...) const noexcept> {
|
||||
using parameters_type = std::tuple<std::remove_cvref_t<Arguments>...>;
|
||||
using return_type = Return;
|
||||
using class_type = This;
|
||||
};
|
||||
|
||||
template <typename Return> struct function_traits<Return (*)()> {
|
||||
using parameters_type = void;
|
||||
using return_type = Return;
|
||||
};
|
||||
|
||||
template <typename Return> struct function_traits<Return (*)() noexcept> {
|
||||
using parameters_type = void;
|
||||
using return_type = Return;
|
||||
};
|
||||
|
||||
template <typename Return> struct function_traits<Return (&)()> {
|
||||
using parameters_type = void;
|
||||
using return_type = Return;
|
||||
};
|
||||
|
||||
template <typename Return> struct function_traits<Return (&)() noexcept> {
|
||||
using parameters_type = void;
|
||||
using return_type = Return;
|
||||
};
|
||||
|
||||
template <typename Return> struct function_traits<Return()> {
|
||||
using parameters_type = void;
|
||||
using return_type = Return;
|
||||
};
|
||||
|
||||
template <typename Return> struct function_traits<Return() noexcept> {
|
||||
using parameters_type = void;
|
||||
using return_type = Return;
|
||||
};
|
||||
|
||||
template <typename This, typename Return>
|
||||
struct function_traits<Return (This::*)()> {
|
||||
using parameters_type = void;
|
||||
using return_type = Return;
|
||||
using class_type = This;
|
||||
};
|
||||
|
||||
template <typename This, typename Return>
|
||||
struct function_traits<Return (This::*)() noexcept> {
|
||||
using parameters_type = void;
|
||||
using return_type = Return;
|
||||
using class_type = This;
|
||||
};
|
||||
|
||||
template <typename This, typename Return>
|
||||
struct function_traits<Return (This::*)() const> {
|
||||
using parameters_type = void;
|
||||
using return_type = Return;
|
||||
using class_type = This;
|
||||
};
|
||||
|
||||
template <typename This, typename Return>
|
||||
struct function_traits<Return (This::*)() const noexcept> {
|
||||
using parameters_type = void;
|
||||
using return_type = Return;
|
||||
using class_type = This;
|
||||
};
|
||||
|
||||
// Support function object and lambda expression
|
||||
template <class Function>
|
||||
struct function_traits : function_traits<decltype(&Function::operator())> {};
|
||||
|
||||
template <typename Function>
|
||||
using function_parameters_t =
|
||||
typename function_traits<std::remove_cvref_t<Function>>::parameters_type;
|
||||
|
||||
template <typename Function>
|
||||
using last_parameters_type_t =
|
||||
std::tuple_element_t<std::tuple_size_v<function_parameters_t<Function>> - 1,
|
||||
function_parameters_t<Function>>;
|
||||
|
||||
template <typename Function>
|
||||
using function_return_type_t =
|
||||
typename function_traits<std::remove_cvref_t<Function>>::return_type;
|
||||
|
||||
template <typename Function>
|
||||
using class_type_t =
|
||||
typename function_traits<std::remove_cvref_t<Function>>::class_type;
|
||||
|
||||
template <typename F, typename... Args>
|
||||
struct is_invocable
|
||||
: std::is_constructible<
|
||||
std::function<void(std::remove_reference_t<Args>...)>,
|
||||
std::reference_wrapper<typename std::remove_reference<F>::type>> {};
|
||||
|
||||
template <typename F, typename... Args>
|
||||
inline constexpr bool is_invocable_v = is_invocable<F, Args...>::value;
|
||||
|
||||
template <typename T> struct remove_first { using type = T; };
|
||||
|
||||
template <class First, class... Second>
|
||||
struct remove_first<std::tuple<First, Second...>> {
|
||||
using type = std::tuple<Second...>;
|
||||
};
|
||||
|
||||
template <typename T> using remove_first_t = typename remove_first<T>::type;
|
||||
|
||||
template <bool has_conn, typename T> inline auto get_args() {
|
||||
if constexpr (has_conn) {
|
||||
using args_type = remove_first_t<T>;
|
||||
return args_type{};
|
||||
} else {
|
||||
return T{};
|
||||
}
|
||||
}
|
||||
|
||||
template <typename Test, template <typename...> class Ref>
|
||||
struct is_specialization : std::false_type {};
|
||||
|
||||
template <template <typename...> class Ref, typename... Args>
|
||||
struct is_specialization<Ref<Args...>, Ref> : std::true_type {};
|
||||
|
||||
template <typename Test, template <typename...> class Ref>
|
||||
inline constexpr bool is_specialization_v = is_specialization<Test, Ref>::value;
|
||||
} // namespace rest_rpc::util
|
||||
@@ -4,6 +4,9 @@
|
||||
#ifdef CINATRA_ENABLE_SSL
|
||||
#include <asio/ssl.hpp>
|
||||
#endif
|
||||
#include <asio/as_tuple.hpp>
|
||||
#include <asio/co_spawn.hpp>
|
||||
#include <asio/detached.hpp>
|
||||
#include <asio/detail/noncopyable.hpp>
|
||||
#include <asio/executor_work_guard.hpp>
|
||||
#include <asio/post.hpp>
|
||||
|
||||
Reference in New Issue
Block a user