joncol / jcon-cpp

JSON RPC 2.0 library using C++ and Qt 6
MIT License
190 stars 65 forks source link

jcon-cpp

jcon-cpp is a portable C++ JSON RPC 2.0 library that depends on Qt 6.

Introduction

If you're using C++ 17 and Qt, and want to create a JSON RPC 2.0 client or server, using either TCP or WebSockets as underlying transport layer, then jcon-cpp might prove useful.

In all of the following, replace "Tcp" with "WebSocket" to change the transport method.

Platforms supported are: Linux, Windows, Mac OS, and Android.

Building the Library

  1. Get the source.
  2. Create a build directory in the top directory.
  3. Change to the build directory: cd build.
  4. cmake ..
  5. make -j4

The build depends on the build directive CMAKE_PREFIX_PATH to find the required Qt dependencies, so if your CMake doesn't pick up on where to find Qt, try adding cmake -DCMAKE_PREFIX_PATH=<QTDIR> .. in step 4 above.

Include Files

Depending on if you're implementing a server or a client and whether you're using TCP or WebSockets, you need to include some of these files:

#include <jcon/json_rpc_tcp_client.h>
#include <jcon/json_rpc_tcp_server.h>
#include <jcon/json_rpc_websocket_client.h>
#include <jcon/json_rpc_websocket_server.h>

Example Code

There's example code of both a server and a client in the file src/main.cpp.

Creating a Server

auto rpc_server = new jcon::JsonRpcTcpServer(parent);

// optionally, enable sending unsolicited notifications
// PS: not part of JSON-RPC standard
rpc_server->enableSendNotification(true);

Create a service (a collection of invokable methods):

  1. Make your service class inherit QObject
  2. Make sure your service method is accessible by the Qt meta object system (either by using the Q_INVOKABLE macro or by putting the method in a public slots: section).
  3. (optionally) Provide unsolicited notifications by emitting sendUnsolicitedNotification. Requires calling enableSendNotification(true) on the server.

For instance:

class ExampleService : public QObject
{
    Q_OBJECT

public:
    ExampleService(QObject* parent = nullptr);
    virtual ~ExampleService();

    Q_INVOKABLE int getRandomInt(int limit);

signals:
    void sendUnsolicitedNotification(const QString&, const QVariant&);
};

Parameters and return types of methods are automatically matched against the JSON RPC call, using the Qt Meta object system, and you can use lists (QVariantList) and dictionary type objects (QVariantMap) in addition to the standard primitive types such as QString, bool, int, float, etc.

However, sendUnsolicitedNotification signal must be declared exactly as shown in the example. The first argument is the notification key, and the second argument is the actual unsolicited notification.

Register your service with:

rpc_server->registerServices({ new ExampleService() });

Note that (as of 2018-11-21) there is also a variant of registerServices that takes a QMap<QObject*, QString>, where the keys are the services, and the values are strings that will need to be used as prefixes when calling the corresponding RPC methods. This can be used as a simple namespace mechanism. Please refer to the example code in src/main.cpp.

The server will take over ownership of the service object, and the memory will be freed at shutdown. Note that the registerServices method changed its signature 2016-10-20, from being a variadic template expecting unique_ptrs, to taking a QObjectList.

Finally, start listening for client connections by:

rpc_server->listen(6001);

Specify whatever port you want to use.

Creating a Client

Simple:

auto rpc_client = new jcon::JsonRpcTcpClient(parent);
rpc_client->connectToServer("127.0.0.1", 6001);

// optionally, enable receiving unsolicited notifications
// PS: not part of JSON-RPC standard
rpc_client->enableSendNotification(true);

(No need to use a smart pointer here, since the destructor will be called as long as a non-null parent QObject is provided.)

Invoking a Remote Method Asynchronously

auto req = rpc_client->callAsync("getRandomInt", 10);

The returned object (of type std::shared_ptr<JsonRpcRequest>) can be used to set up a callback, that is invoked when the result of the JSON RPC call is ready:

req->connect(req.get(), &jcon::JsonRpcRequest::result,
             [](const QVariant& result) {
                 qDebug() << "result of RPC call:" << result;
                 qApp->exit();
             });

To handle errors:

req->connect(req.get(), &jcon::JsonRpcRequest::error,
             [](int code, const QString& message, const QVariant& data) {
                 qDebug() << "RPC error: " << message << " (" << code << ")";
                 qApp->exit();
             });

Invoking a Remote Method Synchronously

auto result = rpc_client->call("getRandomInt", 10);

if (result->isSuccess()) {
    QVariant res = result->result();
} else {
    QString err_str = result->toString();
}

Handling unsolicited notification

QObject::connect(rpc_client, &jcon::JsonRpcClient::notificationReceived,
    &app, [](const QString& key, const QVariant& value){
        qDebug() << "Received notification:"
                 << "Key:" << key
                 << "Value:" << value;
    });

Expanding a List of Arguments

If you want to expand a list of arguments (instead of passing the list as a single argument), use callExpandArgs and callAsyncExpandArgs.

Named Parameters

If you want to call a function using named parameters, use callNamedParams and callAsyncNamedParams. This will pass the parameters as a QVariantMap instead of converting it to a QVariantList.

Named parameters are automatically handled on the (jcon-cpp) server side.

Known Issues

Contributing

Bug reports and pull requests are welcome on GitHub at https://github.com/joncol/jcon-cpp.

Please follow these guidelines for creating commit messages.

Also make sure to follow the existing code style. No lines with more than 80 characters, spaces instead of tabs for instance.

License

The library is available as open source under the terms of the MIT License.