Using JAXRS 2 Async API - Atmosphere/atmosphere GitHub Wiki

This document describes how to get started with the Atmosphere Framework. This document will demonstrates how to use atmosphere.js client and the JAXRS 2 extension point. If you aren't planning to use the JAXRS 2 extension, see Getting started with AtmosphereHandler, Meteor, Jersey or WebSocketHandler documents.

Note

This sample use Websocket for main transport and long-polling for fallback transport. You can change both transport at any time, e.g use sse (Server Side Events) as transport and streaming as fallback transport.

Prerequisites

An Atmosphere application use the .war format and can be deployed anywhere .war are supported (ex: Tomcat, Jetty, GlassFish). For this tutorial it is assumed you already worked with Maven. Experience with JAX RS/Jersey Framework is also recommended. If you want to use WebSocket, make sure you deploy the application using Tomcat 7.0.27, GlassFish 3.1.2 or Jetty 7.5.1 or an higher version of those WebServer.

Building a WebSocket Chat Application

Server side

Let's build a really simple chat to demonstrate how simple the Atmosphere Framework is. A Chat Resource will looks like

  1 @Path("/")
  2 @Produces("application/json")
  3 public class Jaxrs2Chat {
  4 
  5     @Context
  6     ExecutionContext ctx;
  7 
  8     
  9     @GET
 10     public String suspend() {
 11         ctx.suspend();
 12         return "";
 13     }
 14 
 15     @POST
 16     @Broadcast(writeEntity = false)
 17     public Response broadcast(Message message) {
 18         return new Response(message.author, message.message);
 19     }
 20 
 21 }

Line by line explanation:

Line 1 : The Path annotation is used to tell Jersey to map all the request "/" to this resource. That means a request to http://127.0.0.1:8080/ will be delivered to that resource (or that class will be executed).

Line 11 : The Atmosphere ExecutionContext.suspend() API is used to tell the framework to suspend the connection, or, in other word, to not commit the response and let the connection open for write operations. Suspending a connection with HTTP means leaving not writing back anything where with WebSocket it means execute the handshake operation and let the connection open. Here we are telling Atmosphere to suspend the connection indefinitely (no times out). The connection life cycle will be transparently handled by Atmosphere.

Line 15 : The @Broadcast is used to tell the framework to broadcast events to the set of suspended connections. The writeEntity = false attribute is used here to tell the framework to not write anything back to the client, and instead let the broadcast operation to do it. The broadcast operation will be handled by a Broadcaster.

Line 17 : The Message and Response are JSON representation of the bytes received and sent. Jersey will marshal and unmarshall the bytes transparently.

The sequence of events for the Chat application looks like

  1. The client send a GET request to the server. The request will be delivered to line 12 (@Suspend) and the connection suspended.
  2. The client send a POST request with a message. The request will be delivered to line 19 (@Broadcast) and the chat message will be delivered to all suspended connections. Let's now build the client side

Client side

For the client we are using the atmosphere.js jQuery plugin. The javascript looks like

 10     var socket = $.atmosphere;
 11     var request = { url: document.location.toString() + 'chat',
 12                     contentType : "application/json",
 13                     logLevel : 'debug',
 14                     transport : 'websocket' ,
 15                     fallbackTransport: 'long-polling'};
 16 
 17 
 18     request.onOpen = function(response) {
 19         content.html($('<p>', { text: 'Atmosphere connected using ' + response.transport }));
 20         input.removeAttr('disabled').focus();
 21         status.text('Choose name:');
 22     };
 23 
 24     request.onMessage = function (response) {
 25         var message = response.responseBody;
 26         try {
 27             var json = JSON.parse(message);
 28         } catch (e) {
 29             console.log('This does not look like a valid JSON: ', message.data);
 30             return;
 31         }
 32 
 33         if (!logged) {
 34             logged = true;
 35             status.text(myName + ': ').css('color', 'blue');
 36             input.removeAttr('disabled').focus();
 37         } else {
 38             input.removeAttr('disabled');
 39 
 40             var me = json.author == author;
 41             var date =  typeof(json.time) == 'string' ? parseInt(json.time) : json.time;
 42             addMessage(json.author, json.text, me ? 'blue' : 'black', new Date());
 43         }
 44     };
 45 
 46     request.onError = function(response) {
 47         content.html($('<p>', { text: 'Sorry, but there is some problem with your '
 48             + 'socket or the server is down' }));
 49     };
 50 
 51     var subSocket = socket.subscribe(request);
 52 
 53     input.keydown(function(e) {
 54         if (e.keyCode === 13) {
 55             var msg = $(this).val();
 56 
 57             // First message is always the author's name
 58             if (author == null) {
 59                 author = msg;
 60             }
 61 
 62             subSocket.push(JSON.stringify({ author: author, message: msg }));
 63             $(this).val('');
 64 
 65             input.attr('disabled', 'disabled');
 66             if (myName === false) {
 67                 myName = msg;
 68             }
 69         }
 70     });
 71 
 72     function addMessage(author, message, color, datetime) {
 73         content.append('<p><span style="color:' + color + '">' + author + '</span> @ ' +
 74             + (datetime.getHours() < 10 ? '0' + datetime.getHours() : datetime.getHours()) + ':'
 75             + (datetime.getMinutes() < 10 ? '0' + datetime.getMinutes() : datetime.getMinutes())
 76             + ': ' + message + '</p>');
 77     }

Line by line explanation:

Line 10 : The first things we need to so is to get a pointer to atmosphere.js main element. For the chat we will call it socket. Under the hood Atmosphere will use the best transport depending on the server you are using. For example, deploying the application on Tomcat 7.0.27 or Jetty 7.5 and up will automatically enable the WebSocket transport.

Line 11 : Here we define the request we want to make. In our case it will looks like http://127.0.0.1:8080/chat with content-type set to JSON. As the preferred transport we will favorite WebSocket and fall back to long-polling in case the server or client isn't supporting WebSocket.

Line 18 : Here we are defining a function that will be invoked when the connection is opened and ready to execute request. The onOpen function will be invoked once the server suspend operation successfully completed.

Line 24 : Here we are defining a function that will be invoked when a Broadcast operation occurs on the server. The JSON message will be parsed (line 27) and displayed on the screen.

Line 46 : here we are defining a function that will be invoked in case an error occur (the connection is dropped, the server goes down, etc.)

Line 51 : Once we are ready, socket.subscribe(request) will connect to the server, and return a subSocket that can be used to push messages back to the server.

Line 62: here we are using the subSocket connection retrieved from the socket). If the browser is using Websocket, the subSocket connection will be the same WebSocket than socket. If long-polling is used, subSocket will use another connection to push message back to the server.

Line 72 : This function display the received message on the screen.

Note that you don't have to care if WebSocket is supported or not. Both client and server will negotiate the best transport (e.g fallback to long-polling) and transparently used it.

That's all

You can look at the entire application here or download it from here

⚠️ **GitHub.com Fallback** ⚠️