Comet/WebSocket? Introducing the Atmosphere framework
Pushing messages to connected clients has always been a need on the web, growing with the apparition of new Rich Internet Applications, like realtime feeds (Gmail, news, market quotes), social feeds (The Twitter, Facebook and consort) and many other providing realtime collaboration, monitoring and control like new Internet of Things applications (FlightRadar24, Arduino #1, #2).
Comet technics (ie. polling, long-polling, streaming) and more recently, the WebSocket protocol, have made possible various webpush applications.
Today, when you want to enable realtime push on your own Java-based webapp, you have several solutions:
- Use an externalized system that will handle that for you (like Pusher), avoiding you the providing of any additional server
- Use a commercial solution (like Kaazing or Diffusion), generally implementing a Message Broker underneath, with both embedded and standalone server
- Use a container-specific api (like Jetty Continuation, Tomcat’s CometProcessor, Grizzly CometHandler or Netty WebSocket), but your application will be strongly coupled with it
- Or use one of the few Open Source frameworks available (like Atmosphere, cometD or JWebSocket)
In this article, I will focus on Atmosphere. After a brief presentation of the framework, I will demonstrate how easy it is to make push-capable applications, whatever the container you use and the nature of your clients.
Before we start, you can take a look at the following video, that demonstrates what I achieved thanks to the Atmosphere framework (more details below, of course):
What is Atmosphere ?
Atmosphere is a Java OpenSource framework led by Jean-Francois Arcand. Started around 2009, it evolved significantly and powers today websites like smartmoney.com. The project is very active (daily commits, lively mailing list, 300 Github followers). Currently in version 0.9, Atmosphere is close to the major 1.0 release planned for May.
One of the strength of the framework is that it is container agnostic: you can deploy an Atmosphere application in Jetty, Tomcat, JBoss, Grizzly,… Indeed, Atmosphere will seamlessly use specific compatibility modules and enable WebSocket depending on the detected container. The user does not need to learn container-specific Comet or WebSocket implementations anymore, only the way Atmosphere runs.
Atmosphere can run in two modes: embedded or standalone. The first one is the classic approach and the one you generally choose when you start from scratch. The second one, called Nettosphere, relies on the combination of the Netty container and the Atmosphere framework, allowing you to extend your existing Java webapp with realtime push capabilities in a non-intrusive way. Moreover, Nettosphere fits well when you need to do integration test on your realtime resources as you can instantiate it programmatically on demand, similarly to the Jersey-test-framework.
Usually, when you want to achieve standard comet (long-polling, streaming) communication, you don’t have to worry about the underneath container: they are compatible with Atmosphere. About WebSocket nonetheless, the compatibility will depend on the version of the WebSocket specification implemented by the container running Atmosphere. To this day, Jetty, Grizzly, Netty and even Tomcat are some WebSocket-capable containers.
A variety of modules and plugins are bundled with the framework, letting the user benefit from Atmosphere in various way: you are able to write Atmosphere applications in Java, Scala, JRuby and Groovy, and use other frameworks such as Jersey, GWT, Spring or JSF.
Another interesting thing: Atmosphere was built to scale thanks to clustering plugins. These plugins actually wrap a clustering layer implementation among JMS, Redis, XMPP or Hazelcast to name a few… So if one server is not enough, you have tools to scale-out as needed.
Among all Atmosphere’s components, we find the jQuery plugin: this Javascript API connects your web pages to your server and offers methods to exchange realtime data. It can detect client’s capabilities and switch between protocols as necessary via a fallback mechanism. Therefore, the same user experience can be offered from IE6 to Chrome18.
From now on, we will focus on the Jersey’s Atmosphere extension, very convenient as it largely reduces lines to code thanks to JAX-RS implementation (annotations, json mapping, etc…).
Grab your keyboard, launch your Eclipse!
To demonstrate the use of Atmosphere, I chose to build an application that draws realtime events on a map: user can generate events by clicking on the map, and server can generate random events. A generated event (by either client or server) is delivered to each connected clients.
Thus, this application – called MapPush (I was not able to find anything more explicit!) – is composed of:
- The client, a simple HTML page that uses Javascript/jQuery to process logic
- The server, a JEE webapp backed by Atmosphere and Jersey frameworks
Note: the application is hosted on Github at the address http://github.com/ncolomer/MapPush. All the snippets below are extracted from this project. Feel free to browse the source code or clone the project!
Project bootstrap
In Eclipse, create a Maven project with a “webapp” archetype. As Atmosphere is available on Sonatype repositories (snapshots, releases), you can simply add the following to your pom.xml
:
To start the framework we need a servlet, and not any… an AtmosphereServlet! In case the Atmosphere’s Jersey module is detected at load time, the framework will wrap around the Jersey Servlet (ContainerServlet) to load our resources and extend it with Atmosphere capabilities (IoC, annotations, etc…). Therefore, Jersey’s init-params are still available.
Open the web.xml
file of your project and paste the following snippet:
We are now ready to code both client and server application.
The server
If you have already used the official JAX-RS implementation, Jersey, you won’t be disapointed. For others, you’ll find out that programming Atmosphere push APIs is really… dead simple!
Among the essential pieces of Atmosphere, one is called Broadcaster
. This is the object that manages connected clients and delivers broadcasted messages to them. A connected client is seen as an AtmosphereResource
by the framework.
In the push communication lifecycle, we can define three steps:
- the client sends a request, that is suspended (in case of comet) or upgraded (in case of websocket) by the server.
- then, server and client can exchange data : server generally broadcasts/pushes data to clients, and in case of a duplex protocol like WebSocket, client can send data back to it.
- finally, either one closes the connection.
Atmosphere offers two convenient ways to handle this lifecycle: you can choose between annotation or programmatic API (or even a combination of both), the last one allowing more customization from my point of view.
- To suspend a request, use the
@Suspend
annotation, or simply return aSuspendResponse
- To broadcast data, use the
@Broadcast
annotation and simply return aBroadcastable
. For a more specific use of broadcast mechanism, you are able to manually deal with theBroadcaster
and itsbroadcast(…)
methods to push messages to all, a subset or a particularAtmosphereResource
.
Here is an exemple of Atmosphere resource using the main concepts we have described above:
Listen for client messages
In case of a duplex protocol, we said a client can send data back to the server using the connection. But how do we handle such messages? Atmosphere provides a listener mechanism, implementable via interfaces such as the AtmosphereResourceEventListener
or its WebSocket specialization, the WebSocketEventListener
. The WebSocket interface exposes useful methods like onConnect
, onHandshake
, onDisconnect
, or onMessage
. Listeners are declared when the client connects (ie. when suspending a response).
Filter your broadcasted messages
You may also have observed the init()
method… but what is done inside exactly ?
A possibility offered by the framework is the ability to include logic before delivering message to each connected client. We can achieve that thanks to the BroadcastFilter
interface and its specialization, the PerRequestBroadcastFilter
interface. The first interface allows to execute logic once (common to all clients) whereas the second one can apply to each client according to their associated context (session data, first connection headers, etc…).
Each BroadcastFilter can CONTINUE
or ABORT
a broadcast. While the broadcast is not aborted, the filter chain is executed in order (BroadcastFilters
first, PerRequestBroadcastFilters
then) and finally delivered (or not) to each client. The following snippet is an example of PerRequestBroadcastFilter implementation:
The client
Now that all is ready server side, we are able to connect our clients to the realtime endpoint. If you plan to use WebSocket, several clients are compatible with Atmosphere. Let’s focus on Java and Javascript ones:
- In java, you find several projects like Java-WebSocket or async-http-client. Some webapp containers also provide a WebSocket client implementation (e.g. Jetty)
- In Javascript, most of recent browsers implement the WebSocket interface. But, in case the browser is not compatible with WebSocket, this API can’t fallback into another protocol. Here comes the jQuery Atmosphere Plugin and its fallback mechanism.
In our case, you probably guessed, we’ll use the Atmosphere jQuery plugin. Please note that due to the jQuery dependency, we have to import the jQuery library in addition to the Atmosphere plugin.
Once done, connect to the server is no more complicated than the following connect()
javascript routine:
You can see that we attach headers when connecting. They are used here to transmit some client context when connecting (actually, the current bounds of the map). As the WebSocket handshake doesn’t allow the client to pass any headers, they have to be serialized in the query string, justifying the attachHeadersAsQueryString: true
entry. On the server side, the query string will be translated to headers so application wise, you don’t have to care about the difference.
To process message pushed from the server, we can add a callback. It will be triggered on each received message, passing a response (Javascript object) containing values as status
, state
, transport
etc…
The endpoint
variable – that stores the connection – was made global to be used when you want to disconnect the client from the realtime endpoint or push data to the server. The following snippet shows you the two corresponding javascript routines.
Testing your WebSocket resources
In addition to regular browser testing, you can easily try your realtime URIs with a shell and cURL:
-v/--verbose
: Make the operation more talkative-N/--no-buffer
: Disable buffering of the output stream-X/--request <command>
: Specify request command to use
With the second option, you will be able to observe all data sent by the server. Nonetheless, note that you’ll not be able to send data back to it.
cURL also allows you to send headers with the request:
-H/--header <line>
: Custom header to pass to server
You can also go deeper and analyse WebSocket frames with tools such as ngrep or Wireshark: these are a bit more complex tools but it may become very useful in some situations!
Conclusion
Atmophere provides a powerful ecosystem that simplifies the creation of push applications and makes easy full-duplex communication between a server and any kind of client. Its intensive use of async I/O and its ready-to-use clustering plugins give it both performance and scalability. Moreover, the project is under Open source Apache license, the community is growing quickly and the last 0.9 version is stabilizing fast.
In short, the Atmosphere framework has a bright future :)
Additional Resources
- Atmosphere project is hosted on GitHub:
- Download the Atmosphere’s whitepaper
- Take a look at the samples
- Browse the Javadoc
- The community is reachable via Atmosphere’s Google Group
- You can also follow Atmosphere via Jean-Francois Arcand’s blog and Twitter