Push-based synchronized slideshow web application – implemented using WebSockets and Kaazing WebSocket Gateway

In the last few articles, I have discussed downloading, installing and running demos for a number of different tools, frameworks and libraries that support push-style (web) applications in one way or another. I have looked into ‘classic’ comet with Grizzly, Atmosphere and CometD as well as ADF Active Data Service and WebLogic Pub/Sub (Bayeux) Channels. I have also looked to WebSockets with jWebSocket, again Atmosphere and CometD and also with Kaazing. I intend to now take a closer look at some of these frameworks, by taking a simple push-style application and implementing it using each of these frameworks. The functionality I will be implementing is simple:

  • through the web client (HTML 5/javaScript), a user can select an image from a list of ‘slides’

    Image

  • the selection of the image is communicated to the server (background WebSocket based or alternatively regular AJAX)Comet (Bayeux)/Long Poll style)
  • the server informs all connected clients about the selected image through a pushed message (background WebSocket based or alternatively Comet (Bayeux)/Long Poll style); these clients all synchronize that slide selection
  • a Java (server side) component can also connect to the server and listen in to image selection events as well as publish image selection messages of its own
  • bonus: it may also broadcast ‘new image available’ events to allow the web clients to update the list of images presented to the user

In this article, I will present an implementation using the Kaazing WebSocket Gateway. The application parts that I created or configured are a static HTML page with some JavaScript elements, the Kaazing gateway-config.xml file and a single Java Class. Maybe 50 lines of code in total, to get a fully operaional push style WebSocket application. Not a bad start for this series. Note: I would like to thank Matthias Wessendorf from Kaazing for his help.

Configuring Kaazing WebSocket Gateway

Before we can get started with the implementation of the slideshow using Kaazing, we need to set up the Kaazing WebSocket Gateway. In a previous article (https://technology.amis.nl/blog/14755/get-going-with-kaazing-websocket-gateway-html5-edition-installing-locally-and-running-demos) I have described how to get Kaazing going.

The set up for the slideshow in Kaazing is the following:

Image

Two ‘channels’ are defined, one called listener and called sender. Multiple clients – both HTML 5/JavaScript and Java – can open WebSockets to the listener channel to receive ‘image selection events’ and to the sender channel to publish image selection events.

Image

The Kaazing Gateway is not configured specifically for a particular type of client. It is not aware of the content of the communication taking place. It is merely set up to receive messages on a listener ‘channel’ and route them to the server channel. The gateway will handle all clients opening WebSocket connections for either of these channels and will broadcast messages received on the sender channel to all clients connected to the listener channel.

The configuration of the Gateway in order to carry out this responsibility – route messages from sender to listener takes place in the gateway-config.xml file (located in the Kaazing_Home\conf directory):

 <service>
   <accept>ws://${gateway.hostname}:${gateway.extras.port}/listener</accept>
   <type>broadcast</type>
   <properties>
     <accept>ws://${gateway.hostname}:${gateway.extras.port}/sender</accept>
   </properties>
   <cross-site-constraint>
     <allow-origin>http://${gateway.hostname}:${gateway.extras.port}</allow-origin>
   </cross-site-constraint>
 </service>

With this configuration done and the Kaazing server has been (re)started, any WebSocket client can connect to WebSockets at http://host:port/listener and http://host:port/sender. These clients are not aware of Kaazing specific implementation details – they are generic WebSocket clients.

The HTML 5 Web Client

The hardest work of putting the slideshow application together goes into the JavaScript of the HTML 5 Web Page. Before going there, I have collected five images to show in the slideshow. I have copied them to the KAAZING_HOME\web\extras\demo\core\javascript\images directory (to get the application going with a minimum of configuration fuzz):

Image

I then copied the file ws.html in directory KAAZING_HOME\web\extras\demo\core\javascript. The clone is called slideshowDemo.html. From ws.html, I inherit a JavaScript library include as well as a CSS stylesheet. Some of the JavaScript required for making the connection and handling messages is also in the file and I make grateful use of these snippets.

The HTML for choosing and showing the slide is nothing special:

Image

It does not look special when rendered either:

Image

The main hook in the HTML is the setup() call in the BODY onload attribute. In the setup() function, two WebSocket connections are prepared (though not yet connected). One connects to the listener channel (the first websocket) and the other to the sender channel (the second websocket). The connect and disconnect buttons are provided with onclick handlers, to make them connect to or disconnect from a WebSocket.

        var slide ;
        function setup() {
            slide = document.getElementById("slide");
		    var locationURI = new URI(document.URL || location.href);
            var websocket;

        ...
            // construct the WebSocket location
            locationURI.scheme = locationURI.scheme.replace("http", "ws");
            // connect this WebSocket to the listener channel
            locationURI.path = "/listener";
            delete locationURI.query;
            delete locationURI.fragment;

            var location_ = document.getElementById("location_");
            var message = document.getElementById("message");
            var connect = document.getElementById("connect");
            var disconnect = document.getElementById("disconnect");

            location_.value = locationURI.toString();
            connect.disabled = false;
            disconnect.disabled = true;

            var log = function(message) {
                // currently not implemented
            };

            connect.onclick = function() {
                log("CONNECT: "+location_.value);
                try {
                    websocket = new WebSocket(location_.value);
                    websocket.onopen = function(evt) {
                        log("CONNECTED");
                        connect.disabled = true;
                        disconnect.disabled = false;
                    }

            ....

For the first WebSocket (connected to the listener channel), the onmessage callback handler is defined. This handler receives the event pushed on the listener WebSocket.

websocket.onmessage = function(evt) {
				  	  var myObject = eval('(' + evt.data + ')');
					  // only update the slide if the event was not the one this session sent itself
					  if (lastMessageSentUniqueId != myObject.uniqueId) {
                                             adjustSlide( myObject.slideNumber);
					  }
                                  }

It evaluates the JSON content, retrieves the uniqueId property to check whether this event was not sent by the client itself and when the message is not one it published itself, extracts the newly selected slideNumber property and uses it to set the currently selected slide using the adjustSlide() function.

	var defaultColor='black';
	var highlightColor = 'blue';
        var currentSlideNumber;

	function adjustSlide( slideNumber) {
	  markSlideLink (currentSlideNumber, defaultColor);
      currentSlideNumber = slideNumber;
	  slide.src = "images/nature"+slideNumber+".jpg";
	  markSlideLink (currentSlideNumber, highlightColor);
	}

    function markSlideLink (slideNumber, color) {
	  var slideLink = document.getElementById("slide"+slideNumber);
	  if (slideLink) {
	    var span = slideLink.childNodes[0];
	    span.style.color =color;
      }
	}

The second WebSocket – sender – is not intended for receiving messages, only for publishing them. It does not have an onmessage call back handler.

When one of the hyperlinks Slide One..Slide Five is clicked upon, the setSlide function is invoked with the number for the slide to be selected. This function will first of all locally adjust the selected slide using the adjustSlide function we have seen before. It will then prepare and publish an imageSelection event message to the second (sender) WebSocket. The message includes a uniqueIdentifier (simply the current time) that is also stored on the client in the variable lastMessageSentUniqueId. This identifier is used to check when a imageSelection event is received on the listener WebSocket: it does not make sense for a client to handle an event it has published itself.

The message is created as a JSON style text string with two properties: uniqueId and slideNumber.

	var lastMessageSentUniqueId;

	function setSlide( slideNumber) {
	  adjustSlide(slideNumber);
	  uniqueId  = uniqid();
	  lastMessageSentUniqueId = uniqueId;
	  var jsonMessage = "{ uniqueId : "+uniqueId+" , slideNumber: "+slideNumber+"}";
	  websocket2.send(jsonMessage);
	}

We are now ready to open the HTML application in a browser – or better yet: in at least two browsers.

Image

Connect both clients to both channels by clicking the four connect buttons (this can be done programmatically of course, without the need for the buttons and the user effort).

Click on the link for Slide Four:

Image

And the following happens – hard to capture in a screenshot: the Chrome client where the selection is made updates the slide immediately and some 100s miliseconds later, the Firefox client synchronizes.

Image

By debugging the JavaScript, it is easy to find out what happens when the link is clicked:

Image

The onmessage function specified for the WebSocket associated with the listener channel is invoked. The object passed to the function – the message received from the WebSocket interaction – can be evaluated into a JavaScript object because it is a string in JSON syntax. It contains a uniqueId and a slideNumber. The latter is used to adjust the slide.

The Java Client

The (server side) Java application that also connects to the WebSocket channels can be created in any IDE. It has one dependency: on com.kaazing.gateway.client.java.core.jar, a fairly small library that supports communication with WebSockets from Java. This jar is located in the Kaazing installation, in Kaazing_Home\lib\client\java. Note: this library is not Kaazing specific: it allows communication to any WebSocket server – as described by Matthias in this article: http://matthiaswessendorf.wordpress.com/2011/03/12/java-client-for-websocket/.

I will use NetBeans in this case. Open NetBeans, create new project and create a dependency on the JAR file:

Image

The code in the JavaClient is also quite straightforward. It is the equivalent in Java to the code we have seen before in JavaScript.

Two instances of the WebSocket class are created. One is connected to the sender channel on the Kaazing WebSocket server, the other to the listener channel. Two messages – image selection events – are sent to the sender WebSocket (to make all clients synchronize with the slidenumber that is indicated). The listener WebSocket has an associated onMessage callback method that processes the imageSelection message, regardless whether the message originated on one of the HTML 5 Web clients or in the Java client itself:

package websocketkaazingjavaclient;

import java.net.URI;
import com.kaazing.gateway.client.html5.WebSocket;
import com.kaazing.gateway.client.html5.WebSocketAdapter;
import com.kaazing.gateway.client.html5.WebSocketEvent;

public class WebSocketKaazingJavaClient {

    public static void main(String[] args) throws Exception {
        final WebSocket ws = new WebSocket();
        System.out.println("running");
        ws.addWebSocketListener(
                new WebSocketAdapter() {

                    @Override
                    public void onOpen(WebSocketEvent openEvent) {
                        System.out.println("Connection to Server is up!");
                        // we are able to talk to the WebSocket gateway
                        try {
                            ws.send("{uniqueId:918918, slideNumber:4}");
                        } catch (Exception e) {
                            e.printStackTrace();
                        }
                    }
                });

        final WebSocket ws2 = new WebSocket();
        System.out.println("running");

        ws2.addWebSocketListener(
                new WebSocketAdapter() {

                    @Override
                    public void onMessage(WebSocketEvent messageEvent) {
                        System.out.println("Received Event Data: " + messageEvent.getData());
                    }

                    @Override
                    public void onOpen(WebSocketEvent openEvent) {
                        System.out.println("Connection to Listener  is up!");
                    }
                });
        ws.connect(new URI("ws://localhost:8001/sender"));
        ws2.connect(new URI("ws://localhost:8001/listener"));
        Thread.sleep(2000);
        ws.send("{uniqueId:918918, slideNumber:2}");
        Thread.sleep(5000);
        System.out.println("done");
    }
}

Image

Resources

Download sources discussed in this article: slideshowDemoKaazing.zip.

8 Comments

  1. Yiannis February 1, 2012
  2. Rolando Santamaria Maso January 12, 2012
  3. Quim January 8, 2012
    • Lucas Jellema January 8, 2012
  4. Peter Moskovits January 8, 2012
    • Lucas Jellema January 8, 2012
  5. Peter Moskovits January 8, 2012
    • Lucas Jellema January 8, 2012