domingo, 23 de junho de 2013

Using WebSockets to implement Dashboards



Dashboards are extremely important tools mostly used for monitoring systems and environments. The main idea of these tools is to keep clients up-to-date with arriving or changing data on the server side. For that, the most popular technique used in web applications is based on ajax polling. With ajax, the client polls the server for data every time the content of the page requires an update.


Once the idea of monitoring is to show actual data information, there are several problems with current ajax technique. The first one is scalability. The number of requests made to the server can be extremely high if the frequency of polling is set to a small value. Not only the server but also the network can become saturated with all those requests. On the other hand, if we set a high value for the pooling frequency, some information may be lost or delayed. Another problem is that the response may not contain any data. So, in these cases, ajax polling overloads the server for nothing.


Fortunately, in December 2011, a new protocol called WebSocket was defined. As the spec says: “The goal of this technology is to provide a mechanism for browser-based applications that need two-way communication with servers that does not rely on opening multiple HTTP connections”. In other words, with Websocket protocol it is possible to send message frames from server to clients, once these clients have established an initial conversation, of course.


Analyzing such a capability, it becomes clear that implementations of browser-based dashboards, chats, games, kanbans, planning pokers tools, etc should be rewritten to use WebSocket protocol. Therefore, the main idea of this post is to show how to implement a very simple dashboard using websockets.


First of all, I would refer my article about how to create and embedded war with Jetty and Gradle. To make this post short and simple, I will assume you have already read that post or that you have a good knowledge about these subjects.


Second, to start our example we will be adding support for websockets in an embedded Jetty. So, you have to create your build file called ‘build.gradle’ as shown below. Note the lines in red, which are the responsible to include the expected support.


// Tells gradle it is a war project which will be imported into eclipse wtp
apply plugin: 'war'
apply plugin: 'eclipse-wtp'

// Define souce code compatibility
sourceCompatibility = 1.7
targetCompatibility = 1.7

// Use maven repository
repositories {
       mavenCentral()
}

// Fills out all dependencies which are necessary to start the embedded jetty into our war file
configurations {
       embeddedJetty
}
dependencies {
       embeddedJetty 'org.eclipse.jetty:jetty-servlet:+'
       embeddedJetty 'org.eclipse.jetty:jetty-webapp:+'
       embeddedJetty 'org.eclipse.jetty:jetty-jsp:+'
       embeddedJetty 'org.eclipse.jetty:jetty-annotations:+'

       // add support for jetty implementation of websockets
       embeddedJetty 'org.eclipse.jetty.websocket:websocket-server:+'

       compile 'org.glassfish:javax.json:+'
}

war.baseName = 'WebSockets'
war {
       // unzip and add all jetty dependencies into the root of our war file
       from {configurations.embeddedJetty.collect {
                       project.zipTree(it)
               }
         }
        // remove signature and unnecessary files
        exclude "META-INF/*.SF", "META-INF/*.RSA", "about.html", "about_files/**", "readme.txt", "plugin.properties", "jetty-dir.css"
        // include only the classes which will be used to start Embedded Jetty
        from "$buildDir/classes/main"
        exclude "com/myapp/"
        // tells the class to run when the generate war be executed using 'java -jar'
        manifest { attributes 'Main-Class': 'com.embedded.JettyStarter' }
}

// Once you will need some basic api (e.i. servlet api) for compilation, add embeddedJetty dependencies for compilation
sourceSets.main.compileClasspath += configurations.embeddedJetty

// the same for eclipse classpath, so you can use it to edit your java files
eclipse {
       classpath {
               plusConfigurations += configurations.embeddedJetty
       }
}


Unfortunately, Jetty 9 still does not support JSR 356 (JavaTM API for WebSocket). So we are going to use its current API in this example (which, by the way, is very similar to JSR 356). For that, initially we have to create a class for implementing our WebSocket as shown below.


import org.eclipse.jetty.websocket.api.Session;
import org.eclipse.jetty.websocket.api.annotations.OnWebSocketClose;
import org.eclipse.jetty.websocket.api.annotations.OnWebSocketConnect;
import org.eclipse.jetty.websocket.api.annotations.WebSocket;
 
@WebSocket  
public class DashboardWebSocket  {
       @OnWebSocketClose
       public void onClose(final Session session, int x, String text) {
                 EventGenerator.unregistry(new EventObserver(session));
        }
       @OnWebSocketConnect
        public void onOpen(final Session session) {
                 EventGenerator.registry(new EventObserver(session));
        }  
}  


The most important parts of the above class are the onOpen(..) and onClose(..) methods, which will be executed at any WebSocket open and close connections. In our example, each time a user decides to navigate to this app we will be registering his session as an event observer. Doing that, it is possible to broadcast to all registered sessions any event which has happened in the server side. In the same way, whenever that user closes the browser, the connection is lost and his session will be unregistered from the event observer list.


Besides the WebSocket itself, using Jetty API, it is necessary to implement a WebSocketServlet to map and registry such WebSocket. In our example, each time an URI /dashboard is used; it will be redirected for this servlet.


import org.eclipse.jetty.websocket.servlet.WebSocketServlet;
import org.eclipse.jetty.websocket.servlet.WebSocketServletFactory;


@SuppressWarnings("serial")
@WebServlet(name = "Dashboard WebSocket Servlet", urlPatterns = { "/dashboard" })
public class DashboardServet  extends WebSocketServlet {
        @Override
        public void configure(WebSocketServletFactory factory) {
                 factory.register(DashboardWebSocket.class);
        }
}


Once these steps are done, it is necessary to use the JavaScript API to open the initial connection and make it wait for new message frames from the server. Note, in the below JS code, that we have used the protocol ‘ws’ instead of ‘http’. It is necessary because WebSocket is, actually, a different protocol.


<script>
var load = function() {
        var socket = new WebSocket("ws://localhost:8081/dashboard");
        socket.onmessage = function(event) {
                 // show the event.data in your graph, table or whatever
        }
}
</script>


In the above code, we presented a very simple implementation in which a WebSocket is opened and, on each server event, data is received as a parameter by the function onmessage(..). In below image it is possible to see a printscreen of the this app running. The points represent the event amount (for an example purpose we have used a random number from 0 to 100.000) and moment it have happened.



In short I would say WebSocket is pretty important technology for building modern web applications in which, in the near future, it will be largely used for enterprise software as well for general web applications. However, it is still complicate to assume websockets as an unique strategy to replace long polling features because there exists firewalls and proxies which does not understand/support ‘ws’ protocol yet. Besides that, in the Java world, there are still few application servers which provide support for JSR 356 standard. So, we still have to wait some more months to use the API specified in that standard (p.s.: Glassfish already provides such implementation).


If you would like to study and/or execute this example, you can download the full source code here. To start the dashbord, you have to build the war and then execute it with the following command line: ‘java -jar WebSockets.war’. This will start a Jetty server in port 8081, so you can access the http://localhost:8081/ url in the browser and see by yourself.


Moreover, if you would like to understand how websocket frames are sent to the browser, I would suggest you to open the ‘Chrome Developer tools’ and click in ‘Network’ tab. In there, you will be able to see that this example app will be making no ‘http’ requests. But only one ‘ws’ connection. Clicking in the ‘ws’ connection you will be able to see the message frames being sent back from the server as shown in the image below.



Thank you for reading!

4 comentários:

  1. Thanks for this really useful post. I need to try this in our current project. If works, it is a great booster for scalability.

    ResponderExcluir
    Respostas
    1. It works, for sure.. ;-).
      We have used websockets to build our dashboard.
      Try to download the full source code and execute it.
      Thanks for reading..

      Excluir
  2. Hi,
    I think it's very useful for someone having no practice with gradle etc... if we can download the war "WebSocket.war".

    Thanks

    ResponderExcluir
    Respostas
    1. Hi Michard,
      Try to download the war from this link https://docs.google.com/file/d/0B70OhasM5Q2rOVMwQWNVR3BPMnM/edit?usp=sharing
      You can execute it using 'java -jar WebSockets.war'
      Thanks for the interest

      Excluir