A lightweight, modern C++ implementation of JSON-RPC 2.0 servers and clients. The library is designed to be flexible, allowing integration with various transport layers, and provides straightforward method and notification registration.
- Fully Compliant with JSON-RPC 2.0: Supports method calls, notifications, comprehensive error handling, and batch requests.
- Modern and Lightweight: Leverages C++23 features with minimal dependencies, focusing solely on the JSON-RPC protocol.
- Unified Endpoint Design: Single
RpcEndpointclass that can act as both client and server, following JSON-RPC 2.0's symmetric design. - Transport-Agnostic: Abstract transport layer allows use of provided implementations or custom ones.
- Simple JSON Integration: Uses 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.
- Compiler: Any compiler with C++23 support.
- Build System: Either Bazel 7.0+ (preferred) or CMake 3.19+ (alternative).
- Optional: Conan 2.0+ for dependency management with CMake.
There are several ways to include this library in your project:
Bazel provides a streamlined dependency management experience. To include this library in your project with Bazel, ensure you are using Bazel 7.0 or later, as Bzlmod is enabled by default.
# In your MODULE.bazel file
bazel_dep(name = "jsonrpc", version = "0.0.0")
git_override(
module_name = "jsonrpc",
remote = "https://github.com/hankhsu1996/jsonrpc-cpp-lib.git",
tag = "v2.1.2"
)With this approach, all of this library's dependencies (nlohmann_json, spdlog, asio, etc.) will be automatically pulled in and version-managed by Bazel.
CMake offers two main approaches for including this library:
This approach downloads and builds the library as part of your project, suitable for development workflows where a self-contained build is desired:
include(FetchContent)
FetchContent_Declare(
jsonrpc
GIT_REPOSITORY https://github.com/hankhsu1996/jsonrpc-cpp-lib.git
GIT_TAG v2.1.2
)
FetchContent_MakeAvailable(jsonrpc)
# Link your target with the library
target_link_libraries(your_app PRIVATE jsonrpc::jsonrpc)This approach uses a pre-installed version of the library, appropriate for production environments and system-wide installations:
-
First, install the library:
# Configure the project cmake -S . -B build # Build the project cmake --build build # Install the library (may require sudo) cmake --install build
You can specify a custom installation prefix with
-DCMAKE_INSTALL_PREFIX=/your/custom/path -
Then use it in your project:
# Find the package find_package(jsonrpc REQUIRED) # Create your executable add_executable(your_app main.cpp) # Link against the library target_link_libraries(your_app PRIVATE jsonrpc::jsonrpc)
For projects using Conan for dependency management, create a conanfile.txt in your project directory:
[requires]
jsonrpc/2.1.2
[generators]
CMakeDeps
CMakeToolchainThen use Conan to install dependencies and generate CMake files:
conan install . --build=missing
cmake -DUSE_CONAN=ON -B build .
cmake --build buildHere's a simplified example of how to create a client and server using the library:
Server Example
#include <asio.hpp>
#include <jsonrpc/endpoint/endpoint.hpp>
#include <jsonrpc/transport/pipe_transport.hpp>
#include <nlohmann/json.hpp>
using jsonrpc::endpoint::RpcEndpoint;
using jsonrpc::transport::PipeTransport;
using Json = nlohmann::json;
// Calculator functions
auto Add(std::optional<Json> params) -> asio::awaitable<Json> {
double a = params.value()["a"];
double b = params.value()["b"];
co_return Json{{"result", a + b}};
}
auto RunServer(asio::any_io_executor executor, std::string socket_path)
-> asio::awaitable<void> {
// Create transport and endpoint
auto transport = std::make_unique<PipeTransport>(executor, socket_path, true);
auto server = std::make_shared<RpcEndpoint>(executor, std::move(transport));
// Register methods
server->RegisterMethodCall("add", Add);
// Register shutdown notification
server->RegisterNotification(
"stop",
[server](std::optional<Json>) -> asio::awaitable<void> {
co_await server->Shutdown();
co_return;
});
// Start server and wait for shutdown
co_await server->Start();
co_await server->WaitForShutdown();
co_return;
}
int main() {
asio::io_context io_context;
auto executor = io_context.get_executor();
const std::string socket_path = "/tmp/calculator_pipe";
// Run server with error handling
asio::co_spawn(
executor,
RunServer(executor, socket_path),
[](std::exception_ptr e) {
if (e) {
try { std::rethrow_exception(e); }
catch (const std::exception& ex) {
std::cerr << "Error: " << ex.what() << std::endl;
}
}
});
io_context.run();
return 0;
}Client Example
#include <asio.hpp>
#include <jsonrpc/endpoint/endpoint.hpp>
#include <jsonrpc/transport/pipe_transport.hpp>
#include <nlohmann/json.hpp>
using jsonrpc::endpoint::RpcEndpoint;
using jsonrpc::transport::PipeTransport;
using Json = nlohmann::json;
// Main client function
auto RunClient(asio::any_io_executor executor) -> asio::awaitable<void> {
// Create transport and RPC client
const std::string socket_path = "/tmp/calculator_pipe";
auto transport = std::make_unique<PipeTransport>(executor, socket_path);
// Create and initialize client
auto client = co_await RpcEndpoint::CreateClient(executor, std::move(transport));
// Call "add" method
Json params = {{"a", 10}, {"b", 5}};
Json result = co_await client->SendMethodCall("add", params);
std::cout << "Result: " << result.dump() << std::endl;
// Send shutdown notification
co_await client->SendNotification("stop");
// Clean shutdown
co_await client->Shutdown();
co_return;
}
int main() {
asio::io_context io_context;
auto executor = io_context.get_executor();
// Run client with error handling
asio::co_spawn(
executor,
RunClient(executor),
[](std::exception_ptr e) {
if (e) {
try { std::rethrow_exception(e); }
catch (const std::exception& ex) {
std::cerr << "Error: " << ex.what() << std::endl;
}
}
});
io_context.run();
return 0;
}For more examples including different transport types and complete applications, please refer to the examples folder.
Follow these steps to build, test, and set up your development environment. Bazel is the preferred method.
Step 1: Build the Project
bazel build //...
Step 2: Run Tests
bazel test //...
Step 1: Install Dependencies
conan profile detect --force
conan install . --build=missing
conan install . -s build_type=Debug --build=missing
Step 2: Configure and Build
cmake --preset release
cmake --build --preset release
Step 3: Run Tests
ctest --preset release
For Debug configuration:
cmake --preset debug
cmake --build --preset debug
ctest --preset debug
If you prefer not to use Conan, you can build the project with CMake directly:
cmake -S . -B build
cmake --build build
ctest --test-dir build
Generate the compile_commands.json file for tools like clang-tidy and clangd:
- Bazel: Run
bazel run @hedron_compile_commands//:refresh_all. - CMake: Simply build the project. The database will be generated automatically.
In both cases, the compile_commands.json file will be placed in the root directory.
Contributions are welcome. If you have suggestions or find issues, please open an issue or pull request.
This project is licensed under the MIT License. See the LICENSE file for more details.