Using Atmosphere in JBoss AS 7 to push data to your HTML web page

Atmosphere is an asynchronous WebSocket/Comet framework for J2EE based Application Servers. In short, Atmosphere allows to push data to the web browser on near-real time fashion. For example, you could use such tool do develop a web based chat application or a stock market application that receives the new quotes as soon as they become available.

Atmosphere

Unless WebSockets are used (a different protocol than HTTP), Atmosphere will work by having the server side intercepting the web call and keep the connection open. Like this, the client will be the one responsible for shutting the connection down and, by leaving this connection open, the server can now push data through that connection.

In addition, Atmosphere framework also provides:
1. A JQuery Plugin that, if used, highly simplifies the development of the client side.
2. A replacement for JAX-RS – not used in example because JBoss provides RESTEasy (note that RESTEasy can be substituted in favor of Atmosphere REST services if desired)

During my little experiments I’ve noticed that currently Atmosphere only allows the client to maintain one connection per page loaded.

Here is a presentation on Atmosphere:

The following example was successfully executed and tested JBoss AS 7.1 and you can download the example from github here.

The example works by having a web page loaded from the server. Once loaded the JQuery plugin will connect to the Atmosphere Servlet using a HTTP GET request that will override the default connection handler and keep the connection alive. To broadcast/push a message to a subscribed browser web connection simply do an HTTP POST to the desired subscription.

package crazypenguin.servlets;

import java.io.IOException;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.atmosphere.cpr.ApplicationConfig;
import org.atmosphere.cpr.AtmosphereResource;
import org.atmosphere.cpr.Broadcaster;
import org.atmosphere.cpr.BroadcasterFactory;
import org.atmosphere.cpr.HeaderConfig;
import org.atmosphere.handler.AbstractReflectorAtmosphereHandler;
import org.atmosphere.websocket.WebSocketEventListenerAdapter;

public class PubSubAtmosphereHandler extends AbstractReflectorAtmosphereHandler {

	@Override
	public void onRequest(AtmosphereResource<HttpServletRequest, HttpServletResponse> event) throws IOException {
	
	  HttpServletRequest req = event.getRequest();
	  HttpServletResponse res = event.getResponse();
	  String method = req.getMethod();
	
	  // Suspend the response.
	  if ("GET".equalsIgnoreCase(method)) {
		  
		  // Log all events on the console, including WebSocket events.
          event.addEventListener(new WebSocketEventListenerAdapter());
          
	      res.setContentType("text/html;charset=ISO-8859-1");
	      
	      Broadcaster b = lookupBroadcaster(req.getPathInfo());
	      event.setBroadcaster(b);
	
	      String atmoTransport = req.getHeader(HeaderConfig.X_ATMOSPHERE_TRANSPORT);
	      
	      if (atmoTransport != null && !atmoTransport.isEmpty() && 
	    		  atmoTransport.equalsIgnoreCase(HeaderConfig.LONG_POLLING_TRANSPORT)) {
	    	  
	          req.setAttribute(ApplicationConfig.RESUME_ON_BROADCAST, Boolean.TRUE);
	          event.suspend(-1, false);
	          
	      } else {
	          event.suspend(-1);
	      }
	  } else if ("POST".equalsIgnoreCase(method)) {
	      Broadcaster b = lookupBroadcaster(req.getPathInfo());
	
	      String message = req.getReader().readLine();
	
	      if (message != null && message.indexOf("message") != -1) {
	          b.broadcast(message.substring("message=".length()));
	      }
	  }
	}
	
	@Override
	public void destroy() {
		// empty
	}
	
	Broadcaster lookupBroadcaster(String pathInfo) {
		String[] decodedPath = pathInfo.split("/");
		Broadcaster b = BroadcasterFactory.getDefault().lookup(decodedPath[decodedPath.length - 1], true);
		return b;
	}

}

RESTEasy example call when called from the web page button will, within the server side broadcast a message to all subscribed connections.

package crazypenguin.rest;

import org.atmosphere.cpr.Broadcaster;
import org.atmosphere.cpr.BroadcasterFactory;

import javax.ejb.LocalBean;
import javax.ejb.Stateless;
import javax.ws.rs.POST;
import javax.ws.rs.Path;
import javax.ws.rs.core.Response;

@Path("/control")
@Stateless
@LocalBean //Note: @javax.ejb.Local(WindowingResource.class) does not work on AS 7!
public class AtmosphereTestBean implements AtmosphereTest {
	
	private boolean m_visible = false;
	
	@POST
	@Path("/toggle")
	@Override
	public Response toggle(String xml) {
		m_visible = !m_visible;
		notifySubscribers(m_visible ? "ON" : "OFF");
		return Response.ok().build();
	}
	
	private void notifySubscribers(String state) {
		Broadcaster broadcaster = BroadcasterFactory.getDefault().lookup("control", true);
		if (broadcaster != null) broadcaster.broadcast(state);
	}
}
<!DOCTYPE html>
<html><head><meta http-equiv="Content-Type" content="text/html; charset=ISO-8859-1">
	
	<meta charset="UTF-8">
	<title>Atmosphere Test</title>

</head>

<body>
	<input type="button" value="POST to server..." onclick="post()"/>

	<div class="controlPanel">
		<div id="cp-1"  class="cpitem"><img src="img/remoting.png"/></div>
	</div>	

<script src="js/jquery-1.6.4.min.js" type="text/javascript"></script>
<script src="js/jquery.atmosphere.js" type="text/javascript"></script>
<script src="js/jquery.url.js" type="text/javascript"></script>
<script src="js/atmo.js" type="text/javascript"></script>
  
</body>
</html>

Simple JavaScript object responsible for subscribing to server broadcasts and calling the registered message handler methods.

var cm;
var cm_actions = {
	'ON':['showItem', '#cp-1'], 
	'OFF':['hideItem', '#cp-1'],
};

$(document).ready(function(){
	$('#cp-1').hide();
	
	cm = new ControlManager();
 });

function controlConnect() {
	controlUnsubscribe();
    controlSubscribe();
}

// jquery.atmosphere.response
function callback(response) {
	// Websocket events.
	$.atmosphere.log('info', ["response.state: " + response.state]);
	$.atmosphere.log('info', ["response.transport: " + response.transport]);
	$.atmosphere.log('info', ["response.status: " + response.status]);

	if (response.transport != 'polling' && response.state == 'messageReceived') {
		$.atmosphere.log('info', ["response.responseBody: " + response.responseBody]);
		if (response.status == 200) {
			var data = response.responseBody;
			if (data.length > 0) {
				for (var cmd in cm.actions) {
					if (data === cmd) {
						var m = window[cm.actions[cmd][0]];
						var ma;
						if (cm.actions[cmd].length > 1) ma = cm.actions[cmd][1];
						if (ma && ma!= '') m(ma);
						else m();
					}
				}
			}
		}
	}
}

function controlSubscribe() {
	if (!this.callbackAdded) {
		var url = $.url();
		var location = url.attr('protocol') + '://' + url.attr('host') + ':' + url.attr('port') + '/atmo/pubsub/control';
		this.connectedEndpoint = $.atmosphere.subscribe(location,
			!callbackAdded ? this.callback : null,
			$.atmosphere.request = { 
				transport: 'websocket' 
			}
		);
		
		this.callbackAdded = true;
	}	
}

function controlUnsubscribe(){
	this.callbackAdded = false;
	$.atmosphere.unsubscribe();
}

function ControlManager() {
	this.suscribe = controlSubscribe;
	this.unsubscribe = controlUnsubscribe;
	this.connect = controlConnect;
	this.callback = callback;
	this.connect();
	this.actions = cm_actions;
}

function showItem(id) {
	$(id).show();
}

function hideItem(id) {
	$(id).hide();
}

function post() {
	
	var request = $.ajax({
		url: 'rest/control/toggle',
		type: 'POST',
		data: '<doc>foo</doc>',
		contentType:"application/xml; charset=utf-8",
	});
				
	request.fail(function(jqXHR, textStatus) {
  		alert( "Request failed: " + textStatus );
	});
}

Atmosphere is a great library and provides many more features. Make sure you explore it deeply since it can really make your Web 2.0 applications much more interesting and responsive.

Advertisements

About CrazyPenguin

Software Engineer
This entry was posted in Uncategorized. Bookmark the permalink.

17 Responses to Using Atmosphere in JBoss AS 7 to push data to your HTML web page

  1. Hendy Irawan says:

    Thank you ! Been looking for this for my https://github.com/soluvas/primefaces-bootstrap project 🙂

  2. mel says:

    Hi. I am getting an error when I open the index.html page in the firefox debugger console.
    Firefox is not able to connect to websocket ws://lo….

    version of firefox : 14
    Application server : JBoss As 7

    and probably because of this , when I click on the POST to Server button the application fails at the line

    private void notifySubscribers(String state) {
    // fails at BroadcasterFactory.getDefault()
    Broadcaster broadcaster = BroadcasterFactory.getDefault().lookup(“control”, true);
    if (broadcaster != null) broadcaster.broadcast(state);
    }

    would appreciate if there is any help around.

    • CrazyPenguin says:

      JBoss AS 7 uses JBossWeb for the servlet container and, as far as I know, it does not provide WebSockets as of yet.

      That’s why Atmosphere is a great solution because in the day it does, your app will be ready and automatically detect it.

      Atmosphere detects if it can make use of web sockets and will automatically downgrade to a supported messaging format (e.g. long-polling).

      • mel says:

        Thanks. But Why do I get a Null pointer exception in the following line when I click Post to server button

        private void notifySubscribers(String state) {
        // NullPointerException fails at BroadcasterFactory.getDefault()
        Broadcaster broadcaster = BroadcasterFactory.getDefault().lookup(“control”, true);
        if (broadcaster != null) broadcaster.broadcast(state);
        }

      • CrazyPenguin says:

        mel,

        Most probably because you’re missing some dependencies and the Broadcaster fails to load and return the default instance.

        Post some of the exception stack trace from your server log. Also, make sure you have the necessary libs available in your project.

        I just tested the provided sample EAR and everything is working just fine with JBoss 7.1.1 and Firefox 14.

      • Mel says:

        Thanks again for your valuable time.
        would you be in a position to attach your project, please…
        would be really helpful.
        Thanks

      • CrazyPenguin says:

        Mel,

        You can checkout the maven project from github at:

        https://github.com/crazyfrozenpenguin/cpenguin_repository/tree/master/jboss7

        Let me know if it works.

      • Melvin Pereira says:

        ello,

        I did the steps exactly as you specified and deployed it on JBoss Brontes

        and when I click on the Post to Server button, The browser shows a alert message Request failed and below id the stacktrace on the console

        Hello, I deployed the version from github and I was able to have a clean deployment.

        Now when I click on the Post to Server using firefox there is nothing new on the screen but the firefox web console displaying

        [14:03:50.941] POST http://localhost:8080/atmo-web/rest/control/toggle [HTTP/1.1 200 OK 0ms]

        no element found

        what could be the issue ?

        regards Mel

        Date: Mon, 20 Aug 2012 06:21:03 +0000 To: pereiramelvin@hotmail.com

      • CrazyPenguin says:

        Mel,

        Forget the steps in the README.rd file. That was auto generated by the JBoss archtype and I really didn’t read through.

        Basically, all you have to do is to get the atmo directory to your local disk and in the command line use maven 3 to build it:

        mvn clean install

        You should get a atmo.ear file under atmo/atmo-ear/target directory.

        Copy that file into your JBOSS_HOME/standalone/deployments

        In this same directory create an empty text file names:

        atmo.ear.dodeploy

        Start jboss in standalone mode: JBOSS_HOME/bin/standalone.sh

        Everything should start just fine but keep an eye in your console.

        Point your browser to:

        http://localhost:8080/atmo-web/index.html

        and it should work.

        I’ve just tried this process locally and it build just fine and worked without any issues in Chrome and Firefox (latest versions).

      • Melvin Pereira says:

        Hi,

        Thanks again for your help and patience.

        I did a clean build and I did deploy it perfectly on JBoss Brontes

        Now when I click on the Post to Server message, there are no stack trace logs, but

        nothing happens on the browser screen.

        and I get a message on the firefox web console :

        no element found

        I am still not sure abt the reason.

        What should it show on the browser screen when you click on Post to server.

        again thanks for your help and patience.

        I really need this example working.

        also for your reference I would like you to point you to something thats happening on JBoss 7.1.2

        https://github.com/mikebrock/jboss-websockets

        Date: Mon, 20 Aug 2012 13:18:16 +0000 To: pereiramelvin@hotmail.com

      • Melvin Pereira says:

        yep. I got it. infact it was running successfully since morning but for one reason.

        I have gzip compression on in JBoss Brontes.

        If I switch on compression the app is not able to display the toggle image.

        But if I switch the compression off I am able to toggle the image.

        Infact I have created a lot of rest services utilizing jquery, JAX-RS and compression on.

        Why this one should fail is a mystery.

        Thanks a lot for so much of efforts on your part.

        warm regards Mel

        Date: Mon, 20 Aug 2012 13:18:16 +0000 To: pereiramelvin@hotmail.com

      • CrazyPenguin says:

        Theoretically, it should work even with compression on. But then again I certainly did not try it.

        Either way, I glad that you got it working. Hope it’s of good use to you.

      • Melvin Pereira says:

        Yep. Thanks a lot.

        By the way , were you able to run any of the samples from theatmosphere distro on JBoss Brontes.

        I tried a few, but was not able to mostly because of the dependence on grizzly.

        thanks again

        warm regards Mel

        Date: Mon, 20 Aug 2012 16:23:32 +0000 To: pereiramelvin@hotmail.com

      • CrazyPenguin says:

        Melvin,

        I have not tried it yet. Once I get some free time I’ll give it a go.

      • CrazyPenguin says:

        In addition, please notice that there were some minor changes to the code due to an upgrade to the latest stable Atmosphere version: 0.9.7

      • Melvin Pereira says:

        Thanks a lot again for your patience and valuable time.

        I ‘ll check this out and notify you of the results.

        Have a nice time.

        warm regards Mel

        Date: Mon, 20 Aug 2012 06:24:46 +0000 To: pereiramelvin@hotmail.com

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