Coding

Developing a complete client-server application with qooxdoo on Cloud9IDE. Part 2: Integrating socket.io

In the previous post, we have chosen the application architecture and integrated a connect server to dish out the frontend code to the clients. We now need to decide how to client and server should communicate.

Choosing the right communication layer: why REST is not enough

The dominant approach in the world of web apps is the use of HTTP requests and the REST paradigm. This allows for a very clean separation of data presentation on the client and data generation on the backend. The use of descriptive and clear URLs to query data increases the clarity of the code and also makes it possible to debug the backend easily. This differentiates REST from, for example, Remote Procedure Call solutions such as JSON-RPC (which is notoriously difficult to debug). Plus you can very easily provide a public API with which the application backend can be queried by third-party clients. Finally, web application frameworks such as Ruby on Rails, or node’s Express server excel in dealing with such requests.

However, there are drawbacks to this kind of solution. The most important one is that usually, the requests are unidirectional. You can query data, but you don’t know when new data is available on the server (unless you use some form of polling). Also, for all of their commendable readability, REST URLs allow very little complexity in the kind of data that can be communicated to the server. In addition, server frameworks introduce an additional layer — the router — between data consumer and data provider, and often force the MVC paradigm on the data interchange, even when this paradigm doesn’t really fit. In many cases, it would be much better if consumer and provider could communicate directly. Finally, REST and JSON-RPC communicate only between a specific client and server, and does not extend the communication to any other client.

Enter socket.io: realtime, bidirectional messaging

This is why I have always wanted to have a closer look at socket.io as a comprehensive solution for all client-server communication requirements. socket.io is a bidirectional messaging system that is compatible with a huge number of different browsers/platforms/transports. You get quasi-realtime connectivity in both directions, and you can even use it isn RPC-like fashion (i.e. send a “command” and wait for its execution). Most importantly, socket.io is not (mainly) about communication between a client and a server. It is a PUB-SUB (shorthand for “publish-subscribe”) system where clients can subscribe to channels and be notified when the server or some other client publishes a message on this channel. Importantly, the library can be considered very stable, since it is in use in many high-traffic production sites. The API is very simple, and it can be installed by a simple npm install socket.io.

Setting up socket.io with Architect

As with the connect server, we’ll integrate the code as an architect plugin that wraps socket.io (which could be always replaced by a different library that uses the same API). Create a new directory in plugins named “socket” with the following package.json:

{
  "name": "socket",
  "version": "0.0.1",
  "main": "socketio.js",
  "private": true,
  "plugin": {
    "consumes": ["http"],
    "provides": ["socket"]
  }
}

As you can see, the plugin requires that the http server is already set up, because it will hook into it. Then, add the line

{ packagePath: "../plugins/socket", namespace : "/testapp", loglevel : 1 }

to your config files (“source.js”/”build.js”), so that the plugin will be loaded by Architect. You can choose the level of logging of the socket.io server from 0 (= only errors) to 3 (=verbose debug log). One sensible setting would be to have 0 in the “build” config and 2 or 3 in the “source” config.
Finally, we need the plugin code.

// This plugin gives provides a socket.io server for the application

// options:
//   options.namespace is the namespace in which to accept messages.
//   options.loglevel

module.exports = function setup(options, imports, register)
{
    // dependencies
    var socketio = require("socket.io");
    var assert   = require("assert");

    // options/parameters
    var namespace = options.namespace;
    assert(namespace && typeof namespace=="string", "You must provide a namespace for the socket.io channels");
    var loglevel  = options.loglevel || 0;

    // attach socket.io server to http server
    var io = socketio.listen(imports.http.server);
    io.set('log level', loglevel);

    // this will be removed later, serves simply to see if everything works
    io.of(namespace).on('connection',function(socket){
        console.log('A new socket connected!');
        socket.emit("buttonlabel", "Press this button");
        socket.on("buttonpressed",function(data,fn){
          console.log("Message from client: " + data);
          fn("Hello from the server!");
          socket.emit("buttonlabel", "Button has been pressed");
        });
    });

    register(null,{
        // API
        socket : io.of(namespace)
    });
    console.log("socket.io server attached, namespace '%s', loglevel %s", namespace, loglevel);
}

Setting up the client

To use socket.io with qooxdoo, we need to tell the generator to include the code before the qooxdoo library, to make sure it is loaded when the qooxdoo application starts up. This works by inserting the following jobs in the config.json configuration file in the testapp directory:

  "jobs": {
      "source-script": {
          "add-script": [{
              "uri": "/socket.io/socket.io.js"
          }]
      },
      "build-script": {
          "add-script": [{
              "uri": "/socket.io/socket.io.min.js"
          }]
      }
  }

This loads the socket.io javascript files from a virtual path.

Finally, we need some code inside the qooxdoo application to interact with the server through our new communication channel. Change lines 54- inside source/class/testapp/Application.js to read like this:

 /*
      -------------------------------------------------------------------------
        Below is your actual application code...
      -------------------------------------------------------------------------
      */

      // set up socket.io
      var loc = document.location;
      var url = loc.protocol + "//" + loc.host;
      var socket = io.connect(url + "/testapp");

      // Create a button
      var button1 = new qx.ui.form.Button("...", "testapp/test.png");
      var doc = this.getRoot();
      doc.add(button1, {left: 100, top: 50});

      // Add an event listener for the button
      button1.addListener("execute", function(e) {
        socket.emit("buttonpressed", "Hello from client!", function(data){
          alert(data);
        });
      });

      // setup socket events
      socket.on('buttonlabel', function (data) {
        button1.setLabel(data);
      });

When you read this together with the socketio.js file, you can see what it does: A button is created with “…” as label. When the client receiveds the “buttonlabel” message from the server, it changes the label. When the user clicks on the button, the client sends a message to the server, and received data back in return, which then is alerted to the user. Finally, another message is dispatched by the server to change the label again.

This example behavior is completely useless, but it shows you that socket.io can fully replace any other means of client-server communication.

We now have setup application structure and communication channels. The next thing we need is authentication and authorization mechanisms, which allow to grant users different degrees of access to the application. This will be covered in the next post.

Advertisements

3 thoughts on “Developing a complete client-server application with qooxdoo on Cloud9IDE. Part 2: Integrating socket.io

  1. One downside to using socket.io instead of REST: constant http traffic if websockets aren’t available and socket.io resorts to long polling. Which I don’t mind (it’s not a lot of bandwidth), but Google Chrome sometimes thinks it’s a DDoS attack and “throttles” the socket.io calls 😦

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s