Recently, we decided to implement asynchronous event broadcasting between people logged in to our new application Agido based on fantastic AngularJS framework.
We want to make Agido truly interactive application, and allow people to work together concurrently. So, user's events must be propagated to every other interested team member which is working on the same project.
We have 2 choices:
- migrate to new JBoss Wildfly 8.0, and use new pure Java EE 7 API for WebSocket (JSR-356)
- give chance to Atmosphere framework on good, old JBoss AS 7.1.1
Our first shot was Wildfly. We implemented early version using JSR-356, but Wildfly was in early Alpha stage, and Undertow bugs were hard to overcome at this time, so we decided to use Atmosphere!
So, what we do want to archive?
- Asynchronous transport between client (AngularJS) <-> server (JBoss 7.1.1 with standard JEE6 API)
- On server side, events should be propagated trough JMS.
- Handling different types of transport protocol, since old browsers don't support cutting-edge technologies, main choices were: WebSockets, HTML5 Server-Sent Events, and for legacy browsers - long-polling (which is interesting concept by the way, http://en.wikipedia.org/wiki/Push_technology#Long_polling)/
Let's start with the server-side.
First step is of course maven:
Two artifacts should be added to our pom.xml, first is core atmosphere module, second one is jms extension.org.atmosphere atmosphere-runtime-native 2.0.3 compile org.atmosphere atmosphere-jms 2.0.0 compile
Configure JBoss AS
Set native="true" flag in standalone.xml file (or domain.xml if you are using domain setup), in subsection "jboss:domain:web:1.1", example entry for developer machine below:I assume, that JMS is properly configured.
Create Handlers
@ManagedService(path = "/push/events/{projectId}", broadcaster = JMSBroadcaster.class, broadcasterCache = UUIDBroadcasterCache.class) @SuppressWarnings("UnusedDeclaration") public class AtmosphereHandler { private final Logger logger = Logger.getLogger(AtmosphereHandler.class); @Disconnect public void onDisconnect(AtmosphereResourceEvent event) { if (event.isCancelled()) { logger.infov("Browser {0} unexpectedly disconnected.", event.getResource().uuid()); } else if (event.isClosedByClient()) { logger.infov("Browser {0} closed the connection by client action.", event.getResource().uuid()); } } @Message(encoders = {EventEncoder.class}, decoders = {EventDecoder.class}) public Event onMessage(AtmosphereResource atmosphereResource, Event event) { if (event != null) { logger.infov("Received event from client: {0}", event.toString()); return event; } else { return new Event(); } } @Ready public void onReady(final AtmosphereResource resource) { logger.infov("Browser {0} connected.", resource.uuid()); } @Resume public void onResume(final AtmosphereResource resource) { logger.infov("Browser {0} resumed.", resource.uuid()); } }Annotation @ManagedService tell Atmosphere that, it should use this class for handling all push events for paths like "/push/events/*"
Parameter broadcaster specify which broadcaster implementation will be used for handling events. org.atmosphere.plugin.jms.JMSBroadcaster is the default simple implementation for JMS supported message distribution (from atmosphere-jms artifact). Of course you can write your own implementation. You must remember to annotate it with @BroadcasterService, so Atmosphere will detect it automatically, and should extends AbstractBroadcasterProxy
EventEncoder and EventDecored are simple classes implementing org.atmosphere.config.managed.Decoder and Encoder interfaces.
Add atmosphere servlet configuration in web.xml file
AtmosphereServlet AtmosphereServlet org.atmosphere.cpr.AtmosphereServlet org.atmosphere.cpr.broadcasterCacheClass org.atmosphere.cache.UUIDBroadcasterCache org.atmosphere.plugin.jms.JMSBroadcaster.JNDINamespace java:/ org.atmosphere.plugin.jms.JMSBroadcaster.JNDIConnectionFactoryName AgidoJMSConnectionFactory org.atmosphere.plugin.jms.JMSBroadcaster.JNDITopic topic/agidoEventTopic 0 true AtmosphereServlet /push/*
Thats all, your server is properly configured!
In this setup, all incomming messages will be handled by separate Broadcaster using JMS selector, which will be the specific path. So newly connected client to path: "/push/events/2" will be handled by Broadcaster instance connected to JMS using "/push/events/2" selector.
Ok, now we know how to handle received message. But how do we send anything to client?
That is simple. In this example we send new event to all clients connected to "/push/events/2" path:
Event event = new Event("Hello client!"); final Broadcaster broadcaster = getBroadcaster(selector); broadcaster.broadcast(new EventEncoder().encode(event));What we should do, when we want to send specific event to all connected clients? We can use MetaBroadcaster class.
MetaBroadcaster.getDefault().broadcastTo("/push/events/*", new EventEncoder().encode(event));
Client side
There is a simple wrapper for atmosphere.js library, that you can inside AngularJS https://github.com/bertramdev/angular-atmosphere.Using server configuration from this post, we can use all transports, except of Websockets! For more details about using client side Atmosphere library, check out useful Atmosphere wiki pages: https://github.com/Atmosphere/atmosphere/wiki
No comments:
Post a Comment