Skip to content

Commit

Permalink
Introduce Unix Domain Socket Transport Layers (#20)
Browse files Browse the repository at this point in the history
* Add asio 1.28.2 dependency

* Add PipeTransport class with Unix domain socket support

* Upgrade project to C++20 and add FramedPipeTransport implementation
  • Loading branch information
hankhsu1996 authored Aug 7, 2024
1 parent 67abe75 commit 5d0149a
Show file tree
Hide file tree
Showing 28 changed files with 716 additions and 117 deletions.
4 changes: 2 additions & 2 deletions .bazelrc
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
# .bazelrc

# Set the C++ standard to C++17
build --cxxopt='-std=c++17'
# Set the C++ standard to C++20
build --cxxopt='-std=c++20'

# Enable warnings
build --cxxopt='-Wall'
Expand Down
16 changes: 15 additions & 1 deletion CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ cmake_minimum_required(VERSION 3.19)
project(jsonrpc VERSION 1.0.0 LANGUAGES CXX)

# Set C++ standard
set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD 20)
set(CMAKE_CXX_STANDARD_REQUIRED True)

# Set C++ Compiler Launcher
Expand All @@ -16,6 +16,7 @@ file(GLOB_RECURSE SOURCES "src/*.cpp")
find_package(nlohmann_json REQUIRED)
find_package(spdlog REQUIRED)
find_package(bshoshany-thread-pool REQUIRED)
find_package(asio REQUIRED)

# Add the library target
add_library(jsonrpc ${SOURCES})
Expand All @@ -28,6 +29,7 @@ target_link_libraries(jsonrpc PUBLIC
nlohmann_json::nlohmann_json
spdlog::spdlog
bshoshany-thread-pool::bshoshany-thread-pool
asio::asio
)

# Add example executables
Expand All @@ -37,5 +39,17 @@ target_link_libraries(stdio_client PRIVATE jsonrpc)
add_executable(stdio_server examples/stdio_server.cpp)
target_link_libraries(stdio_server PRIVATE jsonrpc)

add_executable(pipe_client examples/pipe_client.cpp)
target_link_libraries(pipe_client PRIVATE jsonrpc)

add_executable(pipe_server examples/pipe_server.cpp)
target_link_libraries(pipe_server PRIVATE jsonrpc)

add_executable(framed_pipe_client examples/framed_pipe_client.cpp)
target_link_libraries(framed_pipe_client PRIVATE jsonrpc)

add_executable(framed_pipe_server examples/framed_pipe_server.cpp)
target_link_libraries(framed_pipe_server PRIVATE jsonrpc)

enable_testing()
add_subdirectory(tests)
3 changes: 3 additions & 0 deletions MODULE.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -15,5 +15,8 @@ bazel_dep(name = "nlohmann_json", version = "3.11.3")
# Register the spdlog dependency
bazel_dep(name = "spdlog", version = "1.14.1")

# Register the asio dependency
bazel_dep(name = "asio", version = "1.28.2")

# Register the catch2 dependency
bazel_dep(name = "catch2", version = "3.6.0")
2 changes: 2 additions & 0 deletions MODULE.bazel.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

24 changes: 16 additions & 8 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,19 @@ Welcome to the **JSON-RPC 2.0 Modern C++ Library**! This library provides a ligh
## ✨ Features

- **Fully Compliant with JSON-RPC 2.0**: Supports method calls, notifications, comprehensive error handling, and batch requests.
- **Modern and Lightweight**: Leverages C++17 features with minimal dependencies, focusing solely on the JSON-RPC protocol.
- **Modern and Lightweight**: Leverages C++20 features with minimal dependencies, focusing solely on the JSON-RPC protocol.
- **Transport-Agnostic**: Abstract transport layer allows use of provided implementations or custom ones.
- **Simple JSON Integration**: Uses [nlohmann/json](https://github.com/nlohmann/json) for easy JSON object interaction, requiring no learning curve.
- **Flexible Handler Registration**: Register handlers using `std::function`, supporting lambdas, function pointers, and other callable objects.

## 🚀 Getting Started

### Prerequisites

- **Compiler**: Any compiler with C++20 support.
- **CMake**: Version 3.19+ (for CMake preset support).
- **Bazel**: Version 5.0+ (for Bazel module support).

To include this library in your project, you can use CMake's FetchContent, Conan 2, or Bazel.

### Using CMake FetchContent
Expand Down Expand Up @@ -59,11 +65,12 @@ bazel_dep(name = "jsonrpc", version = "1.0.0")
Here’s how to create a simple JSON-RPC server:

```cpp
using namespace jsonrpc;
using namespace jsonrpc::server;
using namespace jsonrpc::transport;
using Json = nlohmann::json;

// Create a server with an stdio transport
server::Server server(std::make_unique<transport::StdioTransport>());
Server server(std::make_unique<StdioTransport>());

// Register a method named "add" that adds two numbers
server.RegisterMethodCall("add", [](const std::optional<Json> &params) {
Expand All @@ -87,16 +94,17 @@ To register a method, you need to provide a function that takes optional `Json`
Here’s how to create a JSON-RPC client:
```cpp
using namespace jsonrpc;
using namespace jsonrpc::client;
using namespace jsonrpc::transport;
using Json = nlohmann::json;
// Create a client with an stdio transport
client::Client client(std::make_unique<transport::StdioTransport>());
// Create a client with a standard I/O transport
Client client(std::make_unique<StdioTransport>());
client.Start();
// Perform addition
auto response = client.SendMethodCall("add", Json({{"a", 10}, {"b", 5}}));
std::cout << "Add result: " << response.dump() << std::endl;
spdlog::info("Add result: {}", response.dump());
// Send stop notification
client.SendNotification("stop");
Expand Down Expand Up @@ -140,6 +148,7 @@ Next, install dependencies and generate `ConanPresets.json`:

```bash
conan install . --build=missing
conan install . -s build_type=Debug --build=missing
```

**Step 2: Configure and Build the Project**
Expand All @@ -166,7 +175,6 @@ ctest --preset release
For Debug configuration:

```bash
conan install . -s build_type=Debug --build=missing
cmake --preset debug
cmake --build --preset debug
ctest --preset debug
Expand Down
3 changes: 2 additions & 1 deletion conanfile.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,8 @@ class JsonRpcConan(ConanFile):
requires = [
"nlohmann_json/3.11.3",
"spdlog/1.14.1",
"bshoshany-thread-pool/4.1.0"
"bshoshany-thread-pool/4.1.0",
"asio/1.28.2"
]

tool_requires = [
Expand Down
4 changes: 2 additions & 2 deletions examples/calculator.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -8,14 +8,14 @@ using Json = nlohmann::json;
class Calculator {
public:
Json Add(const Json &params) {
spdlog::info("Received add request with params: {}", params.dump());
spdlog::debug("Received add request with params: {}", params.dump());
double a = params["a"];
double b = params["b"];
return {{"result", a + b}};
}

Json Divide(const Json &params) {
spdlog::info("Received divide request with params: {}", params.dump());
spdlog::debug("Received divide request with params: {}", params.dump());
double a = params["a"];
double b = params["b"];
if (b == 0) {
Expand Down
35 changes: 35 additions & 0 deletions examples/framed_pipe_client.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
#include <iostream>
#include <memory>

#include <jsonrpc/client/client.hpp>
#include <jsonrpc/transport/framed_pipe_transport.hpp>
#include <nlohmann/json.hpp>
#include <spdlog/sinks/basic_file_sink.h>
#include <spdlog/spdlog.h>

using namespace jsonrpc::client;
using namespace jsonrpc::transport;
using Json = nlohmann::json;

int main() {
auto logger = spdlog::basic_logger_mt("client", "logs/client.log", true);
spdlog::set_default_logger(logger);
spdlog::set_level(spdlog::level::debug);
spdlog::flush_on(spdlog::level::debug);

std::string socketPath = "/tmp/calculator_pipe";
auto transport = std::make_unique<FramedPipeTransport>(socketPath, false);
Client client(std::move(transport));
client.Start();

Json addRes = client.SendMethodCall("add", Json({{"a", 10}, {"b", 5}}));
spdlog::info("Add result: {}", addRes.dump());

Json divRes = client.SendMethodCall("divide", Json({{"a", 10}, {"b", 0}}));
spdlog::info("Divide result: {}", divRes.dump());

client.SendNotification("stop");

client.Stop();
return 0;
}
41 changes: 41 additions & 0 deletions examples/framed_pipe_server.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
#include <memory>

#include <jsonrpc/server/server.hpp>
#include <jsonrpc/transport/framed_pipe_transport.hpp>
#include <nlohmann/json.hpp>
#include <spdlog/sinks/basic_file_sink.h>
#include <spdlog/spdlog.h>

#include "calculator.hpp"

using namespace jsonrpc::server;
using namespace jsonrpc::transport;
using Json = nlohmann::json;

int main() {
auto logger = spdlog::basic_logger_mt("server", "logs/server.log", true);
spdlog::set_default_logger(logger);
spdlog::set_level(spdlog::level::debug);
spdlog::flush_on(spdlog::level::debug);

std::string socketPath = "/tmp/calculator_pipe";
auto transport = std::make_unique<FramedPipeTransport>(socketPath, true);
Server server(std::move(transport));
Calculator calculator;

server.RegisterMethodCall(
"add", [&calculator](const std::optional<Json> &params) {
return calculator.Add(params.value());
});

server.RegisterMethodCall(
"divide", [&calculator](const std::optional<Json> &params) {
return calculator.Divide(params.value());
});

server.RegisterNotification(
"stop", [&server](const std::optional<Json> &) { server.Stop(); });

server.Start();
return 0;
}
35 changes: 35 additions & 0 deletions examples/pipe_client.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
#include <iostream>
#include <memory>

#include <jsonrpc/client/client.hpp>
#include <jsonrpc/transport/pipe_transport.hpp>
#include <nlohmann/json.hpp>
#include <spdlog/sinks/basic_file_sink.h>
#include <spdlog/spdlog.h>

using namespace jsonrpc::client;
using namespace jsonrpc::transport;
using Json = nlohmann::json;

int main() {
auto logger = spdlog::basic_logger_mt("client", "logs/client.log", true);
spdlog::set_default_logger(logger);
spdlog::set_level(spdlog::level::debug);
spdlog::flush_on(spdlog::level::debug);

std::string socketPath = "/tmp/calculator_pipe";
auto transport = std::make_unique<PipeTransport>(socketPath, false);
Client client(std::move(transport));
client.Start();

Json addRes = client.SendMethodCall("add", Json({{"a", 10}, {"b", 5}}));
spdlog::info("Add result: {}", addRes.dump());

Json divRes = client.SendMethodCall("divide", Json({{"a", 10}, {"b", 0}}));
spdlog::info("Divide result: {}", divRes.dump());

client.SendNotification("stop");

client.Stop();
return 0;
}
42 changes: 42 additions & 0 deletions examples/pipe_server.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
#include <memory>

#include <jsonrpc/server/server.hpp>
#include <jsonrpc/transport/pipe_transport.hpp>
#include <nlohmann/json.hpp>
#include <spdlog/sinks/basic_file_sink.h>
#include <spdlog/spdlog.h>

#include "calculator.hpp"

using namespace jsonrpc::server;
using namespace jsonrpc::transport;
using Json = nlohmann::json;

int main() {
auto logger = spdlog::basic_logger_mt("server", "logs/server.log", true);
spdlog::set_default_logger(logger);
spdlog::set_level(spdlog::level::debug);
spdlog::flush_on(spdlog::level::debug);

std::string socketPath = "/tmp/calculator_pipe";

auto transport = std::make_unique<PipeTransport>(socketPath, true);
Server server(std::move(transport));
Calculator calculator;

server.RegisterMethodCall(
"add", [&calculator](const std::optional<Json> &params) {
return calculator.Add(params.value());
});

server.RegisterMethodCall(
"divide", [&calculator](const std::optional<Json> &params) {
return calculator.Divide(params.value());
});

server.RegisterNotification(
"stop", [&server](const std::optional<Json> &) { server.Stop(); });

server.Start();
return 0;
}
9 changes: 5 additions & 4 deletions examples/stdio_client.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -7,17 +7,18 @@
#include <spdlog/sinks/basic_file_sink.h>
#include <spdlog/spdlog.h>

using namespace jsonrpc;
using namespace jsonrpc::client;
using namespace jsonrpc::transport;
using Json = nlohmann::json;

int main() {
auto logger = spdlog::basic_logger_mt("client_logger", "logs/client.log");
auto logger = spdlog::basic_logger_mt("client", "logs/client.log");
spdlog::set_default_logger(logger);
spdlog::set_level(spdlog::level::debug);
spdlog::flush_on(spdlog::level::debug);

auto transport = std::make_unique<transport::StdioTransport>();
client::Client client(std::move(transport));
auto transport = std::make_unique<StdioTransport>();
Client client(std::move(transport));
client.Start();

Json addRes = client.SendMethodCall("add", Json({{"a", 10}, {"b", 5}}));
Expand Down
15 changes: 7 additions & 8 deletions examples/stdio_server.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -8,17 +8,18 @@

#include "calculator.hpp"

using namespace jsonrpc;
using namespace jsonrpc::server;
using namespace jsonrpc::transport;
using Json = nlohmann::json;

int main() {
auto logger = spdlog::basic_logger_mt("server_logger", "logs/server.log");
auto logger = spdlog::basic_logger_mt("server", "logs/server.log");
spdlog::set_default_logger(logger);
spdlog::set_level(spdlog::level::debug);
spdlog::flush_on(spdlog::level::debug);

auto transport = std::make_unique<transport::StdioTransport>();
server::Server server(std::move(transport));
auto transport = std::make_unique<StdioTransport>();
Server server(std::move(transport));
Calculator calculator;

server.RegisterMethodCall(
Expand All @@ -31,10 +32,8 @@ int main() {
return calculator.Divide(params.value());
});

server.RegisterNotification("stop", [&server](const std::optional<Json> &) {
spdlog::info("Server Received stop notification");
server.Stop();
});
server.RegisterNotification(
"stop", [&server](const std::optional<Json> &) { server.Stop(); });

server.Start();
return 0;
Expand Down
Loading

0 comments on commit 5d0149a

Please sign in to comment.