Wednesday, February 20, 2013

Server Push using Atmosphere (Glassfish + PrimeFaces)

Server Push is described as a mechanism in which the server initiates a transaction to a client (http://en.wikipedia.org/wiki/Push_technology). This mechanism has various interesting implementations which improve the options for interaction on a website such as an online chatbox (http://www.primefaces.org/showcase/push/chat.jsf) in which users are informed of new logins and messages.

There are various technologies which implement mechanisms to achieve the experience of server push such as;

- Native Comet (The WebServer has API for Comet)
- Native WebSockets (The WebServer has API for WebSocket)
- WebSockets
- Long-Polling
- Http Streaming
- JSONP
- Server-Sent Events

I will not go into detail for each of these options.

Atmosphere

Which technology is a wise choice to follow and implement? Every method has it's own drawbacks. I myself have considered implementing a custom Long-polling mechanism in the past. WebSockets is new and gaining popularity ('hip' in dutch ;), however a lot of browsers do not natively support it yet. See a list on; http://stackoverflow.com/questions/1253683/what-browsers-support-html5-websocket-api. When WebSockets is not supported you want the option to fall back on more primitive options. This would however require implementing different technologies and thus more work. Luckily there are frameworks which provide an abstraction to the technology based on what the server and client support. Atmosphere is one of those frameworks; https://github.com/Atmosphere/atmosphere. Atmosphere provides a wide array of support options for server and clientside of the server push mechanism; https://github.com/Atmosphere/atmosphere/wiki/Supported-WebServers-and-Browsers. Also when using Atmosphere, future technologies will most likely not require more rework then an Atmosphere upgrade.

Implementation

I wanted to create a website which provided chat support and some other options. I didn't want to have to click a refresh button to update my screen but I wanted events triggered by clients to lead to updates; I wanted a server push mechanism. I chose my development environment to be Netbeans, my server to be Glassfish and my JSF framework to be PrimeFaces (see http://javaoraclesoa.blogspot.nl/2013/02/a-simple-j2ee-webapplication-netbeans.html). Glassfish has good support options for server push mechanisms; all the mentioned technologies can be used with the latest version of Glassfish. PrimeFaces natively supports Atmosphere (https://github.com/Atmosphere/atmosphere/wiki/Atmosphere-PlugIns-and-Extensions).

Glassfish Comet support

At first I got the following errors in Glassfish;

SEVERE: AtmosphereFramework exception
java.lang.IllegalStateException: Make sure you have enabled Comet or make sure the Thread invoking that method is the same as the Servlet.service() Thread.
at com.sun.grizzly.comet.CometContext.addCometHandler(CometContext.java:295)
at com.sun.grizzly.comet.CometContext.addCometHandler(CometContext.java:314)
at org.atmosphere.container.GrizzlyCometSupport.suspend(GrizzlyCometSupport.java:139)
at org.atmosphere.container.GrizzlyCometSupport.service(GrizzlyCometSupport.java:121)
at org.atmosphere.container.GlassFishWebSocketSupport.service(GlassFishWebSocketSupport.java:70)

My first guess whas that I needed to enable Comet support. I looked it up and found the following;

When I activated it and restarted the server however, the setting was lost. I found the following bug; http://markmail.org/message/3fo3tyo72pew4jv4. The Comet support setting was not always persisted. I found some other options to try; using asadmin to add the option (http://blog.eisele.net/2012/09/primefaces-push-with-atmosphere-on.html) and manually editing the domain.xml. Both didn't fix my problem. My Glassfish server was a Netbeans embedded version. Then I found the following checkbox and the problem was fixed.


PrimeFaces implementation

All the code required was the following in the JSF file;

<p:socket onMessage="handleMessage" channel="/messages" />
<p:remoteCommand name="updateMessagesTable" actionListener="#{messageBean.loadMessages}" update="MessagesDataTable"/>
            
<script type="text/javascript">
                function handleMessage(data) {
                    updateMessagesTable();
                }
</script>

The following in the web.xml file;

    <servlet>
        <servlet-name>Push Servlet</servlet-name>
        <servlet-class>org.primefaces.push.PushServlet</servlet-class>
    </servlet>
    <servlet-mapping>
        <servlet-name>Push Servlet</servlet-name>
        <url-pattern>/primepush/*</url-pattern>
    </servlet-mapping>

And the following in the backing bean of the JSF page;

    private void doPushUpdate() {
        PushContext pushContext = PushContextFactory.getDefault().getPushContext();
        pushContext.push("/messages", "update");
    }

How does this work?

In the messageBean, when the doPushUpdate function is called, all clients receive asynchronous updates on the specified channel. This triggers them to update the messages table.

<p:socket onMessage="handleMessage" channel="/messages" /> in the JSF page responds to a message on the channel /messages. It calls the Javascript function handleMessage when an asynchronous server push event is received. The line; <p:remoteCommand name="updateMessagesTable" actionListener="#{messageBean.loadMessages}" update="MessagesDataTable"/> makes available a Javascript function called updateMessagesTable() which calls the messageBean function loadMessages after which it updates my MessagesDataTable. The Javascript function handleMessage calls the updateMessagesTable function and in effect the messageBean function. The handleMessage function is not required since I could have called the updateMessagesTable function directly, but I wanted a location at which I could do something in Javascript with the received message.

How does it look?

Keep in mind that this is my first play with server push and PrimeFaces. I was pretty satisfied with the result and the relative ease at which it was achieved. After every message all clients get updated. If you want to know what PrimeFaces is capable of, check out their impressive showcase (http://www.primefaces.org/showcase).


What more could you want?

I could have dynamically updated the table or other portions of the site instead of refreshing it completely. Also I have not implemented a lazy loading mechanism; http://www.primefaces.org/showcase/ui/datatableLazy.jsf

The potential usages of server push mechanisms are numerous. Server push allows communication with a server as mediator. For example it can be implemented to allow direct communication with website visitors or allows visitors to immediately interact with each other. It also allows external events (happening on the server) to influence what visitors get to see on your site on the fly.

The code can be downloaded here

No comments:

Post a Comment