Archive

Archive for November, 2011

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