Vert.x 3 real time web apps

One of the in­ter­est­ing fea­tures of Vert.x is the SockJS event bus bridge. This piece of soft­ware al­lows ex­ter­nal ap­pli­ca­tions to com­mu­ni­cate with Vert.x event bus using Web­sock­ets and if your browser does not sup­port it then it grace­fully de­grades to pool­ing AJAX calls.

Web­Sock­ets bring a new level of in­ter­ac­tion to the web, they re­ally bring real time to web ap­pli­ca­tions due to the fact that its com­mu­ni­ca­tion model is bi-​directional in con­trast to the tra­di­tional HTTP model where a client can ini­ti­ate a data re­quest to a server but not the other way around.

In this small post I will demon­strate how you can cre­ate a sim­ple col­lab­o­ra­tive draw­ing app. The idea is sim­ple, all users that open the app will be be pre­sented with a empty can­vas and what they draw or is drawn on other can­vas is shared in real time on their screen.

For the sake of sim­plic­ity and mak­ing this post light there is no se­cu­rity in­volved so, every­one is free to lis­ten to what is being drawn, how­ever the ex­ter­nal ap­pli­ca­tion has lim­ited read write ac­cess to a sin­gle ad­dress on Vert.x event bus, en­sur­ing that other ser­vices run­ning on the clus­ter will not be ex­posed.

This is what you should ex­pect to see:

Screencast

Bootstrap a project

If you fol­lowed the pre­vi­ous se­ries on Vert.x de­vel­op­ment, you saw that Java and Maven were the main topic, since Vert.x is poly­glot I will focus on JavaScript and NPM as my pro­gram­ming lan­guage and pack­age man­age­ment tool.

With NPM start by cre­at­ing a package.json, in order to do this we should run:

npm init

This will present a se­lec­tion of ques­tions and in the end you should have a basic package.json file. This con­fig­u­ra­tion is very basic so you need to add a de­pen­dency to Vert.x so you can run the ap­pli­ca­tion. You can add it to the dependencies prop­erty and it should look more or less like this:

{
  "name": "draw",
  "private": true,
  "dependencies": {
    "vertx3-full": "3.0.0-1"
  },
  "scripts": {
    "start": "vertx run server.js"
  },
  "version": "1.0.0",
  "main": "server.js",
  "devDependencies": {},
  "author": "",
  "license": "ISC",
  "description": "A Real Time Drawing App"
}

If you do not know why there is the de­pen­dency on vertx3-full or why the added scripts prop­erty please check the older blog post about it.

Project Structure

This post has no pref­er­ence over project struc­ture, so if you do not agree with the struc­ture used here feel free to use what you feel best. For this ex­am­ple I will keep it to:

├── package.json
├── server.js
└── webroot
  ├── assets
  │   └── js
  │     ├── script.js
  │     └── vertxbus.js
  └── index.html

3 directories, 5 files

As you can imag­ine server.js will be our Vert.x ap­pli­ca­tion and every­thing under webroot will be the client ap­pli­ca­tion.

The client ap­pli­ca­tion is not re­ally Vert.x spe­cific and could in the­ory be used by any other frame­work so I will go lightly over its code.

Client Application

Our ap­pli­ca­tion main entry point is as one can ex­pect index.html. In the index file de­fine the fol­low­ing HTML:

<!DOCTYPE html>
<html>
<head>
  <meta charset="utf-8"/>
  <title>Real time drawing App</title>
  <!--[if lt IE 9]>
  <script src="http://html5shiv.googlecode.com/svn/trunk/html5.js"></script>
  <![endif]-->
</head>

<body>
<canvas id="paper" width="1900" height="1000">
  Your browser needs to support canvas for this to work!
</canvas>

<!-- JavaScript includes. -->
<script src="http://code.jquery.com/jquery-1.8.0.min.js"></script>
<script src="//cdn.jsdelivr.net/sockjs/0.3.4/sockjs.min.js"></script>
<script src='assets/js/vertxbus.js'></script>
<script src="assets/js/script.js"></script>

</body>
</html>

As I pre­vi­ously wrote, the idea is to keep it as sim­ple as pos­si­ble so it is all about hav­ing a can­vas el­e­ment and a ap­pli­ca­tion main script script.js. All the rest are files served by CDNs that pro­vide com­mon web ap­pli­ca­tion li­braries such as jQuery, HTML5 shim for older browsers, SockJS client and vertxbus bridge.

The main code is on script.js file:

$(function () {

  // This demo depends on the canvas element
  if (!('getContext' in document.createElement('canvas'))) {
    alert('Sorry, it looks like your browser does not support canvas!');
    return false;
  }

  var doc = $(document),
    canvas = $('#paper'),
    ctx = canvas[0].getContext('2d');

  // Generate an unique ID
  var id = Math.round($.now() * Math.random());

  // A flag for drawing activity
  var drawing = false;

  var clients = {};
  // create a event bus bridge to the server that served this file
  var eb = new vertx.EventBus(
      window.location.protocol + '//' + window.location.hostname + ':' + window.location.port + '/eventbus');

  eb.onopen = function () {
    // listen to draw events
    eb.registerHandler('draw', function (data) {
      // Is the user drawing?
      if (data.drawing && clients[data.id]) {

        // Draw a line on the canvas. clients[data.id] holds
        // the previous position of this user's mouse pointer

        drawLine(clients[data.id].x, clients[data.id].y, data.x, data.y);
      }

      // Saving the current client state
      clients[data.id] = data;
      clients[data.id].updated = $.now();
    });
  };

  var prev = {};

  canvas.on('mousedown', function (e) {
    e.preventDefault();
    drawing = true;
    prev.x = e.pageX;
    prev.y = e.pageY;
  });

  doc.bind('mouseup mouseleave', function () {
    drawing = false;
  });

  var lastEmit = $.now();

  doc.on('mousemove', function (e) {
    if ($.now() - lastEmit > 30) {
      eb.publish('draw', {
        'x': e.pageX,
        'y': e.pageY,
        'drawing': drawing,
        'id': id
      });
      lastEmit = $.now();
    }

    // Draw a line for the current user's movement, as it is
    // not received in the eventbus

    if (drawing) {

      drawLine(prev.x, prev.y, e.pageX, e.pageY);

      prev.x = e.pageX;
      prev.y = e.pageY;
    }
  });

  // Remove inactive clients after 10 seconds of inactivity
  setInterval(function () {

    for (var ident in clients) {
      if (clients.hasOwnProperty(ident)) {
        if ($.now() - clients[ident].updated > 10000) {
          // Last update was more than 10 seconds ago.
          // This user has probably closed the page
          delete clients[ident];
        }
      }
    }

  }, 10000);

  function drawLine(fromx, fromy, tox, toy) {
    ctx.moveTo(fromx, fromy);
    ctx.lineTo(tox, toy);
    ctx.stroke();
  }

});

The most im­por­tant part in this code is all the code re­lated to eb. The vari­able eb is our bridge to the event bus, Start by cre­at­ing a bridge using the vertx.EventBus ob­ject and de­fine where to con­nect, using the de­tails of the cur­rent win­dow lo­ca­tion.

Then add a onopen lis­tener that will sub­scribe to the ad­dress draw on the event bus so it can lis­ten to all mes­sages re­gard­ing draw­ing and per­form the draw­ing ac­tions. Since lis­ten­ing is not enough I also add a mouse lis­tener to the doc­u­ment so when it moves it pub­lishes events to the draw ad­dress.

Note that I am using publish and not send, the rea­son should be ob­vi­ous, I want every­one to know this users mouse move­ments, I am not in­ter­ested on send­ing the events to just a sin­gle user. You can see now that if you want to have a draw­ing app in a one on one user basis then in­stead of publish() you should use send().

Server Application

The server code is quite straight for­ward, all you need is:

var Router = require("vertx-web-js/router");
var SockJSHandler = require("vertx-web-js/sock_js_handler");
var StaticHandler = require("vertx-web-js/static_handler");

var router = Router.router(vertx);

// Allow outbound traffic to the draw address

var options = {
  "outboundPermitteds" : [{"address" : "draw"}],
  "inboundPermitteds" :  [{"address" : "draw"}]
};

router.route("/eventbus/*").handler(SockJSHandler.create(vertx).bridge(options).handle);

// Serve the static resources
router.route().handler(StaticHandler.create().handle);

vertx.createHttpServer().requestHandler(router.accept).listen(8080);

We start with the usual im­ports, we im­port a ref­er­ence to the Router ob­ject and a cou­ple of helper han­dlers SockJSHandler and StaticHandler. As their names should tell you one han­dler will be re­spon­si­ble to han­dle all SockJS data and the other all HTTP file serv­ing re­quests.

We then add then to a router and start a HTTP server that will han­dle all in­com­ing re­quest using the han­dler ac­cept func­tion. Fi­nally we lis­ten on port 8080 and we are ready.

Note that there is a op­tions ob­ject where a cou­ple of prop­er­ties are de­fined outbound/inbound per­mit­ted ad­dresses. With­out this con­fig­u­ra­tion the ex­ter­nal ap­pli­ca­tion will not be al­lowed to con­nect to the vert.x bus, in fact the de­fault con­fig­u­ra­tion of the Sock­JSHan­dler is deny all. So you must spec­ify ex­plic­itly which ad­dress are al­lowed to re­ceive mes­sages from SockJS and which ones are al­lowed to send/pub­lish to SockJS.

Now you can start your ap­pli­ca­tion, don’t for­get to in­stall the de­pen­den­cies for the first time:

npm install

And then run the ap­pli­ca­tion:

npm start

If you now open 2 browser win­dows you will be able to draw nice pic­tures and see the draw­ing show­ing in “real time” on the other win­dow, if you then draw on the sec­ond you should get the mir­ror ef­fect on the first win­dow.

Have fun!

Next post

Writing secure Vert.x Web apps

This is a starting guide for securing Vert.x web applications. Standard rules and practices apply to Vert.x apps as if they would to any other web framework.

Read more
Previous post

Unit and Integration Tests

Let’s refresh our mind about what we developed so far in the introduction to vert.x series. We forgot an important task. We didn’t test the API.

Read more
Related posts

Unit and Integration Tests

Let’s refresh our mind about what we developed so far in the introduction to vert.x series. We forgot an important task. We didn’t test the API.

Read more

Combine vert.x and mongo to build a giant

This blog post is part of the introduction to Vert.x series. We are now going to replace this JDBC client by the vertx-mongo-client, and thus connect to a Mongo database.

Read more

Using the asynchronous SQL client

Finally, back... This post is the fifth post of the introduction to vert.x blog series, after a not-that-small break. In this post we are going to see how we can use JDBC in a vert.x application.

Read more