diff --git a/include/cloud_point_rpc/cli.hpp b/include/cloud_point_rpc/cli.hpp index 60e0650..43aa35a 100644 --- a/include/cloud_point_rpc/cli.hpp +++ b/include/cloud_point_rpc/cli.hpp @@ -2,7 +2,7 @@ #include #include - +#include "export.h" namespace cloud_point_rpc { /** @@ -14,7 +14,7 @@ namespace cloud_point_rpc { * @param port Server Port * @return int exit code */ -int run_cli(std::istream &input, std::ostream &output, const std::string &ip, +int CRPC_EXPORT run_cli(std::istream &input, std::ostream &output, const std::string &ip, int port); } // namespace cloud_point_rpc diff --git a/include/cloud_point_rpc/rpc_client.hpp b/include/cloud_point_rpc/rpc_client.hpp index 1dff2a7..267f976 100644 --- a/include/cloud_point_rpc/rpc_client.hpp +++ b/include/cloud_point_rpc/rpc_client.hpp @@ -6,7 +6,6 @@ #include #include #include - namespace cloud_point_rpc { class RpcClient : public jsonrpccxx::JsonRpcClient { diff --git a/include/cloud_point_rpc/rpc_server.hpp b/include/cloud_point_rpc/rpc_server.hpp index 349f7cc..4d03079 100644 --- a/include/cloud_point_rpc/rpc_server.hpp +++ b/include/cloud_point_rpc/rpc_server.hpp @@ -5,14 +5,27 @@ #include #include #include +#include "export.h" +extern "C" { + +struct rpc_string { + rpc_string(const char* data, uint64_t size) : s(data,size) {} + rpc_string() = default; + + std::string s; +}; +} namespace cloud_point_rpc { -class RpcServer { +class CRPC_EXPORT RpcServer { public: using Handler = std::function; + using callback_t = rpc_string*(*)(rpc_string*); void register_method(const std::string &name, Handler handler); + void register_method(const std::string &name, callback_t handler); + ///@param request_str - json rpc 2.0 formatted string [[nodiscard]] std::string process(const std::string &request_str); private: diff --git a/include/cloud_point_rpc/service.hpp b/include/cloud_point_rpc/service.hpp index c8cea62..d3134be 100644 --- a/include/cloud_point_rpc/service.hpp +++ b/include/cloud_point_rpc/service.hpp @@ -2,10 +2,10 @@ #include "cloud_point_rpc/config.hpp" #include - +#include "export.h" namespace cloud_point_rpc { -class Service { +class CRPC_EXPORT Service { public: explicit Service(const TestData &data = {}); diff --git a/include/cloud_point_rpc/tcp_connector.hpp b/include/cloud_point_rpc/tcp_connector.hpp index 030ab77..69d0975 100644 --- a/include/cloud_point_rpc/tcp_connector.hpp +++ b/include/cloud_point_rpc/tcp_connector.hpp @@ -5,7 +5,7 @@ #include #include #include - +#include "export.h" namespace cloud_point_rpc { /** * TCPConnector main purpose is to implement jsonrpccxx::IClientConnector Send @@ -13,7 +13,7 @@ namespace cloud_point_rpc { * the json "expected size" of a packet in case when the packet was for some * reason on any level fractured. */ -class TCPConnector : public jsonrpccxx::IClientConnector { +class CRPC_EXPORT TCPConnector : public jsonrpccxx::IClientConnector { public: TCPConnector(const std::string &ip, size_t port) noexcept(false) : io_context_(), socket_(io_context_) { diff --git a/include/cloud_point_rpc/tcp_server.hpp b/include/cloud_point_rpc/tcp_server.hpp index b4556b2..8b8d3dc 100644 --- a/include/cloud_point_rpc/tcp_server.hpp +++ b/include/cloud_point_rpc/tcp_server.hpp @@ -7,10 +7,10 @@ #include #include #include - +#include "export.h" namespace cloud_point_rpc { -class TcpServer { +class CRPC_EXPORT TcpServer { public: using RequestProcessor = std::function; diff --git a/include/export.h b/include/export.h new file mode 100644 index 0000000..0df1a04 --- /dev/null +++ b/include/export.h @@ -0,0 +1,14 @@ +#pragma once +#if defined(_WIN32) +#ifdef CRPC_SERVER_API_EXPORT +#define CRPC_EXPORT __declspec(dllexport) +#else +#define CRPC_EXPORT __declspec(dllimport) +#endif +#elif defined(__GNUC__) // GCC, Clang, etc. + // Linux, macOS, etc. + #define CRPC_EXPORT __attribute__((visibility("default"))) +#else + #define CRPC_EXPORT + #pragma warning Unknown dynamic link import/export semantics. +#endif \ No newline at end of file diff --git a/include/server_api.h b/include/server_api.h new file mode 100644 index 0000000..9679c68 --- /dev/null +++ b/include/server_api.h @@ -0,0 +1,38 @@ +#ifndef CRPC_SERVER_API +#define CRPC_SERVER_API + +#include +#include "export.h" + + +#ifdef __cplusplus +extern "C" { +#endif //cpp +/** + * @brief basically just std::string wrapper: + * struct rpc_string { + * std::string s; + * }; + * has internal gc and would be automatically deallocated on deinit call + * but it is better to call destroy manually, to prevent exceeding memory usage + */ +struct CRPC_EXPORT rpc_string; + +CRPC_EXPORT const char* crpc_str_get_data(const rpc_string*); +CRPC_EXPORT uint64_t crpc_str_get_size(const rpc_string*); + +CRPC_EXPORT rpc_string* crpc_str_create(const char* data, uint64_t size); +CRPC_EXPORT void crpc_str_destroy(rpc_string*); + +typedef rpc_string*(*callback_t)(rpc_string*); + +CRPC_EXPORT void crpc_init(const char* config_path); +CRPC_EXPORT void crpc_deinit(); + +CRPC_EXPORT void crpc_add_method(callback_t cb, rpc_string* name); + +#ifdef __cplusplus +} +#endif //cpp + +#endif //CRPC_SERVER_API \ No newline at end of file diff --git a/include/test_api.h b/include/test_api.h new file mode 100644 index 0000000..b208c2b --- /dev/null +++ b/include/test_api.h @@ -0,0 +1,22 @@ +#ifndef CRPC_TEST_API +#define CRPC_TEST_API + +#include +#include "export.h" +#ifdef __cplusplus +extern "C" { +#endif //cpp + +struct CRPC_EXPORT rpc_string; +typedef rpc_string*(*callback_t)(rpc_string*); + +CRPC_EXPORT void crpc_test_init(); +CRPC_EXPORT void crpc_test_deinit(); + +CRPC_EXPORT void crpc_test_add_method(callback_t cb, rpc_string* name); + +#ifdef __cplusplus +} +#endif //cpp + +#endif //CRPC_SERVER_API \ No newline at end of file diff --git a/src/meson.build b/src/meson.build index ba59f69..64633e1 100644 --- a/src/meson.build +++ b/src/meson.build @@ -1,10 +1,12 @@ +add_project_arguments('-DCRPC_SERVER_API_EXPORT -pthread', language: 'cpp') + cloud_point_rpc_sources = files( 'rpc_server.cpp', 'service.cpp', - 'cli.cpp' + 'server_api.cpp' ) -libcloud_point_rpc = static_library('cloud_point_rpc', +libcloud_point_rpc = shared_library('cloud_point_rpc', cloud_point_rpc_sources, include_directories : inc, dependencies : [json_dep, thread_dep, glog_dep, yaml_dep, asio_dep], @@ -15,14 +17,39 @@ cloud_point_rpc_dep = declare_dependency( link_with : libcloud_point_rpc, dependencies : [json_dep, glog_dep, yaml_dep, asio_dep]) +# Test lib +libcloud_point_rpc_test = shared_library('test_cloud_point', + 'test_api.cpp', + dependencies: cloud_point_rpc_dep, + install : true) + +cloud_point_rpc_test_dep = declare_dependency( + include_directories: inc, + link_with: libcloud_point_rpc_test, + dependencies: [cloud_point_rpc_dep] +) + +libcloud_point_rpc_cli = shared_library('libcloud_point_rpc_cli', + 'cli.cpp', + include_directories : inc, + dependencies : [json_dep, thread_dep, glog_dep, yaml_dep, asio_dep, cloud_point_rpc_dep], + install : true) + +cloud_point_rpc_cli_dep = declare_dependency( + include_directories: inc, + link_with: libcloud_point_rpc_cli, + dependencies: [cloud_point_rpc_dep] +) + # Client/CLI tool (legacy stdin/stdout) executable('cloud_point_rpc_cli', - 'main.cpp', - dependencies : cloud_point_rpc_dep, + ['main.cpp', ], + dependencies : cloud_point_rpc_cli_dep, install : true) # Server executable (TCP) executable('cloud_point_rpc_server', 'server_main.cpp', dependencies : cloud_point_rpc_dep, + link_args : '-pthread', install : true) diff --git a/src/rpc_server.cpp b/src/rpc_server.cpp index 477af79..a634472 100644 --- a/src/rpc_server.cpp +++ b/src/rpc_server.cpp @@ -21,12 +21,22 @@ void RpcServer::register_method(const std::string &name, Handler handler) { handlers_[name] = std::move(handler); } +void RpcServer::register_method(const std::string& name, callback_t handler) { + handlers_[name] = [handler](const nlohmann::json& j) -> nlohmann::json { + rpc_string tmp; + tmp.s = j.dump(); + rpc_string* res = handler(&tmp); + return {res->s}; + }; +} + std::string RpcServer::process(const std::string &request_str) { + LOG(INFO) << request_str; json request; try { request = json::parse(request_str); } catch (const json::parse_error &) { - LOG(ERROR) << "json parse error" << __func__; + LOG(ERROR) << "json parse error; " << __func__; return create_error(-32700, "Parse error").dump(); } diff --git a/src/server_api.cpp b/src/server_api.cpp new file mode 100644 index 0000000..0083768 --- /dev/null +++ b/src/server_api.cpp @@ -0,0 +1,68 @@ +#include "cloud_point_rpc/config.hpp" +#include "cloud_point_rpc/rpc_server.hpp" +#include "cloud_point_rpc/tcp_server.hpp" +#include +#include "server_api.h" +#include +#include +#include +#include + +static std::list> gc; +cloud_point_rpc::RpcServer rpc_server; +std::unique_ptr server = nullptr; + +extern "C" { + +const char* crpc_str_get_data(const rpc_string* that) { + return that->s.c_str(); +} + +uint64_t crpc_str_get_size(const rpc_string* that){ + return that->s.size(); +} + +rpc_string* crpc_str_create(const char* data, uint64_t size){ + gc.push_back(std::make_unique(data, size)); + return gc.back().get(); +} + +void crpc_str_destroy(rpc_string* that){ + auto it = std::ranges::find(gc, that, &std::unique_ptr::get); + if(it != gc.end()) + gc.erase(it); +} + + +void crpc_init(const char* config_path) { + google::InitGoogleLogging("CloudPointRPC"); + if(config_path == nullptr) { + LOG(INFO) << "config_path was not provided"; + } + try { + auto config = cloud_point_rpc::ConfigLoader::load(config_path); + LOG(INFO) << "Loaded config from " << config_path; + + server = std::make_unique(config.server.ip, config.server.port, + [&](const std::string &request) { + return rpc_server.process( + request); + }); + server->start(); + } catch (const std::exception &e) { + LOG(ERROR) << "Fatal error: " << e.what(); + } +} + +void crpc_deinit() { + if(server) + server->join(); + server.reset(); + gc.clear(); +} + +void crpc_add_method(callback_t cb, rpc_string* name) { + rpc_server.register_method(name->s, cb); +} +} + diff --git a/src/test_api.cpp b/src/test_api.cpp new file mode 100644 index 0000000..65cd662 --- /dev/null +++ b/src/test_api.cpp @@ -0,0 +1,97 @@ +#include "test_api.h" +#include "server_api.h" +#include "cloud_point_rpc/rpc_server.hpp" +#include +#include +#include +#include +#include +#include "server_api.h" + +class TestThread { + std::string make_jsonrpc(const std::string& method_name) { + return std::format(R"( + {{ + "jsonrpc": "2.0", + "method": "{}", + "params": {{}}, + "id": 1 + }} + )", method_name); + } +public: + TestThread() = default; + ~TestThread() { + join(); + } + + void routine() { + std::unique_lock lock(mtx); + std::stop_token stoken = thr.get_stop_token(); + lock.unlock(); + while(!stoken.stop_requested()) { + lock.lock(); + DLOG(INFO) << methods.size() << " ; Size of methods"; + if(!methods.empty()) { + auto it = methods.begin(); + std::advance(it, distance % methods.size()); + DLOG(INFO) << *it << " : Method at this position"; + LOG(INFO) << server.process(make_jsonrpc(*it)); + } + lock.unlock(); + + using namespace std::chrono_literals; + std::this_thread::sleep_for(100ms); + } + LOG(INFO) << "Stopped"; + } + void start() { + thr = std::jthread(&TestThread::routine, this); + } + void join() { + DLOG(INFO) << "Requested thread stop"; + thr.request_stop(); + if(thr.joinable()) { + thr.join(); + } + } + void add_method(callback_t cb, rpc_string* name) { + LOG(INFO) << "Trying to add method: " << name->s; + std::lock_guard lock(mtx); + methods.emplace_back(name->s); + server.register_method(name->s,cb); + } + + void call(rpc_string* name) { + std::lock_guard lock(mtx); + LOG(INFO) << server.process(name->s); + } +private: + std::list methods; + size_t distance; + std::jthread thr; + std::mutex mtx; + cloud_point_rpc::RpcServer server; +} test; + +extern "C" { + +void crpc_test_init() { + if(!google::IsGoogleLoggingInitialized()) + google::InitGoogleLogging("TestRPC"); + try { + test.start(); + } catch (const std::exception &e) { + LOG(ERROR) << "Fatal error: " << e.what(); + } +} +void crpc_test_deinit() { + test.join(); + crpc_deinit(); +} + +void crpc_test_add_method(callback_t cb, rpc_string* name){ + test.add_method(cb, name); +} + +} \ No newline at end of file diff --git a/tests/meson.build b/tests/meson.build index 5356e95..997be38 100644 --- a/tests/meson.build +++ b/tests/meson.build @@ -1,12 +1,13 @@ test_sources = files( 'test_rpc.cpp', 'test_integration.cpp', + 'test_tcp.cpp', 'test_cli.cpp', - 'test_tcp.cpp' + 'test_c_api.cpp' ) test_exe = executable('unit_tests', test_sources, - dependencies : [cloud_point_rpc_dep, gtest_dep, gtest_main_dep, gmock_dep]) + dependencies : [cloud_point_rpc_dep, cloud_point_rpc_cli_dep, cloud_point_rpc_test_dep, json_dep, gtest_dep, gtest_main_dep, gmock_dep]) test('unit_tests', test_exe) diff --git a/tests/test_c_api.cpp b/tests/test_c_api.cpp new file mode 100644 index 0000000..10eb2d4 --- /dev/null +++ b/tests/test_c_api.cpp @@ -0,0 +1,45 @@ +#include +#include +#include +#include +#include "cloud_point_rpc/rpc_server.hpp" +#include "test_api.h" +#include "server_api.h" +class TestCApi : public ::testing::Test { +protected: + void SetUp() override { + EXPECT_NO_THROW(crpc_test_init()); + } + + void TearDown() override { + crpc_test_deinit(); + } +}; + +TEST_F(TestCApi, Base) { + FLAGS_logtostderr = 1; + rpc_string name; + name.s = "test"; + static std::promise task; + std::future called = task.get_future(); + LOG(INFO) << "Test"; + crpc_test_add_method(+[](rpc_string*)-> rpc_string* { + static bool installed = false; + DLOG(INFO) << "Trying to do something"; + if(!installed){ + LOG(INFO) << "Trying to install"; + installed = true; + task.set_value(installed); + } + DLOG(INFO) << "Go out"; + return crpc_str_create("res", sizeof("res")); + }, &name); + + using namespace std::chrono_literals; + called.wait_for(500ms); + + EXPECT_EQ(called.valid(), true); + EXPECT_EQ(called.get(), true); + + LOG(INFO) << "DONE"; +} \ No newline at end of file