Archive

Archive for the ‘JQuery’ Category

Atmosphere 1.0, the Asynchronous JavaScript/Java Framework now available!

September 4, 2012 36 comments

Today, after almost 4 years of in and out works(*), I’m really happy to announce the immediate availability of the Atmosphere Framework version 1.0, the only Asynchronous JavaScript/Java Portable framework running on the JVM!

The Atmosphere Framework ships with multiple components, and can be used with JRuby, Scala, Groovy and Java:

  • Atmosphere Javascript: The client side of Atmosphere, supporting by all browsers. Supports Websockets and HTML 5 Server Sides Events when available, transparently fallback to Http Streaming/Long Polling or JSONP when not supported. Available for JQuery and Ext JS. Supports sharing/multiplexing a single connection amongst browser’s windows/tabs.
  • Atmosphere Runtime: The server side of Atmosphere, supporting all major WebServers. Supports WebSockets and HTML 5 Server Sides Events when available, fallback to WebServer’s Native Comet Implementation or the ugly Servlet 3 Async API at last. Also works transparently on top of the Netty and Grizzly Framework (no Servlet Container required). It also transparently brings WebSockets support to any exiting Servlet, without any changes required!
  • Atmosphere Jersey:  A native extension to the Jersey Framework (REST). Transparently brings WebSockets and HTML5 Server Side Events to  your REST architecture, using the JAX RS API. Also support the strange JAX RS 2 Async API.
  • Atmosphere GWT: A native extension to the GWT Framework. Transparently brings WebSockets and HTML5 Server Side Events.
  • Atmosphere Socket.IO: A native extension to the Socket.IO protocol. Free you from node.js, brings you the JVM scalabilty for free. WebSockets included.
  • Atmosphere Cometd: Run the Cometd Framework on top of Atmosphere, get portable WebSockets  and the cloud API for free.
  • Atmosphere Cloud API: Many module are available to make your application working into the cloud. Native Support for Redis PubSub, Hazelcast, JGroups, XMPP (GMail), JMS

Atmosphere is natively supported and available in PrimeFaces, Apache Wicket, Vaadin, Grails, RichFaces, Scalatra, Apache Tuscany, JQuery-Socket etc. and works fine with Spring and Google Guice.

Atmosphere works fine with Firefox 3+, Internet Explorer 6+, Opera 10+, Chrome 2+, Safari 2+, IOS 2+ and Android 2+ and supports connections sharing across multiple tabs and windows as. Open one connection, share it on all pages!

Atmosphere works with every Java EE Application Server and can automatically negotiates the best transport between the client and server. Atmosphere’s support portable WebSockets with Jetty, GlassFish, Grizzly 2, Tomcat and Netty. Write once, deploy anywhere!

Atmosphere ships with many samples, so get started very fast.

In short, Atmosphere works everywhere and with every framework. Websockets, Server Sides Events or any HTTP techniques are supported transparently. Write one javascript, one server file and Atmosphere will make it work every where, without any changes! As simple as (Client)

    var request = { url: document.location.toString() + 'chat',
                    contentType : "application/json",
                    transport : 'websocket' ,
                    fallbackTransport: 'long-polling'};

    request.onOpen = function(response) {
         // Display the user logged
    };

    request.onMessage = function (response) {
        var message = response.responseBody;
        var json = jQuery.parseJSON(message);
        addMessage(json);
    };

(Server)

@AtmosphereHandlerService(path = "/chat", 
interceptors= {AtmosphereResourceLifecycleInterceptor.class})
public class Chat implements AtmosphereHandler {

    @Override
    public void onRequest(AtmosphereResource r) throws IOException {
        r.getBroadcaster().broadcast(r.getRequest().getReader().readLine());
    }

    @Override
    public void onStateChange(AtmosphereResourceEvent event) throws IOException {
        AtmosphereResponse res = r.getResponse();

        if (event.isSuspended()) {
            String body = event.getMessage().toString();
            res.getWriter().write(new Data(body).toString());
        }
    }
}

You can see the Wordnik's Atmosphere implementation lives, servicing approximatively between 40 to 50 millions of requests per day on WSJ.com (depending on enabled pages), or with any website using our Related Content PlugIn.

Atmosphere gets more than 25 000 downloads per month and widely used in production. Follow us on Github or Twitter. The more we are the better it will be!

(*) I would like to gigantically thanks Tony Tam and Erin McKean (and the team @Wordnik) for allowing me to work a significant amount of my time on Atmosphere every day, since last year!!.

Categories: Atmosphere, Comet, JQuery, Websocket

Writing Portable HTML5 Server Side Events Applications using the Atmosphere Framework

The Atmosphere Framework easily allow the creation of HTML5 Server Side Events (SSE). Better, any existing Servlet based application can add SSE support without any changes to their existing application.

HTML5 Server Side Events (SSE) are getting more and more adopted and support for it starts to appear. As an example,  the GlassFish Application Server recently added support for it,  the upcoming release of the Jersey Framework is also adding some sort of support, and framework like jQuery-Socket has sample supporting SSE as well. Both GlassFish and Jersey suffer major issues: First, you need to use non portable API to start using SSE (will only work in GlassFish or Jersey) and second, they expose special API to support SSE, which is a major mistake in my opinion. Just take a look at how simple it can be to implement SSE using the jQuery-Socket sample. Why would you use heavyweight API like GlassFish or Jersey to achieve something that simple? Not only that, but currently Internet Explorer isn’t supporting SSE, so if you use either use GlassFish or Jersey, your application will NOT WORK with Internet Exporer. Oups!!!

This is where Atmosphere comes to the rescue. With Atmosphere, you don’t have to add anything special to have SSE supported by your application. Event better, you can ask Atmosphere to fallback to another technique if SSE is not supported. As an example, you can ask for WebSocket or Long-Polling to be used when SSE is not available. Atmosphere will transparently fall back. On the server side, you don’t need to care about anything as Atmosphere will do it for you. As an example, let’s write a simple Chat application using Atmosphere and Jersey (but not the Jersey SSE implementation!).

First, let’s define a super simple Jersey Resource:

  1 @Path("/")
  2 public class ResourceChat {
  3 
  4     @Suspend(contentType = "application/json")
  5     @GET
  6     public String suspend() {
  7        return "";
  8     }
  9 
 10     @Broadcast(writeEntity = false)
 11     @POST
 12     @Produces("application/json")
 13     public Response broadcast(Message message) {
 14          return new Response(message.author, message.message);
 15     }
 16 }

The important code here is line 4, where the Atmosphere Suspend annotation is used to suspend to tell Atmosphere to not commit the response, e.g leave the connection open. Under the hood it means the Atmosphere native SSE implementation will be enabled and SSE message transparently handled. With line 10, we are telling Atmosphere to broadcast the message back the client to all suspended connections, or stated differently, to all our connected Browser, supporting SSE or not. This is important to not here that if the remote browser isn't supporting SSE, a fallback transport will be used. For that sample let's use long-polling, but if you are using Internet Explorer 10 we could have chosen WebSockets as a fallback mechanism. You can download the server code (complete sample) here.

No on the client side, all we need to do is to tell Atmosphere to use SSE as its main transport (complete code here):

  1 var request = { url: document.location.toString() + 'chat',
  2     contentType : "application/json",
  3     logLevel : 'debug',
  4     transport : 'sse' ,
  5     fallbackTransport: 'long-polling'};
  6 
  7     
  8 request.onOpen = function(response) {
  9     content.html($('<p>', { text: 'Atmosphere connected using '
          + response.transport }));
 10     input.removeAttr('disabled').focus();
 11     status.text('Choose name:');
 12 };
 13     
 14 request.onMessage = function (response) {
 15     var message = response.responseBody;
 16     try {
 17         var json = JSON.parse(message); 
 18     } catch (e) {
 19         console.log('This doesn\'t look like a valid JSON: ',
                    message.data);
 20         return;
 21     }
 22         
 23     if (!logged) {
 24         logged = true;
 25         status.text(myName + ': ').css('color', 'blue');
 26         input.removeAttr('disabled').focus();
 27     } else {
 28         input.removeAttr('disabled');
 29         
 30         var me = json.author == author;
 31         var date =  json.time;
 32         addMessage(json.author, json.text, me 
               ? 'blue' : 'black', new Date(date));
 33     }
 34 };
 35     
 36 request.onError = function(response) {
 37     content.html($('<p>', { text: 
           'Sorry, but there\'s some problem with your '
 38                 + 'socket or the server is down' }));
 39 };
 40                                                                                                                                                          
 41 var subSocket = socket.subscribe(request);

Important code here is line 1 where we create a request and configuring SSE as the main transport and Long-Polling as a fallback transport. Note that we could replace SSE with WebSocket and our application WILL STILL WORK, WITHOUT ANY CHANGES NEEDED ON THE SERVER SIDE!!! Line 8, 14 and 36 are used to define some function that will be invoked when the connection gets established, when messages are received and when network errors occurs. And your application will works everywhere Servlet 2.5 is supported. So the question is: Why using private/non portable API and worse, special API just to support only Server Side Events? Don't go that route and use Atmosphere, and get all for free!

For any questions or to download Atmosphere Client and Server Framework, go to our main site, use our Google Group forum, follow the team or myself and tweet your questions there! .

Categories: Atmosphere, Comet, JQuery, Websocket

Atmosphere .9 .9 .9 .9 released: Tomcat/GlassFish WebSocket, Netty Framework, Hazelcast, Fluid API, JQuery Optimization

The Atmosphere Framework version 0.9 has been released and contains a lot of new cool features and bugs fixes (around 101!!). This blog will describes the main new features! But first, let’s make it clear: the Atmosphere Framework has been designed to transparently support Comet and WebSocket with both client and server components. That means you don’t have to care if a server supports WebSocket or Comet, if a Browser supports WebSocket or not. You write your application and Atmosphere will pick the best transport for you, TRANSPARENTLY! As an example, all applications written with Atmosphere deployed on Jetty can now be deployed AS-IT-IS in Tomcat and automatically use the new Tomcat’s WebSocket API. Yes, you read it correctly: WITHOUT any changes!

Performance and Massive Scalability?

Atmosphere is live for 3 months of WSJ.com and Smartmoney.com and serves millions of requests using WebSocket, Long-Polling and JSONP => Atmosphere is production ready! And there is much more live Web Site using Atmosphere…I just can list them here.

Adoption

Atmosphere is available in mostly all available framework, either as a plugin or extension. If your framework isn’t supporting Atmosphere, complains to them and tell them it is really simple to add support!

jQuery.atmosphere.js new API

The Atmosphere Javascript client has been rewritten in order to allow a better integration with the WebSocket API  and auto detection of the best transport to use depending of server capacity. For example, atmosphere.js always try to use WebSocket as the initial transport and will transparently negotiate with the server to see which transport to use. If the server isn’t supporting WebSocket, atmosphere.js will transparently use HTTP (long-polling, streaming or JSONP). On both client and server side, your application doesn’t need to implement anything in order to work. API wise, function support has been added to make it really easy to write Javascript client. New functions available are onOpen, onReconnect, onClose, onError. As an example, a chat client supporting WebSocket and Comet will only consist of:

    var socket = $.atmosphere;
    var request = { url: document.location.toString() + 'chat',
                    contentType : "application/json",
                    logLevel : 'debug',
                    transport : 'websocket' ,
                    fallbackTransport: 'long-polling'};

    request.onOpen = function(response) {
       ...
    };

    request.onReconnect = function (request, response) {
       ...
    };

    request.onMessage = function (response) {
        var message = response.responseBody;
        ...
    };

    request.onError = function(response) {
       ...
    };

    var subSocket = socket.subscribe(request);

A lot of improvements has been made to hide Browser specific implementation: the famous Internet Explorer works transparently and supported version are 6/7/8/9 and always pick the best transport.

Native Tomcat WebSocket

Starting with Tomat 7.0.27, Atmosphere will detect the new Tomcat WebSocket API and use it by default. That means atmosphere.js will negotiate the WebSocket protocol and use it. That also means as soon as you deploy your application from previous Tomcat version to 7.0.27, WebSocket will transparently used. As an example, the chat client described above will transparently communicates using WebSocket to its associated Server component

@Path("/")
@Produces("application/json")
public class ResourceChat {

    @Suspend
    @GET
    public String suspend() {
        return "";
    }

    @Broadcast(writeEntity = false)
    @POST
    public Response broadcast(Message message) {
        return new Response(message.author, message.message);
    }
}

Now you can compare the number of line the Atmosphere Chat (support all transports) requires versus the Tomcat Chat (which, btw, only support WebSocket). Much more simpler with Atmosphere!

Netty Framework Support

YES, you read it correctly. Atmosphere has been refactored and can now be run on top of non Servlet Container! NettoSphere is the runtime that allow any existing Atmosphere application to run transparently on top of the Netty Framework. NettoSphere also support WebSocket and any existing applications will work without any change. As simple as

    Nettosphere server = new Nettosphere.Builder().config(
                 new Config.Builder()
                    .host("127.0.0.1")
                    .port(8080)
                    .staticResourcePath("/Users/jfarcand/")
                    .resource(MyResource.class)
                    .build())
                 .build();
    server.start();

GlassFish 3.1.2 WebSocket Support

The latest GlassFish release ships with an updated WebSocket implementation and Atmosphere makes use of it. As with Tomcat, Netty and Jetty, Atmosphere applications can be deployed without any modifications.

JAX-RS 2.0 Async API Support

The work in progress JAX-RS 2.0 specification introduces a new async API (which strangely looks like Atmosphere's own API ;-)). The current incarnation of the API is really limited and I really hope the Spec EG will reconsider their decision. But whatever decision is made, Atmosphere supports the new annotation and the ExecutionContext class. The chat application would looks like

    // You can use that object to suspend as well.
    @Context ExecutionContext ctx;

    @Suspend() // Not the Atmosphere's Suspend
    @GET
    public String suspend() {
        // ctx.suspend
        return "";
    }

    @POST
    @Broadcast(writeEntity = false)
    public Response broadcast(Message message) {
        return new Response(message.author, message.message);
    }

You can compare with the current JAX-RS samples Chat, that only support long-polling (much more complex). The JAX-RS Async API lack of listener is a major issue IMO. As an example, Atmosphere's Suspend annotation suppors event listeners which can be used to track the current state of the connection. As an example, with the current incantation of JAX-RS Async API, an application cannot be notified when a client drop the connection and hence the application's resources can never be cleaned. That can easily produces out of memory error. Another problem is with the JAX-RS Async API it is quite complex to implement http-streaming because some browsers required some padding data before the response (WebKit and IE). In conclusion: since Atmosphere runs transparently in all WebServer, there are no good reasons to move to JAX RS 2 Async API (which will only runs on top of Servlet 3.0 WebServer). Instead use the API with Atmosphere and get portability, WebSocket and transport negotiation.

Native WebSocket Application

Atmosphere supports native WebSocket development, e.g application that only support WebSocket. A simple pubsub WebSocket will looks like

public class WebSocketPubSub extends WebSocketHandler {

    private static final Logger logger = LoggerFactory.getLogger(WebSocketPubSub.class);

    @Override
    public void onTextMessage(WebSocket webSocket, String message) {
        AtmosphereResource r = webSocket.resource();
        Broadcaster b = lookupBroadcaster(r.getRequest().getPathInfo());

        if (message != null && message.indexOf("message") != -1) {
            b.broadcast(message.substring("message=".length()));
        }
    }

    @Override
    public void onOpen(WebSocket webSocket) {
        // Accept the handshake by suspending the response.
        AtmosphereResource r = webSocket.resource();
        Broadcaster b = lookupBroadcaster(r.getRequest().getPathInfo());
        r.setBroadcaster(b);
        r.addEventListener(new WebSocketEventListenerAdapter());
        r.suspend(-1);
    }

. The client would consists of

var request = { url : document.location.toString() };

request.onMessage = function (response) {
    if (response.status == 200) {
        var data = response.responseBody;
        if (data.length > 0) {
            // print the message
        }
    }
}
subSocket = socket.subscribe(request);

You can also use the WebSocket API directly, but atmosphere.js does a lot more (reconnection, proper API use, etc.)

Hazelcast support

Atmosphere can now be clustered/clouded using Hazelcast. As simple as:

@Path("/pubsub/{topic}")
@Produces("text/html;charset=ISO-8859-1")
public class JQueryPubSub {

    private @PathParam("topic") HazelcastBroadcaster topic;

    @GET
    public SuspendResponse subscribe() {
        return new SuspendResponse.SuspendResponseBuilder()
                .broadcaster(topic)
                .outputComments(true)
                .addListener(new EventsLogger())
                .build();
    }

    @POST
    @Broadcast
    public Broadcastable publish(@FormParam("message") String message) {
        return new Broadcastable(message, "", topic);
    }
}

The broadcast operations will be distributed to all Hazelcast server available. You can masively scale your WebSocket application by just using the Hazelcast plugin! Don't like Hazelcast? You can do the same using Redis/JMS/XMPP or JGroup broadcaster as well.

Miscellaneous

Tons of new features and improvements (101 issues fixed). Amongst them

1.0.0: Coming SOON, SwaggerSocket around the corner!

Atmosphere is fully powered by Wordnik and we are working hard to make the 1.0.0 release happens soon (End of May 2012). At Wordnik we are using Atmosphere a lot and soon we will release our REST over WebSocket protocol (called SwaggerSocket, which integrate with Swagger). New features will includes an Atmosphere Java Client that will mimic the Javascript functionality, Terracotta supports, etc (see a detailed list here).

For any questions or to download Atmosphere Client and Server Framework, go to our main site, use our Google Group forumfollow the team or myself and tweet your questions there! .

Atmosphere 0.8: Jersey on Steroid, WebSocket Sub Protocol, Native WebSocket, JQuery Plugin CORS, REST over HTTP

November 25, 2011 5 comments

The Atmosphere Framework 0.8 version has been released. This is our biggest release ever! This blog will describe the new feature covered by this fantastic release.

Jersey on Steroid with WebSocket

The Jersey Framework can now be fully run on top of a WebSocket connection seamlessly. A single WebSocket connection can now be used to invoke Jersey. That’s open the door to request pipeline and asynchronous processing of REST call transparently. This will be discussed soon in more details, but this feature is implemented via the new WebSocketProtocol API.

Browser WebSocket Support

Atmosphere fully support Opera, Firefox (MozWebSocket), Chrome, Safari and IE 10 Preview WebSocket transparently when used with the Atmosphere JQuery Plug In. Safari on iOS is also supported.

Native WebSocket Support

It is now possible to write native WebSocket application. A simple PubSub application can consist only of:

    public AtmosphereRequest onMessage(WebSocket webSocket, String message) {
        AtmosphereResource r = (AtmosphereResource) webSocket.resource();
        Broadcaster b = lookupBroadcaster(r.getRequest().getPathInfo());

        b.broadcast(message);

        //Do not dispatch to another Container
        return null;
    }

    public void onOpen(WebSocket webSocket) {
        // Accept the handshake by suspending the response.
        AtmosphereResource r = (AtmosphereResource) webSocket.resource();
        Broadcaster b = lookupBroadcaster(r.getRequest().getPathInfo());
        r.setBroadcaster(b);
        r.addEventListener(new WebSocketEventListenerAdapter());

        // Keep Alive the WebSocket Connection Forever
        r.suspend(-1);
    }

    public void onClose(WebSocket webSocket) {
        webSocket.resource().resume();
    }

    public void onError(WebSocket webSocket,
                        WebSocketProcessor.WebSocketException t) {
        logger.error(t.getMessage() + " Status {} Message {}",
                     t.response().getStatus(),
                     t.response().getStatusMessage());
    }

WebSocket Sub Protocol Implementation Support

Writing WebSocket sub protocol on top of a WebSocket connection is now extremely simple, thanks to the WebSocketProtocol API

public interface WebSocketProtocol extends AsyncProtocol{

    /**
     * Allow an implementation to query the
     * AtmosphereConfig of init-param, etc.
     */
    void configure(AtmosphereServlet.AtmosphereConfig config);

    /**
     * Parse the WebSocket message, and delegate the processing
     * to the {@link org.atmosphere.cpr.AtmosphereServlet#cometSupport} or
     * to any existing technology. Invoking
     * {@link org.atmosphere.cpr.AtmosphereServlet#cometSupport} will
     * delegate the request processing
     * to the {@link org.atmosphere.cpr.AtmosphereHandler} implementation.
     * Returning null means this implementation will
     * handle itself the processing/dispatching of the WebSocket's request;
     * /
    AtmosphereRequest onMessage(WebSocket webSocket, String data);

    AtmosphereRequest onMessage(WebSocket webSocket, byte[] data, int offset, int length);

    /**
     * Invoked when a WebSocket is opened
     * @param webSocket {@link WebSocket}
     */
    void onOpen(WebSocket webSocket);

    /**
     * Invoked when a WebSocket is closed
     * @param webSocket {@link WebSocket}
     */
    void onClose(WebSocket webSocket);

    /**
     * Invoked when an error occurs.
     * @param webSocket {@link WebSocket}
     * @param t a WebSocketProcessor.WebSocketException
     */
    void onError(WebSocket webSocket, WebSocketProcessor.WebSocketException t);

By default, Atmosphere uses the SimpleHttpProtocol to dispatch WebSocket message to Servlet based container. As an example, Atmosphere dispatch WebSockets messages to Jersey by wrapping the message inside an HttpServletRequest and  an asynchronous I/O HttpServletResponse. By default message are considered as POST when dispatched to Jersey, but all HTTP property are configurable (content-type, headers, cookies, etc.). Another example is the EchoProtocol, which just echo message to all connected WebSocket.

Improved Cross-Origin Resource Sharing (CORS) Support

The Atmosphere JQuery Plug In has an improved support for CORS, specially with IE 8/9. You can either turn it on globally or per request. As simple as:

                    jQuery.atmosphere.subscribe(
                        this.url,
                        this.atmosphereCallback,
                        jQuery.atmosphere.request = {
                            method : 'POST',
                            data : json,
                            transport: "websocket" ,
                            fallbackMethod: "POST",
                            enableXDR : 'true', 
 attachHeadersAsQueryString: true });

Trackability Support and Multi Tab

Trackability of remote AtmosphereResource was available in previous version only with Jersey. With 0.8, Trackability is now available to all modules and natively implemented in the JQuery Atmosphere Plug In. When the Plug In execute a request, a unique ID is assigned to the request. The server will read that unique id and will try to look up an AtmosphereResource linked to that ID. That allow application to remotely manipulate AtmosphereResource without the need to keep track, inside the application itself, a list of AtmosphereResource. That allow an application to suspend more than one AtmosphereResource per connection, opening the possibility to implement Browser multi-tab support by assigning a unique ID per tab, represented by different AtmosphereResource on the server side.

Atmosphere also support Trackable injection like:

  @Path("/subscribe")
  @POST def subscribeAndPublish(
    @HeaderParam(X_ATMOSPHERE_TRACKING_ID) trackedResource: 
                    TrackableResource[AtmosphereResource[_, _]],
    @HeaderParam(X_ATMOSPHERE_TRACKING_ID) trackingID: String,
    @HeaderParam(X_ATMOSPHERE_TRANSPORT) transport: String, message: String)
            : SuspendResponse[TrackableResource[AtmosphereResource[_, _]]] = 
    {
     ....
    }

Headers as Query String

Some environment are just allowing the GET operation. An example if the WebSocket Handshake operation, which by default execute a GET without allowing the client to configure any headers. IE CORS also only support this model. The good news is Atmosphere can encode the headers as a QueryString and decode it on the server side as header, allowing application to pass information during the handshake operation. As simple as:

                    jQuery.atmosphere.subscribe(
                        this.url,
                        this.atmosphereCallback,
                        jQuery.atmosphere.request = {
                            method : 'POST',
                            data : json,
                            transport: "websocket" ,
                            fallbackMethod: "POST",
                            attachHeadersAsQueryString: true

                        });

There is much more new features, take a look at the changes log for more info.

For any questions or to download Atmosphere Client and Server Framework, go to our main site, use our Google Group forum, follow the team or myself and tweet your questions there! .

Categories: Atmosphere, Comet, JQuery, Websocket

Hitchiker Guide to the Atmosphere Framework using WebSocket, Long-Polling and Http Streaming

November 7, 2011 4 comments

The Atmosphere Framework easily allow the writing of web application that support, transparently, SSE (Server Side Events), JSONP, WebSocket, Long-Polling and Http Streaming. The Atmosphere Framework also hide the complexity of the current asynchronous API, which differ from Server to Server and make your application portable among them. More important, it is much more easy to write an Atmosphere application than using the Servlet 3.0 API.

There are several APIs available in Atmosphere to write an asynchronous application: AtmosphereHandler, Meteor or using Jersey‘s Atmosphere extension. In this blog I will take the famous JQuery PubSub sample to demonstrate those APIs. Note that I will not discuss the JQuery Atmosphere Plugin as it is the same for all APIs. Important, all code snippet below support WebSocket, SSE, JSONP, Long-Polling and Streaming by default. Only the last section only support WebSocket.

The JQuery PubSub Application is quite simple. You enter a topic to subscribe, you select a transport to use (WebSocket, JSONP, SSE, Streaming or Long-Polling) or let the plug in decide for you, and then you are ready to publish message. You can use Redis on the server side to cluster your application among servers. The subscribe operation is done using a GET, the publish using a POST (in the form of message=”something”). If the WebSocket transport is used, the message is wrapped as a POST and delivered as a normal HTTP request. Note that this feature is configurable in Atmosphere

PubSub using AtmosphereHandler

The AtmosphereHandler  is a low level API that can be used to write an asynchronous application. An application just have to implement that interface. This API is usually used by other framework in order to integrate with Atmosphere (GWT, Jersey, Vaading, etc.) but it can also be used if you want to write Servlet style code.  So, with an AtmosphereHandler, the PubSub implementation will take the form of:

public class AtmosphereHandlerPubSub
      extends AbstractReflectorAtmosphereHandler {

    @Override
    public void onRequest
       (AtmosphereResource r)
          throws IOException {

        HttpServletRequest req = r.getRequest();
        HttpServletResponse res = r.getResponse();
        String method = req.getMethod();

        // Suspend the response.
        if ("GET".equalsIgnoreCase(method)) {
            String trackingId = trackingId(req);

            // Log all events on the console, including WebSocket events.
            r.addEventListener(new WebSocketEventListenerAdapter());

            res.setContentType("text/html;charset=ISO-8859-1");

            Broadcaster b = lookupBroadcaster(req.getPathInfo());
            r.setBroadcaster(b);

            if (req.getHeader(X_ATMOSPHERE_TRANSPORT)
                    .equalsIgnoreCase(LONG_POLLING_TRANSPORT)) {
                req.setAttribute(RESUME_ON_BROADCAST, Boolean.TRUE);
                r.suspend(-1, false);
            } else {
                r.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() {
    }

    Broadcaster lookupBroadcaster(String pathInfo) {
        String[] decodedPath = pathInfo.split("/");
        Broadcaster b = BroadcasterFactory.getDefault()
              .lookup(decodedPath[decodedPath.length - 1], true);
        return b;
    }

}

When a GET is received, we lookup a Broadcaster and then suspend the response based on the path info (REST style). Here we need a make sure we aren't sending padding data (required for WebKit browser) when long polling is used. That's the only required conditional evaluation needed in terms of transport. With the POST we just look up the Broadcaster (which represent a pubsub topic) and broadcast the request's body. That;s it.

PubSub using Meteor

The Meteor is another low level API that can be used with existing Servlet application. As an example, the ADF framework use Meteor in order to integrate Atmosphere support.

public class MeteorPubSub extends HttpServlet {

    @Override
    public void doGet(HttpServletRequest req, HttpServletResponse res)
       throws IOException {
        // Create a Meteor
        Meteor m = Meteor.build(req);

        // Log all events on the console, including WebSocket events.
        m.addListener(new WebSocketEventListenerAdapter());

        res.setContentType("text/html;charset=ISO-8859-1");

        Broadcaster b = lookupBroadcaster(req.getPathInfo());
        m.setBroadcaster(b);

        if (req.getHeader(X_ATMOSPHERE_TRANSPORT)
                .equalsIgnoreCase(LONG_POLLING_TRANSPORT)) {
            req.setAttribute(RESUME_ON_BROADCAST, Boolean.TRUE);
            m.suspend(-1, false);
        } else {
            m.suspend(-1);
        }
    }

    public void doPost(HttpServletRequest req, HttpServletResponse res)
        throws IOException {

        Broadcaster b = lookupBroadcaster(req.getPathInfo());
        String message = req.getReader().readLine();

        if (message != null && message.indexOf("message") != -1) {
            b.broadcast(message.substring("message=".length()));
        }
    }

    Broadcaster lookupBroadcaster(String pathInfo) {
        String[] decodedPath = pathInfo.split("/");
        Broadcaster b = BroadcasterFactory.getDefault()
              .lookup(decodedPath[decodedPath.length - 1], true);
        return b;
    }
}

When a GET is received, we create a Meteor and use that Meteor to suspend the response, again using the path info. For post, we do the same as with AtmosphereHandler, e.g retrieve the Broadcaster and broadcast the message.

PubSub using Jersey's Atmosphere Extension

With the Jersey extension, we can either use annotations or the programmatic API. As simple as"

@Path("/pubsub/{topic}")
@Produces("text/html;charset=ISO-8859-1")
public class JQueryPubSub {

    private
    @PathParam("topic")
    Broadcaster topic;

    @GET
    public SuspendResponse subscribe() {
        return new SuspendResponse.SuspendResponseBuilder()
                .broadcaster(topic)
                .outputComments(true)
                .addListener(new EventsLogger())
                .build();
    }

    @POST
    @Broadcast
    public Broadcastable publish(@FormParam("message") String message) {
        return new Broadcastable(message, "", topic);
    }
}

The GET could have been handled using the @Suspend annotation:

    @GET
    @Suspend(listeners = EventsLogger.class, outputComments = true)
    public Broadcastable subscribe(){
        return new Broadcastable(topic);
    }

As you can see, it is quite simpler that with Meteor and AtmosphereHandler.
PubSub.

PubSub using WebSocket only

If you are planning to write pure WebSocket application and don't plan to support normal HTTP, you can also write your own WebSocket sub protocol. It is quote important to note here that only WebSocket will be supported.

public class WebSocketPubSub implements WebSocketProtocol {

    private AtmosphereResource r;

    @Override
    public HttpServletRequest onMessage(WebSocket webSocket, String message) {
        Broadcaster b = lookupBroadcaster(r.getRequest().getPathInfo());

        if (message != null && message.indexOf("message") != -1) {
            b.broadcast(message.substring("message=".length()));
        }

        //Do not dispatch to another Container like Jersey
        return null;
    }

    @Override
    public void onOpen(WebSocket webSocket) {
        // Accept the handshake by suspending the response.
        r = (AtmosphereResource)
              webSocket.atmosphereResource();

        Broadcaster b = lookupBroadcaster(r.getRequest().getPathInfo());
        r.setBroadcaster(b);
        r.addEventListener(new WebSocketEventListenerAdapter());

        r.suspend(-1);
    }

    @Override
    public void onClose(WebSocket webSocket) {
        webSocket.atmosphereResource().resume();
    }

    Broadcaster lookupBroadcaster(String pathInfo) {
        String[] decodedPath = pathInfo.split("/");
        Broadcaster b = BroadcasterFactory.getDefault().
                 lookup(decodedPath[decodedPath.length - 1], true);
        return b;
    }

The important method here is onOpen (for accepting the handshake) and the onMessage, which is were the messages are received.

Conclusion

It is quite important to pick the best API when writing Atmosphere application as it can save you a lot of time! You can download all samples from here.

For any questions or to download Atmosphere Client and Server Framework, go to our main site, use our Google Group forum, follow the team or myself and tweet your questions there! .

Categories: Atmosphere, Comet, JQuery, Websocket

REST + WebSocket applications? Why not using the Atmosphere Framework

The Atmosphere Framework easily allow the creation of REST applications … using WebSocket. This time I will describe a super simple example on how to do it.

The Atmosphere Framework supports transparently both WebSocket and Comet transport and brings portability to any Java based application. An application written using Atmosphere can be deployed in any WebServer and Atmosphere will transparently make it work.  Atmosphere is also able to transparently select the best transport to use, e.g. WebSocket or Comet. Now let’s write a very simple REST application with Comet support as we normally write:

@Path("/pubsub/{topic}")
@Produces("text/html;charset=ISO-8859-1")
public class JQueryPubSub {

    private @PathParam("topic") Broadcaster topic;

    @GET
    public SuspendResponse<String> subscribe() {
        return new SuspendResponse.SuspendResponseBuilder<String>()
                .broadcaster(topic)
                .outputComments(true)
                .addListener(new EventsLogger())
                .build();
    }

    @POST
    @Broadcast
    public Broadcastable publish(@FormParam("message") String message) {
        return new Broadcastable(message, "", topic);
    }
}

Doing

GET /pubsub/something

will invoked the SuspendResponse. To make the exercise simple, all we do there is suspend the connection (e.g. do not return any response, wait for an event). If you want to make this exercise more difficult, you can always implements the ETag trick! Once the connection is suspended, we need to use a second connection in order to post some data

POST /pubsub/something
message=I love Comet

Executing the POST request will result in the invocation of the publish method. The @Broadcast annotation means the FormParam value will be broadcasted to all suspended connections.

WebSocket to rule them all

OK so let's assume we now deploy your application in a WebServer that supports WebSocket like Jetty or GlassFish. Now Atmosphere will auto detect WebSockets are supported and use it when a WebSocket request is done. Now let's assume we build the client using the Atmosphere JQuery Plug In and execute the GET request using Chrome (which support Websocket).The Atmosphere Javascript library is able to challenge the remote server and discover if the server and client support WebSocket, and use it.

In that scenario, suspending the connection will tell Atmosphere to execute the WebSocket handshake. Now the POST will be executed on the same connection, and the public method will be invoked this time.  This is not a POST as you see normally with normal HTTP. Since WebSocket is used, only the form param will be send over the wire:

message=I love WebSocket

All of this occurs without any modification of your REST application. All you need to do to enable WebSocket is to "suspend" the connection when a @GET occurs. Transparent, is it :-) You can download the current version of the sample here.

Now what Atmosphere is doing under the hood is wrapping the WebSocket message into an HttpServletRequest so any framework like Jersey, Wicket, etc, works as it is. If you are familiar with Atmosphere, your AtmosphereHandler implementation will get invoked with an instance of HttpServletRequest that contains the WebSocket message, so you can use it as your will normally do using Comet or normal HTTP request.

For any questions or to download Atmosphere Client and Server Framework, go to our main site, use our Nabble forum, follow the team or myself and tweet your questions there! You can also checkout the code on Github.

Categories: Atmosphere, Comet, JQuery, Websocket

Atmosphere 0.7 released: WebSocket, GWT, Wicket, Redis, XMPP, Async I/O

February 25, 2011 4 comments

Atmosphere 0.7 is available! This release contains an impressive number of new functionality and bug fixes. The extensive list of fixed bugs for that release can be read here, and the new functionality are explained below

Native GWT support

Starting with 0.7, the Atmosphere-GWT project is now fully integrated into Atmosphere. That means you can add support for WebSocket/Comet to any GWT application. You can download demos from here.

Wicket Support

As described here, we do support the Wicket framework our of the box. As simple as:

public class PushPage extends WebPage
    implements AtmosphereResourceEventListener {

    private final AtomicBoolean scheduleStarted = new AtomicBoolean(false);

    public PushPage() {
        HttpServletRequest req = getWebRequestCycle()
               .getWebRequest().getHttpServletRequest();
        Meteor meteor = Meteor.build(req);
        if (!scheduleStarted.getAndSet(true)) {
            meteor.schedule(new Callable<String>() {
                public String call() {
                    String s = new Date().toString();
                    return s;
                }
            }, 1); // One second
        }
        meteor.addListener(this);

        // Depending on the connection
        String transport = req.getHeader("X-Atmosphere-Transport");
        meteor.suspend(-1, !(transport != null
              && transport.equalsIgnoreCase("long-polling")));
    }

    public void onBroadcast(AtmosphereResourceEvent
           <HttpServletRequest, HttpServletResponse> event) {

        String transport = event.getResource()
                .getRequest().getHeader("X-Atmosphere-Transport");
        if (transport != null
              && transport.equalsIgnoreCase("long-polling")) {
            Meteor meteor = Meteor.lookup(event.getResource().getRequest());

            meteor.removeListener(this);
            meteor.resume();
        }
    }

    ...
}

you can download the demo here.

Native Redis Support

As described here, you can now use Redis for massively distribute server sides events amongst your Atmosphere application. This is as simple as configuring Atmosphere to use the RedisBroadcaster to broadcast server sides events. If you already have an Atmosphere application, you don't need to change anything except configuring Atmosphere. You can switch from your in memory broadcaster, JMSBroadcaster, JGroupsBroadcaster by simply doing:

    // JSMBroadcaster || XMPPBroadcaster
    private @PathParam("topic") RedisBroadcaster topic;

    @GET
    public SuspendResponse subscribe() {

        return new SuspendResponse.SuspendResponseBuilder()
                .broadcaster(topic)
                .outputComments(true)
                .addListener(new EventsLogger())
                .build();
    }

    @POST
    @Broadcast
    public Broadcastable publish(@FormParam("message") String message) {
      return new Broadcastable(message, "", topic);
    }

you can download the demo here.

Native XMPP Support

As described here, you can now use the XMPP protocol for massively distribute server sides events amongst your Atmosphere application. As with Redis and JMS, you just need to tell Atmosphere to use the XMPPBroadcaster. You can download the demo here.

Broadcasting Callable<T> now supported

As described here, you can now broadcast Callable, which gets executed just before the response is written back to the browser. This is quite useful when scheduled tasks are needed:

if (feed.getAtmosphereResources().size() == 0) {
    Future<?> future = feed.scheduleFixedBroadcast(new Callable<String>() {
        private final AtomicReference<String> refreshUrl
               = new AtomicReference<String>("");
           public String call() throws Exception {
              String query = null;
              if (!refreshUrl.get().isEmpty()) {
                  query = refreshUrl.get();
              } else {
                  query = "?q=" + tagid;
              }
              asyncClient.prepareGet(
                  "http://search.twitter.com/search.json"  + query)
                    .execute(new AsyncCompletionHandler <Integer>()) {
                          @Override
                          public Object onCompleted(Response response)
                               throws Exception {
                            String s = response.getResponseBody();
                            JSONObject json = new JSONObject(s);
                            refreshUrl.set(json.getString("refresh_url"));
                            feed.broadcast(s).get();
                            return response.getStatusCode();
                          }
                    });
                    return "OK";
                }
            }, 1, TimeUnit.SECONDS);
            futures.put(tagid, future);
        }

you can download the demo here.

WebSocket Emulator Supported (like Flash)

The Atmosphere JQuery Plugin can now be configured to use an external WebSocket implementation. As an example, you can use the web-socket-js library, which adds support for Flash.


     <script type="text/javascript" src="web_socket.js"></script>

     <script type="text/javascript">
         $(document).ready(function()
         {
             .....
         /* transport can be : long-polling, streaming or websocket */
         $.atmosphere.subscribe(uri,
                myEventCallback,
                $.atmosphere.request =
                    {transport: getElementByIdValue('transport'),
                     webSocketImpl: new WebSocket(uri) });

Asynchronous write I/O operation now supported

All the Broadcaster has been modified to support asynchronous I/O write operation using a dedicated ExecutorService. This will prevent slow clients to block the entire broadcast process. You can configure the thread pool strategy using the BroadcastConfig API:

Per Request BroadcastFilter

As described here, per request BroadcastFilter are now supported. That means you can transform an events based on some request's headers, query string etc. This is quite useful when it is time to unify browser support, or take advantage of some browser native API. As simple as:

public class JavascriptClientFilter implements PerRequestBroadcastFilter {

    private final AtomicInteger uniqueScriptToken = new AtomicInteger();

    @Override
    public BroadcastFilter.BroadcastAction filter
                   (HttpServletRequest request, Object message) {

        if (request.getHeader("User-Agent") != null) {
            String userAgent =
               request.getHeader("User-Agent").toLowerCase();
            if (userAgent != null && userAgent.indexOf("MSIE") != -1
                    && message instanceof String) {
                StringBuilder sb = new StringBuilder
                    ("<script id=\"atmosphere_")
                    .append(uniqueScriptToken.getAndIncrement())
                    .append("\">")
                    .append("parent.callback")
                    .append("('")
                    .append(message.toString())
                    .append("');</script>");
                message = sb.toString();
            }
        }
        return new BroadcastFilter.BroadcastAction
            (BroadcastFilter.BroadcastAction.ACTION.CONTINUE, message);
    }

Improved Dependency Injection Support for Spring and Guice

With 0.7 we have added new API to allow easier integration with Spring and Guice.  You can download the demo here.

New Broadcaster's Lifecycle Policy Support

Broadcaster implementation like the JMSBroadcaster can hold open connections and before 0.7 it was difficult to decide what to do with those connections when no WebSocket/Comet connections where alive. We have added a new API that allow an application to configure Atmosphere and decide when a Broadcaster's resource get garbage collected. As simple as:

Broadcaster.setBroadcasterLifeCyclePolicy( IDLE | IDLE_DESTROY | EMPTY | EMPTY_DESTROY | NEVER );

Improve compatibility with other JavaScript Framework

It is now easier to integrate the Atmosphere JQuery Plugin with other framework like Prototype.

Jersey's @DefaultValue and @Singleton now supported

You can now use these two annotations with Atmosphere's object like Broadcaster. As simple as:

More WebContainer supported

Atmosphere 0.7 now support all flavor of Jetty 7 and 8 Websocket implementation, as well a GlassFish v3.1 WebSocket API. Tomcat 7 Servlet 3.0 implementation works also perfectly well.

Broadcaster new API

The Broadcaster API has been heavily improved and you can now look up per request Broadcaster and also you can configure Atmosphere to broadcast value when a resume operation occurs, etc. All new features are available to all Broadcaster. See the API for more information

What's next

The Atmosphere 0.8 release should happens before the summer and will include Socket.IO support, Google App Engine channel support, Cometd 2.2.x support, unified Atmosphere Client Javascript library and of course all the requests asked by the community.

For any questions or to download Atmosphere Client and Server Framework, go to our main site, use our Nabble forum, follow the team or myself and tweet your questions there! You can also checkout the code on Github.

Categories: Atmosphere, Comet, JQuery, Websocket

Friday’s Trick #5: Per Request Filtering of WebSocket/Comet Server Side Events

November 26, 2010 1 comment

This week I will describe how you can filter, transform and aggregate WebSocket/Comet’s server side events per request using the Atmosphere Framework.

As you may know, Atmosphere already support global filtering for a set of WebSocket/Comet connection that are associated with a Broadcaster. Those filters are called BroadcastFilter and are applied every time a server side event (SSE) is broadcasted.  In that case, all connections associated with the Broadcaster, which include WebSocket, long-polling and http streaming connections will receive a SSE that may have been transformed by a BroadcastFilter. As an example, in order to support http streaming in Internet Explorer, the following BroadcastFilter needs to be added (in web.xml or programmatically):

public class JavaScriptClientFilter implements BroadcastFilter {
    private final AtomicInteger uniqueScriptToken = new AtomicInteger();

    @Override
    public BroadcastAction filter(Object message) {
        if (message instanceof String) {
            StringBuilder sb = new StringBuilder("<script id=\"atmosphere_")
                    .append(uniqueScriptToken.getAndIncrement())
                    .append("\">")
                    .append("parent.callback")
                    .append("('")
                    .append(message.toString())
                    .append("');</script>");
            message = sb.toString();
        }
        return new BroadcastAction(BroadcastAction.ACTION.CONTINUE, message);
    }
}

If we use the Atmosphere JQuery PubSub sample, which is defined as:

@Path("/pubsub/{topic}")
@Produces("text/html;charset=ISO-8859-1")
public class JQueryPubSub {

    private @PathParam("topic") Broadcaster topic;

    @GET
    public SuspendResponse subscribe() {
        return new SuspendResponse.SuspendResponseBuilder()
                .broadcaster(topic)
                .outputComments(true)
                .addListener(new EventsLogger())
                .build();
    }

    @POST
    @Broadcast
    public Broadcastable publish(@FormParam("message") String message) {
        return new Broadcastable(message, "", topic);
    }

BroadcastFilter will be invoked after the publish method return. Hence the browse generates SSE "hello sent using websocket" to be broadcasted to all connections associated with the Broadcaster's "topic", the remote browser will receive the transformed message

<script id="atmosphere_0">parent.callback('hello sent using websocket');</script> 

If you use the Atmosphere JQuery Plugin, the IE specific code for supporting http streaming will be executed. But that solution is far from optimal because we do process every message independently of the browser user-agent, which is a waste of resource. The JQuery Plugin will have no problem parsing the message, but it would be far more optimal if we could transform SSE only when IE is used. This is where the PerRequestBroadcastFilter comes into the picture. To implements a per request transformation, all we need to do is:

public class JavascriptClientFilter implements PerRequestBroadcastFilter {

    private final AtomicInteger uniqueScriptToken = new AtomicInteger();

    @Override
    public BroadcastFilter.BroadcastAction filter
                   (HttpServletRequest request, Object message) {

        if (request.getHeader("User-Agent") != null) {
            String userAgent = request.getHeader("User-Agent").toLowerCase();
            if (userAgent != null && userAgent.indexOf("MSIE") != -1
                    && message instanceof String) {
                StringBuilder sb = new StringBuilder
                    ("<script id=\"atmosphere_")
                    .append(uniqueScriptToken.getAndIncrement())
                    .append("\">")
                    .append("parent.callback")
                    .append("('")
                    .append(message.toString())
                    .append("');</script>");
                message = sb.toString();
            }
        }
        return new BroadcastFilter.BroadcastAction
            (BroadcastFilter.BroadcastAction.ACTION.CONTINUE, message);
    }

Now we talk! With the above, our SSE is only transformed when the IE browser is used. Filtering WebSocket's message can also be transformed using the same technique. If you have play/read with the JQuery PubSub, the sample can be used with WebSocket/Comet and the JQueryPubSub code described above. When the browser send form param via a Websocket connection, we need to extract the form data in order to broadcast it back. A simple way to do it is by:

public class FormParamFilter implements PerRequestBroadcastFilter{

    @Override
    public BroadcastFilter.BroadcastAction filter
          (HttpServletRequest request, Object message) {

        if (request.getHeaders("X-Atmosphere-Transport").equals("WebSocket")) {
            if ( (message instanceof String) 
               && ((String) message).indexOf("=") != -1) {
                message =  message.toString().split("=")[1];
            }
        }
        return new BroadcastFilter.BroadcastAction
             (BroadcastFilter.BroadcastAction.ACTION.CONTINUE, message);
    }
}

With the above filter we are parsing the form param value only when Websocket is used, in order to extract the SSE we want to broadcast to all other connections. Of course, you can do much more complex operations inside filters!

For any questions or to download Atmosphere Client and Server Framework, go to our main site, use our Nabble forum, follow the team or myself and tweet your questions there! You can also checkout the code on Github.

Categories: Atmosphere, Comet, JQuery, Websocket

Friday’s Tricks #4: Improving Websocket/Comet performance using Delayed/Aggregated Server Side Events

November 12, 2010 1 comment

This week I will explain how you can significantly improve the performance of your WebSocket/Comet application using delayed and aggregated Server Side Events using the Atmosphere Framework.

It is not trivial to broadcast real time server side events using a Comet or WebSocket connection. As an example, if the frequency of your server side events broadcast is high like many events per seconds, it is important to pick up the best strategy when it is time to write those events back to the client. The usual mistake an application does is to sent events to the browser one by one. With the long-polling Comet technique, this gives catastrophic performance issues as the browser needs to reconnect after receiving an events. For the http-streaming Comet’s technique or with WebSocket, the browser doesn’t have to reconnect, but you can still produce catastrophic performance problem on both client and server side if you send events one by one.

With the Atmosphere Framework, you can aggregate or delay events in order to reduce the number of events you sent back to the browser. Let’s explore the first technique, which consist of delaying server side events:

    @Path("/")
    @POST
    @Broadcast(delay=0)
    public Broadcastable buffer(@FormParam("message") String message){
        return broadcast(message);
    }

The code above reflect any form data received from a browser to the set of WebSocket/Comet connections listening to server side events. Instead of automatically sent back the form data to the browser, the code above delay the operation until a second event happens. This is particularly useful if your server side event doesn't have to be sent real time. Now it may never occurs a second event, so the code above is a little dangerous. Instead, let's use:

    @Path("/")
    @POST
    @Broadcast(delay=5)
    public Broadcastable buffer(@FormParam("message") String message){
        return broadcast(message);
    }

The difference this time is the server side events will be delayed for 5 seconds maximum. If an second event happens before, the two events will be aggregated and send as a single event.

Although the above mechanism is useful, you may want to aggregate many server side events and send them as a hole instead of one by one. This is particularly true when you need to construct a complex data structure. You can let the browser cache/aggregate the result (but all the data can potentially be lost if the browser crash). Instead, you should create the complex data on the server side by aggregating events. Let's say your application stream JSON array from an external source. It is quite possible the JSON data received from an external source isn't fully complete as I've demonstrated in my Twitter Feed application, which use the Atmosphere JQuery Plugin (ya, I do what I recommend to avoid :-)):

function subscribe()
   {
     function callback(response)
     {
       $.atmosphere.log('info', ["response.state: " + response.state]);
       $.atmosphere.log('info', ["response.transport: " + response.transport]);
       if (response.transport != 'polling'
         && response.state != 'connected'
         && response.state != 'closed') {

         if (response.status == 200) {
           var data = response.responseBody;

           try {
                var result =  $.parseJSON(incompleteMessage + data);
                incompleteMessage = "";

                 var i = 0;
                 for (i = result.results.length -1 ; i > -1; i--){
                   $('ul').prepend($('').text("["
                         + response.transport + "] "
                         + result.results[i].from_user + " "
                           result.results[i].text));
                 }
            } catch (err) {
               // JSON error (JSON message not complete)
               incompleteMessage = data;
            }
 

In that case, it would be much better to aggregate the server side events until the JSON object has been fully constructed. With Atmosphere, all you need to do is to define a BroadcastFilter. Let's use a simple example by aggregating String events:

public class StringFilterAggregator implements BroadcastFilter {
    private final int maxBufferedString;
    private final AtomicReference<StringBuilder> bufferedMessage
        = new AtomicReference<StringBuilder>(new StringBuilder());

    public StringFilterAggregator() {
        maxBufferedString = 256;
    }

    public StringFilterAggregator(int maxBufferedString) {
        this.maxBufferedString = maxBufferedString;
    }

    public BroadcastAction filter(Object message) {
        if (message instanceof String) {
            bufferedMessage.get().append(message);
            if (bufferedMessage.get().length() > maxBufferedString) {
                return new BroadcastAction(ACTION.ABORT, message);
            } else {
                message = bufferedMessage.toString();
                bufferedMessage.get().delete(0, bufferedMessage.get().length());
                return new BroadcastAction(ACTION.CONTINUE, message);
            }
        } else {
            return new BroadcastAction(message);
        }
    }
}

All we need to do is to add our Filter to the Atmosphere's Broadcaster.

    @Path("/")
    @Broadcast
    @POST
    public Broadcastable aggregate(@Context Broadcaster bc) {
         .....
         bc.getBroadcasterConfig().addFilter(new StringFilterAggregator());

        return new Broadcastable(bc);
    }
} 

So now every time an events gets broadcasted, the String will be aggregated until it reach the limit, and only when the limit is reached the write operations will be executed. That can significantly improve the performance of your application.

For any questions or to download Atmosphere Client and Server Framework, go to our main site, use our Nabble forum, follow the team or myself and tweet your questions there! You can also checkout the code on Github.

Categories: Atmosphere, Comet, JQuery, Websocket

Using JQuery, XMPP and Atmosphere to cluster your WebSocket/Comet application

November 8, 2010 3 comments

The Extensible Messaging and Presence Protocol (XMPP) is an open technology for real-time communication, which powers a wide range of applications including instant messaging, presence, multi-party chat, voice and video calls, collaboration, lightweight middleware, content syndication, and generalized routing of XML data. The protocol’s adoption is phenomenal, and this time I will show how easy is to use the Atmosphere’s XMPP Plugin with GTalk to scale and cluster a WebSocket/Comet application.

Warning: If you have read my previous blog entry on Redis and WebSocket, this new entry will suspiciously looks similar :-) The Atmosphere Framework Plugin API is bloody simple and adding support for any cloud/clustering technology is extremely easy.

Atmosphere supports many communication channel between server, but today we will focus on our newly addition: XMPP.  For the sample I will use GTalk (but works with any XMPP server) and the XMPP library named  Smack. I will show how it is possible to deploy a WebSocket application on any Webserver and use GTalk/XMPP to share events between “the cloud”.  As usual I will use Atmosphere JQuery Plugin on the client side as the library will always try to use WebSocket, and if fail will fall back to Comet techniques. And fortunately, the Plug In will also fall back to Comet as well if the remote server isn’t supporting WebSocket.  This is great because when you deploy into a cloud or a cluster, you aren’t guarantee all servers will be the same product, same version etc. The Atmosphere Framework saves you from all those questions.  Less things to worries.

OK, so let’s write a simple PubSub application. Without going into the details (read this entry for an introduction of Atmosphere JQuery Plugin), our client side will looks like (browse the code here):


    function callback(response)
    {
      if (response.status == 200) {
        // display the result.
      }
    }
    /* transport can be : long-polling, streaming or websocket */
    $.atmosphere.subscribe(uri + 'pubsub/' + "A topic",
                           callback,
                           $.atmosphere.request = {transport: 'websocket'});

As simple as it look, we just invoke  the subscribe method with a uri, a callback and the default transport we want to use: WebSocket. The server will understand the request and act appropriately: upgrade for WebSocket, avoid committing the response for Comet. For publishing, you can use any existing Redis client or use Atmosphere JQuery Plugin as well

    $.atmosphere.response.push(uri + 'pubsub/' + "A Topic",
                               $.atmosphere.request = {data: 'Some Message'});

Here the push method re-use the existing connection (the WebSocket one) to push messages back to the server.  If WebSocket is not supported, the Plug In will still works and fall back to use Comet. That's it for the client side.

Now, configuration wise, the Atmosphere XMPP's plugin can be configured via your web.xml (or using the API as well):

<?xml version="1.0" encoding="ISO-8859-1"?>
<web-app xmlns="http://java.sun.com/xml/ns/j2ee"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee
               http://java.sun.com/xml/ns/j2ee/web-app_2_4.xsd"
         version="2.4">

    <display-name>XMPPAtmosphere</display-name>

    <servlet>
        <description>AtmosphereServlet</description>
        <servlet-name>AtmosphereServlet</servlet-name>
        <servlet-class>org.atmosphere.cpr.AtmosphereServlet</servlet-class>
        <init-param>
            <param-name>org.atmosphere.useWebSocket</param-name>
            <param-value>true</param-value>
        </init-param>
        <init-param>
            <param-name>org.atmosphere.useNative</param-name>
            <param-value>true</param-value>
        </init-param>

        <init-param>
            <param-name>org.atmosphere.plugin.xmpp.
                 XMPPBroadcaster.authorizationt</param-name>
            <param-value>me@gmail.com:password</param-value>
        </init-param>
        <init-param>
            <param-name>org.atmosphere.plugin.xmpp.
                  XMPPBroadcaster.server</param-name>
            <param-value>http://gmail.com</param-value>
        </init-param>

        <load-on-startup>0</load-on-startup>
    </servlet>

    <servlet-mapping>
        <servlet-name>MeteorServlet</servlet-name>
        <url-pattern>/*</url-pattern>
    </servlet-mapping>
</web-app>

Here you specify a GMail account and which server you want to use ti transit your messages.

Now on the server side, let's use the Atmosphere's Jersey module and the Atmosphere XMPP Plugin. The XMPP Plugin uses the great Smack internally to connect to XMPP Server. For this demo I use GTalk. (browse the code here)

    private @PathParam("topic") XMPPBroadcaster topic;

    @GET
    public SuspendResponse subscribe() {

        return new SuspendResponse.SuspendResponseBuilder()
                .broadcaster(topic)
                .outputComments(true)
                .addListener(new EventsLogger())
                .build();
    }

    @POST
    @Broadcast
    public Broadcastable publish(@FormParam("message") String message) {
      return new Broadcastable(message, "", topic);
    }

Don't look for more code, there isn't more! How it works:

  1. When the JQuery Plugin issue a request like GET /pubsub/"A Topic", the above resource is created and an instance of XMPPBroadcaster gets injected.  The XMPPBroadcaster uses the value of "A Topic" for creating a channel to the  to GTalk' server  using your GMail ID (more on that below) . "A Topic"  is also the name of theXMPPBroadcaster , which can always be used to retrieve it by an external component like an EJB, a Java Program etc.
  2. Second, the subscribe method gets invoked, and we tell Atmosphere to suspend the response. For WebSocket, it means accept the upgrade handshake.
  3. Now any messages send using  Atmosphere JQuery's $.atmosphere.response.push will transit in XMPP via GTalk. The $.atmosphere.response.push will invoke the publish method of your resource, which will use the XMPPBroadcaster  to progate the change to all WebSocket/Comet connection via GTalk.

That's it. The JQuery look and field is the same as the one described here.  Of course you can do much more with XMPP and Atmosphere, but my goal for this blog was to show how easy it is to get started.

As demonstrated, writing an application with Atmosphere is super simple, and deploying it inside a cloud/distributed environment is also simple. The good news here is you don't have to focus on how GTalk/XMPP works, but on your application. On a side note, if you want to use RedisActiveMQ (any JMS impl) or JGroups intead of Redis, this is as simple as:

    private @PathParam("topic")
    RedisBroadcaster topic; // Or JMSBroadcaster or JGroupsBroadcaster

    @GET
    public SuspendResponse subscribe() {

        return new SuspendResponse.SuspendResponseBuilder()
                .broadcaster(topic)
                .outputComments(true)
                .addListener(new EventsLogger())
                .build();
    }

Just need to inject the proper technology you want to use.

The complete binary/source code can be downloaded here. For any questions or to download Atmosphere Client and Server Framework, go to our main site and use our Nabble forum, or follow the team and myself and tweet your questions there!

Follow

Get every new post delivered to your Inbox.

Join 50 other followers