Quick start

A quick guide to getting the project set up and running a project.

The Development Environment

The minimum JVM requirement for the library is Java 8.

Additionally, for the Native Library option a minimum of Linux kernel 3.9 is required.

Using Maven

To include the library in a maven project a similar entry in the pom.xml should be used.

  <dependency>
	<groupId>rubris</groupId>
	<artifactId>rubris-io</artifactId>
	<version>${rubris-io.version}</version>
  </dependency>

Instantiating the Server

The Server class is a simple Class intended to be embedded into your application and is used to register your own Endpoints and supply configuration options to the library:

        // List of My handlers
        List<Object> userHandlers = new ArrayList<>();
        ....

        // create the server
        Server s = new Server();

        // add the user handlers
        s.setUserHandlers(userHandlers);

        // initialise and start the server component
        s.init();

        // ensure it does not exit
        CountDownLatch l = new CountDownLatch(1);
        l.await();

        // when we are interrupted close the server to free up resources
        s.close();

This is not much use as we have not controlled what socket configuration it uses, what the paths will be and any handlers to receive messages.

We have 2 choices:

  Server s = new Server();
  s.getConfig().getSocketConfig().setListenPort(port);
  SocketConfig socketConfig = new SocketConfig();
  socketConfig.setBindAddress("127.0.0.1");
  socketConfig.setListenPort(8099);
  ModuleConfig config = new ModuleConfig();
  config.setSocketConfig(socketConfig)

  Server s = new Server();
  s.setConfig(config)

Set up the bind address and listening port

The listening port defines what port the app will accept connections on. The HTTP and WS port is the same in Rubris-IO and it is the protocol request type that determines which is used. This enables functionality such as upgrading an existing HTTP connection to WS without needing to reconnect the socket.

  Server s = new Server();
  ModuleConfig config = s.getConfig();
  config.getSocketConfig().setListenPort(8091);
  config.getSocketConfig().setBindAddress("127.0.0.1");

The bind address works in the same way that one normally sets the bind address in Java.

The Service Path

  Server s = new Server();
  ModuleConfig config = s.getConfig();
  config.getSocketConfig().setBindAddress("127.0.0.1");
  config.getSocketConfig().setListenPort(8091);
  ((HttpConfig) config.getTransportConfig(ModuleConfig.HTTP))
          .setServicePath("mypath");

This will result in a url which forms the base of the protocol path as:

http://127.0.0.1:8091/mypath

The service path is the base url that the server uses to identify the root of all the Engine-IO requests used for polling and messaging. All the requests starting with this url (controlled on the client by the Engine-IO library) are assumed to either be a POST of messaging data or a GET that acts as a poll for outstanding data.

URLs that are different from this are classified as direct url requests. Direct requests are handled outside the Rubris message processing pipeline and are handled as raw http requests (although the responses are still asynchronous - see the Handler’s guide.

Adding handlers

Adding handlers is covered in more depth in the Handler’s guide. However, a simple set up for a request response handler would be:

  @Endpoint(name = "meta")
  class MetaHandler implements AsynchronousHandler {

        @Override
        public void onMessage(User user, Object message, DataHandle handle) {
                LOGGER.debug("Meta Message received " + message);
                handle.onMessage(s + " response META");
        }
  }

  ...

  List<Object> userHandlers = new ArrayList<>();
  userHandlers.add(new MetaHandler());
  ....

  s.setUserHandlers(userHandlers);

This will create an RPC channel called meta which all users can send messages to.

Each defined handler represents a Channel.

A Channel can be either a Request/Reply Channel, A Subscription channel, or a Private Subscription channel. The different types of channels are entirely controlled by the config. There is no special naming convention and the developer chooses how these manifest.

Sample Launcher

A simple launcher class would then look like: See the ServiceLaucnher in the test package as a sample.

public class ServerLauncher {

  public static void main(String[] args) throws Exception {
    ServerLauncher launcher = new ServerLauncher();
    int port = 0;
    if (args.length > 0) {
          port = Integer.parseInt(args[0]);
    }
    String path = "meta";

    if (args.length > 1) {
      path = args[1];
    }
    launcher.run(port, path);
  }


  public void run(int port, String path) throws Exception {
    Server s = new Server();
    List<Object> userHandlers = new ArrayList<>();
    createAPI(userHandlers);

    s.setUserHandlers(userHandlers);
    s.getConfig().getSocketConfig().setListenPort(port);

    ((HttpConfig)s.getConfig().getTransportConfig(ModuleConfig.HTTP))
      .setServicePath(path);

    s.init();

    CountDownLatch l = new CountDownLatch(1);
    l.await();

    s.close();
  }

  protected void createAPI(List<Object> userHandlers) {
    userHandlers.add(new MetaHandler());
  }

  @Endpoint(name = "meta")
  static class MetaHandler implements AsynchronousHandler {

        @Override
        public void onMessage(User user, Object message, DataHandle handle) {
          String s = null;
          if (message instanceof byte[]) {
                  s = new String((byte[]) message);
          }
          LOGGER.debug("Meta Message received " + message);
          handle.onMessage(s + " response META");
        }

  }
}

The Client

For the client side we know want to communicate with our server.

The Client libraries


<script src="/path/js/rubris-io.js"></script>
<script src="/path/js/engine-io/1.6.8/engine.io.js"></script>

For IE11 you will need to use the TextEncoder polyfill as it is not supported natively:


<script src="/path/js/text-encoding/encoding.js"></script>

Initialising

Multiple instances of the rubris object can be held on a page or on different tabs and are constructed like so:


var rubris = new Rubris({});

Callback APIs

Rather than wrapping EngineIO, the Rubris library plugs itself into the EngineIO library to try and provide a small amount of intrusion into the natural style of using EngineIO.

/ create engine socket as normal
socket = eio.Socket({ path : mypath , port: 8091, 
  protocol: "http", host: 127.0.0.1 });
socket.binaryType = 'arraybuffer';

// on socket open set socket on rubris to enbale use of socket event emitter
socket.once('open', function(){
  rubris.setSocket(socket)

  // make the initial channels call and set up the library
  // this must be done first as all messages require a channelid
        rubris.initChannels();
    ....
}

Once the root object is initialise then we can register callback functions to receive the response data:

IMPORTANT: the “message” callback MUST be set in order for Rubris to parse the response messages.


// The engineIO callback for messages.
// Register Rubris library to parse or wrap with own function
socket.on('message', function(data){
		rubris.parseMessage(data);
});

// Following callback functions will be called with above message registration

// callback resulting from init message with the all channels and status
socket.on('channels', function(data){
	....
});

// RPC responses
socket.on('rpc', function(data){
	...
});

As Engine-io is asynchronous it is only after the channels message has been processed that messages can be sent (as you require the channelId to create a message. Therefore all RPC and subscribe message can only be created after the on(“channels”…) callback has been called.

Sending data

Sending data utilises the engineIO socket library with a few methods provided to generate the appropriate message formats for the Rubris protocol itself

The general format is:

ChannelId is the numeric id of the channel you want to send on as returned from the channels callback.

MessageId is the sequence number of the message.


socket.send(rubris.rpcMessage($PAYLOAD, channelId, messageid));

To handle the message for example we extract the payload


  function displayRPC(message){
    // get the length of the payload
    var length = message.payload.end - message.payload.start;
	
    // get the payload unit8 array as a string and display
    $('#messageId').text( message.id+ ': time'+
      ((new Date).getTime()-message.timestamp)+" "
      +rubris.getPayloadAsText(message.payload));
}

Response message format

The response messages in the callback are defined in the following prototype:


function Message(type, timestamp, channel, id, status, payload) {
      this.type = type;
      this.timestamp = timestamp;
      this.id = id;
      this.channel = channel;
      this.status = status;
      this.payload = payload;
  };


The payload object is:


function Payload(data, type, start, end) {
      this.data = data;
      this.start = start;
      this.end = end;
      this.type = type;

  }

See the ClientGuide for further details.