Introducing Cute: RPC the way it was always meant to be

This post introduces Cute. Cute is a powerful yet extremely easy-to-use and secure C++ RPC (Remote Procedure Call) framework for building modern connected apps and backend services quickly and easily. Cute enables developers to use the intuitive Qt signals and slots mechanism to communicate over a network seamlessly through remote signal-slot connections and direct remote slot calls.

The Evolution of RPC Systems

The general communication process involves the messages exchanged by the communicating peers and the actions that the exchanged messages trigger in them. Thus, the evolution of network-based communication can be classified as either message-based or action-based.

RPC systems focus on the actions that can be triggered during communication. These systems have existed since the 80s and evolved over time. The last widely known RPC system is gRPC, created by Google to be used internally on its microservices-based architecture.

Cute builds upon the previous experiences of other RPC systems to provide developers with a secure, high-performance, and easy-to-use framework for network communication.


Why Cute?

Productiveness

The signals and slots mechanism Cute uses to abstract communications over a network is a central feature of Qt programming and a widely known and easy-to-use technology. With Cute, developers can focus on writing application code instead of dealing with sockets or reliable, safe, and efficient network data processing. Also, by relying heavily upon Qt's meta-type system, Cute can provide concise, intuitive, and easy-to-use SDKs with a negligible learning curve.

The greeter example shown later in this post enables you to compare Cute and gRPC in terms of productivity.

High-Performance Server

Implementing a secure and high-performance server is a huge task. That's why, unlike other RPC systems, Cute provides its own server and frees developers from the burden and pitfalls of implementing one.

Cute provides a high-performance, asynchronous, event-driven, Linux-based server. In addition, by integrating epoll into Qt's event handling system, the Cute server can handle high network loads while staying responsive. Also, the Cute server uses error handlers to enable developers to identify and deal with malicious clients.

On the server side, developers only have to create and map classes to endpoints. The Cute server fetches mapped classes from a user-specified shared library loaded during startup. Cute also provides client SDKs for Linux, Windows, macOS, iOS, Android, and WebAssembly.

Security

The Cute server uses TLS encryption to guarantee data confidentiality. Also, the Cute server provides highly configurable encryption settings. Ciphers, elliptic curves, custom Diffie-Hellman parameters, two-way SSL, the minimum permitted TLS protocol, and custom CA certificates can all be specified by users. Additionally, the Cute server uses HMAC to enforce message integrity on all of its editions on both unencrypted and encrypted connections.

Many RPC systems abstract the network side of the interaction wrongly. Information hiding aims at making developers more productive, but abstractions must not hide important behaviors inherent to network communication. The Cute server enables developers to identify clients and take action whenever a suspicious activity occurs on the server side.

Truly Two-Way Communication

With Cute, clients can connect to remote signals to receive server-side events. Cute provides the most straightforward and intuitive way for backend code to notify connected clients.

WebSocket-based transport

Cute uses WebSocket as its transfer protocol. WebSocket is a lightweight binary protocol that enables low latency, bidirectional communication between connected peers. Additionally, WebSocket is a friendly protocol for the intermediaries (proxies, firewalls, etc.) that may exist in the connection chain.

Event-Driven Architecture

The signals and slots mechanism plays the perfect role as an abstraction to model network-based communications between objects due to its ability to naturally represent asynchronous interactions through events. Cute integrates with Qt's event handling system to provide an intuitive and high-performance messaging system.

Qt for Encoding and Marshaling

Qt's meta-type system provides powerful introspection capabilities, enabling Cute to provide tiny SDKs that are extremely easy to use. With Cute, developers only have to write QObject-derived classes with specially-tagged signals and slots to communicate over a network. Cute does all the hard work under the hood by relying on Qt's meta-type system and signals and slots mechanism.

In addition, Cute provides a custom data streamer capable of handling rogue data sent by malicious clients trying to abuse the server.


Where to use Cute?

Microservices

Cute provides an elegant solution to implement communication among microservices. For C++ programmers that do not know/use Qt, it is much easier to use Cute than learning other RPC alternatives, as the greeter example in this post demonstrates.

Additionally, developers can use HTTP to communicate with microservices implemented in other languages, as the Cute server fully supports HTTP-based interactions.

Mobile/IoT/Desktop/Browser apps to Backend Services

With Qt, developers can create mobile/IoT/desktop/browser apps. With Cute, these apps can interact with backend code seamlessly. Cute provides client SDKs for Linux, Windows, macOS, iOS, Android, and WebAssembly.

SaaS/API Providers

The signals and slots mechanism enables SaaS/API providers to create rich interaction models for their products.

Cute also protects the intellectual property of SaaS/API providers. Although Cute abstracts the network from clients and enables them to interact with remote objects as if they were local, server-side code stays on the server. Clients only need to know the signatures of the remote signals and slots to interact with remote objects on the server side.


The Greeter Example

In the greeter example, clients send a string to the server that responds with the sent string prepended with the "Hello " string. This example was extracted from the official gRPC repository.

We will show implementations of the greeter example using both gRPC and Cute.

First, we show the gRPC-based implementation from the gRPC repository.

The server is implemented using gRPC as follows:

class ServerImpl final {
 public:
  ~ServerImpl() {
    server_->Shutdown();
    cq_->Shutdown();
  }

  void Run() {
    std::string server_address("0.0.0.0:50051");
    ServerBuilder builder;
    builder.AddListeningPort(server_address, grpc::InsecureServerCredentials());
    builder.RegisterService(&service_);
    cq_ = builder.AddCompletionQueue();
    server_ = builder.BuildAndStart();
    std::cout << "Server listening on " << server_address << std::endl;
    HandleRpcs();
  }

 private:
  class CallData {
   public:
    CallData(Greeter::AsyncService* service, ServerCompletionQueue* cq)
        : service_(service), cq_(cq), responder_(&ctx_), status_(CREATE) {
      Proceed();
    }

    void Proceed() {
      if (status_ == CREATE) {
        status_ = PROCESS;
        service_->RequestSayHello(&ctx_, &request_, &responder_, cq_, cq_,
                                  this);
      } else if (status_ == PROCESS) {
        new CallData(service_, cq_);
        std::string prefix("Hello ");
        reply_.set_message(prefix + request_.name());
        status_ = FINISH;
        responder_.Finish(reply_, Status::OK, this);
      } else {
        GPR_ASSERT(status_ == FINISH);
        delete this;
      }
    }

   private:
    Greeter::AsyncService* service_;
    ServerCompletionQueue* cq_;
    ServerContext ctx_;
    HelloRequest request_;
    HelloReply reply_;
    ServerAsyncResponseWriter<HelloReply> responder_;
    enum CallStatus { CREATE, PROCESS, FINISH };
    CallStatus status_;  // The current serving state.
  };

  void HandleRpcs() {
    new CallData(&service_, cq_.get());
    void* tag;  // uniquely identifies a request.
    bool ok;
    while (true) {
      GPR_ASSERT(cq_->Next(&tag, &ok));
      GPR_ASSERT(ok);
      static_cast<CallData*>(tag)->Proceed();
    }
  }

  std::unique_ptr<ServerCompletionQueue> cq_;
  Greeter::AsyncService service_;
  std::unique_ptr<Server> server_;
};

int main(int argc, char** argv) {
  ServerImpl server;
  server.Run();
  return 0;
}

And the client is implemented using gRPC as shown below:

class GreeterClient {
 public:
  explicit GreeterClient(std::shared_ptr<Channel> channel)
      : stub_(Greeter::NewStub(channel)) {}

  std::string SayHello(const std::string& user) {
    HelloRequest request;
    request.set_name(user);
    HelloReply reply;
    ClientContext context;
    CompletionQueue cq;
    Status status;
    std::unique_ptr<ClientAsyncResponseReader<HelloReply> > rpc(
        stub_->PrepareAsyncSayHello(&context, request, &cq));
    rpc->StartCall();
    rpc->Finish(&reply, &status, (void*)1);
    void* got_tag;
    bool ok = false;
    GPR_ASSERT(cq.Next(&got_tag, &ok));
    GPR_ASSERT(got_tag == (void*)1);
    GPR_ASSERT(ok);
    if (status.ok()) {
      return reply.message();
    } else {
      return "RPC failed";
    }
  }

 private:
  std::unique_ptr<Greeter::Stub> stub_;
};

int main(int argc, char** argv) {
  GreeterClient greeter(grpc::CreateChannel(
      "localhost:50051", grpc::InsecureChannelCredentials()));
  std::string user("world");
  std::string reply = greeter.SayHello(user);  // The actual RPC call!
  std::cout << "Greeter received: " << reply << std::endl;
  return 0;
}

The example above explains why many developers switched back from gRPC to Restful services.

Now we will use Cute to implement the greeter example. In the code below, the Greeter class is implemented on the server side, and clients interact with instances of the Greeter class as if they were local.

#include <CuteServer.h>
#include <QSharedPointer>

class Greeter : public QObject
{
Q_OBJECT
public:
    Greeter(QSharedPointer<Cute::IConnectionInformation> ci)
    {Q_UNUSED(ci)}
    ~Greeter() override = default;

public slots:
    REMOTE_SLOT QString greetMe(QString message)
    {
        auto response = message.prepend("Hello ");
        emit newGreetMessage(response);
        return response;
    }

signals:
    REMOTE_SIGNAL void newGreetMessage(QString);
};

Classes with remote signals and slots are registered to the Cute server as follows:

// Classes are registered in source files
#include "Greeter.h"
REGISTER_REMOTE_OBJECT("/greeter", Greeter);

Clients can obtain the greeting message from either the response of the remote slot call or by connecting to the remote newGreetMessage signal. Below we show both ways that clients can use to obtain the greeting message:

#include <CuteClient.h>
#include <QtDebug>
#include <QCoreApplication>

using namespace Cute::Client;

class GreeterClient : public QObject
{
Q_OBJECT
public:
    GreeterClient(QString user)
        : m_greeter("Greeter",
                    QUrl("cute://127.0.100.125:1234/greeter"))
    {
        // Establish remote signal-slot connection
        RemoteObject::connect(&m_greeter,
            SIGNAL(newGreetMessage(QString)),
            this,
            SLOT(onNewGreetMessage(QString)));
        // Call the remote slot directly
        m_slotResponse = m_greeter.callRemoteSlot("greetMe", user);
        QObject::connect(m_slotResponse.data(),
            &IRemoteSlotResponse::responded,
            [](const QVariant &response) {
            qWarning() << "Greet message is " 
                << response.toString();
            QCoreApplication::quit();});
    }

public slots:
    // Remote signal-slot connections require public slots.
    void onNewGreetMessage(QString message)
    {
        qWarning() << "Greet message from remote signal: "
            << message;
    }

private:
    RemoteObject m_greeter;
    QSharedPointer<IRemoteSlotResponse> m_slotResponse;
};

The difference in complexity between the two implementations is staggering.


Summary

The simple greeter example illustrates how Cute streamlines network-based interaction by enabling developers to use signals and slots to interact over a network seamlessly. The Cute-based implementation code of the greeter example is intuitive, simple to write, understand, and clearly focuses on the app business logic.

Cute frees developers to focus on writing application code instead of dealing with network programming. In addition, the signals and slots mechanism provided by Qt plays the perfect role as an abstraction to model network-based interactions between objects.

The Cute server and SDKs provide an RPC framework that empowers developers to build modern connected systems quickly and easily. You can learn to use the Cute server and SDKs on the Learning Hub. Also, you can view more on our blog or website.