Improved RPC by JSON

If the data exchange is meant to use only strings, the basic RPC support provided by RabbitMQ is more than adequate, and the StringRpcServer class makes it even easier. But what if we want to use other data types? The rabbit answer to this question is JSON-RPC. This RPC protocol based on JSON is implemented in a couple of classes, JsonRpcServer and JsonRpcClient, that take care of the low leve details and let us free to use RPC in a more natural way.

To get acquainted to it, I went through the JSON-RPC example provided in the test folder of the source RabbitMQ distribution, package com.rabbitmq.examples, files HelloJsonService.java, HelloJsonClient.java, and HelloJsonServer.java. I have reworked it and you can see my commented version in this post, full Java source code is available on github.

The service

The JSON-RPC interface between client and server is represented by a service, that exposes to the client which methods are available on the server:
public interface JsonSvc {
    String greeting(String name);
    int sum(List<Integer> values);
}
This interface is going to be implemented by a class, I called it MyJsonSvc, used internally by the RPC server.

The client

In the client, the service is used to provide access to the server functionality, as shown here:
// ...
JsonRpcClient client = new JsonRpcClient(channel, "", QUEUE_NAME, RPC_TIMEOUT_ONE_SECOND); // 1
JsonSvc service = (JsonSvc)client.createProxy(JsonSvc.class); // 2

System.out.println(service.greeting("Rabbit")); // 3

List<Integer> numbers = new ArrayList<>();
numbers.add(1);
numbers.add(2);
numbers.add(3);
System.out.println("1 + 2 + 3 = " + service.sum(numbers));
// ...
client.publish(null, null); // 3
1. A JSON-RPC client is created, specifying a channel, a queue, and also an optional timeout in milliseconds.
2. This is the core of the example, the rabbit JSON-RPC client creates a proxy for the passed interface, then we can use it in our client as shown below.
3. We could still use the client as a plain RpcClient client, bypassing the JSON layer. Here we are sending an empty message with no associated properties.

The server

Implementing the JSON-RPC capabilities in the server is straightforward:
// ...
channel.queueDeclare(QUEUE_NAME, false, false, false, null);
JsonRpcServer server = new MyRpcServer(channel, QUEUE_NAME, JsonSvc.class, new MyJsonSvc()); // 1

System.out.println("JSON-RPC server is up.");
server.mainloop(); // 2
// ...
1. We create a JsonRpcServer object specifying a service on which it would operate, and the interface that it should use to access it.
2. Then we start looping on it.

You have surely noticed in the code here above that I didn't create a plain JsonRpcServer, but a mysterious MyRpcServer. I did that because I wrote the client to be a bit smarter than usual, calling not only the JsonSvc methods, but also sending a plain (empty) message. To give the server a way to manage it as expected (in this case an empty message is seen as a signal to terminate the server run), I have to add functionality to the JsonRpcServer, as we already have seen for StringRpcServer. Actually, all we need is just reimplementing handleCast(), so that we can react correctly to an empty message:
// ...
private class MyRpcServer extends JsonRpcServer {
    // ...
    @Override
    public void handleCast(byte[] requestBody)
    {
        if(requestBody.length == 0) {
            System.out.println("Empty message, terminating.");
            terminateMainloop();
        }
    }
}

No comments:

Post a Comment