Getting Started with Meteor, WebSocket and Long Polling - Atmosphere/atmosphere Wiki

It is recommended to start with @ManagedService tutorial before reading this article.

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

Note

This sample uses 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. If you want to use WebSockets, 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 WebSockets Chat Application

Server side

Let's build a really simple chat to demonstrate how simple the Atmosphere Framework is. All we need to do is to write create of Meteor from our Servlet

  1 import org.atmosphere.cpr.AtmosphereResourceEventListenerAdapter;
  2 import org.atmosphere.cpr.BroadcasterFactory;
  3 import org.atmosphere.cpr.DefaultBroadcaster;
  4 import org.atmosphere.cpr.Meteor;
  5 
  6 import javax.servlet.http.HttpServlet;
  7 import javax.servlet.http.HttpServletRequest;
  8 import javax.servlet.http.HttpServletResponse;
  9 import java.io.IOException;
 10 import java.util.Date;
 11 
 12 import static org.atmosphere.cpr.AtmosphereResource.TRANSPORT.LONG_POLLING;
 13 @MeteorService
 14 public class MeteorChat extends HttpServlet {
 15 
 16     @Override
 17     public void doGet(HttpServletRequest req, HttpServletResponse res) throws IOException {
 18         Meteor m = Meteor.build(req).addListener(new AtmosphereResourceEventListenerAdapter());
 19         
 20         m.resumeOnBroadcast(m.transport() == LONG_POLLING ? true : false).suspend(-1);
 21     }   
 22     
 23     @Override
 24     public void doPost(HttpServletRequest req, HttpServletResponse res) throws IOException {
 25         String body = req.getReader().readLine().trim();
 26         // Simple JSON -- Use Jackson for more complex structure
 27         // Message looks like { "author" : "foo", "message" : "bar" }
 28         String author = body.substring(body.indexOf(":") + 2, body.indexOf(",") - 1);
 29         String message = body.substring(body.lastIndexOf(":") + 2, body.length() - 2);
 30
 31         BroadcasterFactory.getDefault().lookup(DefaultBroadcaster.class, "/*").
 32             broadcast(new Data(author, message).toString());
 33     }
 34 
 35     private final static class Data {
 36 
 37         private final String text;
 38         private final String author;
 39 
 40         public Data(String author, String text) {
 41             this.author = author;
 42             this.text = text;
 43         }
 44 
 45 }

Line by line explanation:

Line 18 : Here we just create a Meteor based on the HttpServletRequest, and add a event listeners that will be invoked when the connection gets suspended, resumed, disconnected or when a event gets broadcasted.

Line 20 : One of the main concept of the Atmosphere Framework is a Meteor. A Meteor represents a suspended connection (or live connection) between the client and the server. A Meteor is what needed to suspend/resume and broadcast events. [Meteor.suspend](http://atmosphere.github.com/atmosphere/apidocs/org/atmosphere/cpr/Meteor.html#suspend(long, java.util.concurrent.TimeUnit)) 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 operation. Suspending a connection with HTTP means leaving not writing back anything where with WebSockets it means execute the handshake operation and let the connection open. Here we also use resumeOnBroadcast which will tell the framework to resume the connection after the first event gets broadcasted. We use that API to transparently support Long-Polling and WebSocket transport. The connection will be resumed only if Long-Polling is used.

Line 31 : Use a Broadcaster to broadcast (or publish) the request's body to all suspended connections.

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 18 and the connection suspended.
  2. The client send a POST request with a message. The request will be delivered to line 25 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 n0t 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