Archive

Archive for May, 2012

Writing Portable WebSockets Application using Java

Writing Portable WebSockets’ applications using the Java Language can be challenging. Since there is no API standardization yet, several “native” API exists (Play!, Vert.x, Kaazing), you can easily get locked with their API, changes, single vendor etc. The same “problem” happens with well known WebServer: Jetty, Tomcat or GlassFish all have native API making your application not portable. Worse, the same problem happens on the client side: Grizzly, Netty, JWebSocket all have their own API. Not better on the browser side as well: Firefox went with its own API instead of the recommended one (MozWebSocket, fixed in Firefox 12).

So, already giving up? The solution is simple: use the AHC library(client) and the Atmosphere Framework (browser and server). The AHC library adds a thin layer on top of existing WebSockets’ client libraries, using the Netty Framework as default. The Atmosphere Framework allow portable WebSockets across Java based WebServers.

Part I — Writing the Server Component

The Atmosphere Framework works on top of existing native WebSocket implementation and currently supports Jetty, Tomcat, GlassFish and the Netty Framework (the NettoSphere). The Atmosphere Framework allows writing pure WebSockets applications as well as applications supporting the HTML 5 Server Side Events, Long-Polling or HTTP Streaming. Today this blog will only focus on writing WebSockets-only applications. So, no fallback on another transport in case WebSockets aren’t supported, but it is important to note that Atmosphere transparently support that transport’s fallback.

The portable API and easiest way to write pure WebSockets with Atmosphere is called WebSocketHandler, and can be defined as:

public abstract class WebSocketHandler implements WebSocketProtocol {

    public void onByteMessage(WebSocket webSocket, 
                              byte[] data, int offset, int length) {
    }

    public void onTextMessage(WebSocket webSocket, String data) {
    }

    public void onOpen(WebSocket webSocket) {
    }

    public void onClose(WebSocket webSocket) {
    }

    @Override
    public void onError(WebSocket webSocket, 
                        WebSocketProcessor.WebSocketException t) {
    }

For example, let’s say we want to write a simple echo server that broadcast received messages to all connected WebSockets, all we need to do is to extends that class with:

    @Override
    public void onTextMessage(WebSocket webSocket, String message) {
        MetaBroadcaster.getDefault().broadcastTo("/*", messages);
    }

All this code is doing is to broadcast the received messages to all connected client using the MetaBroadcaster utility classes. Since, by default, all connected WebSockets are registered at ‘/*’, no extra code is required. Of course we could have wrote something more complicated (like writing our own WebSocket Protocol), but that’s not the goal of this blog.

Part II — Writing the Browser Component

For the Browser component, let’s use the Atmosphere JQuery Plugin, which supports all browsers and could fallback to another transport in case WebSocket aren’t supported. In the easiest form, all we need to do to receive WebSockets messages is:

   var socket = $.atmosphere;
   var req = new $.atmosphere.AtmosphereRequest();
   req.url = document.location.toString() + '/echo';
   req.transport = "websocket';

   req.onOpen = function(response) {
       alert("WebSocket opened");
   }

   req.onTransportFailure = function(request) {
       alert("This browser or the remote Server doesn't support WebSocket");
   }

   req.onMessage(response) {
      var message = response.responseBody;
      alert("WebSocket Message received: " + message);
   }

   var subSocket = socket.subscribe(request);

Of course a real application will not use the alert call. An interesting callback here is the onTransportFailure, which is called in case the browser or the server isn’t supporting WebSockets. For sending WebSocket’s message:

    subSocket.push("Hello World");

Part III — Writing a Java Client

Now let’s write a portable Java client using the AHC library. As simple as

    AsyncHttpClient c = new AsyncHttpClient();
    WebSocket w = c.prepareGet("ws://127.0.0.1:8080")
                   .execute(new WebSocketUpgradeHandler.Builder().build())
                  .get();
    w.addWebSocketListener(new WebSocketTextListener() {

       public void onMessage(String message) {
         System.out.println("Message Received: " + message);
       }

       public void onOpen(WebSocket websocket) {
         System.out.println("WebSocket Opened");
       }
   }).sendTextMessage("Hello World");

By default the AHC library use the Netty Framework for WebSockets support, but other framework (like the Grizzly Framework) can easily be replaced.

For a more complex, portable WebSockets application, take a look at the Atmosphere Chat sample, which transparently support WebSocket, Server Sides Events, Long-Polling and Streaming. If you are planning to use WebSockets and Java, I strongly recommend you look at Atmosphere instead of using private native API and get stuck on a server/framework forever. For more information, ping me on Twitter or follow the Atmosphere Framework!

Advertisements

Safari’s WebSocket implementation and Java: Problematic!

The current Safari version (~5.1.5 … on OS X and iOS) implements an old version of the WebSockets specifications. This old version can  cause major issues with Java WebServer  in production. Here is some recommendations to workaround Safari. Important note: my observation are based on large deployments using the Atmosphere Framework.

First, let’s take a look at Java WebServers supporting WebSockets

WebServers Version Specification Safari Stability
Tomcat 7.0.27 and up hybi-13 and up NOT SUPPORTED
Jetty 7.0 to 7.4.5 Up to hybi-12 UNSTABLE: Server suffer High CPU when Safari’s WebSocket connection get closed.
Jetty 7.5.x to 7.6.2 Up to hybi-12 UNSTABLE: Server suffer High CPU when Safari’s WebSocket connection get closed.
Jetty 7.5.x to 7.6.2 Up to hybi-13 UNSTABLE: Server suffer High CPU when Safari’s WebSocket connection get closed.
Jetty 8.x to 8.1.2 Up to hybi-13 UNSTABLE: Server suffer High CPU when Safari’s WebSocket connection get closed.
Jetty 7.6.3 All hybi version STABLE
Jetty 8.1.3 All hybi version STABLE
GlassFish 3.1.1 All hybi version UNSTABLE: Suffer many API bugs
GlassFish 3.1.2 All hybi version STABLE
NettoSphere (based on Netty Framework) 1.x All hybi version STABLE

My recommendation is if you need to put a WebSocket application in production, use Jetty 7.6.3 or 8.1.3. GlassFish is also a good server but much more heavyweight to use if you are planning to write pure WebSocket applications. NettoSphere is fairly new and until Atmosphere 1.0.0 is released, I’m not yet recommending it (yet!). Note that the Netty Framework’s WebSocket implementation can be considered a STABLE as well, but to run Atmosphere on top of it you need NettoSphere.

Now if you can’t any of the stable WebServer, you can still use WebSockets. All you need to do is to write a Servlet’s Filter that will detect the WebSocket version and force Safari to downgrade to another “transport” or communication channel. Server Sides Events, long-polling, http streaming, polling or JSONP can then be used to reconnect. You just need to implement the reconnect inside your websocket#onClose function. With Atmosphere JQuery PlugIn, the reconnect is done transparently, e./g no special code needed. The Atmosphere Filter looks like:

    @Override
    public void init(FilterConfig filterConfig) throws ServletException {
        String draft = filterConfig
            .getInitParameter(ApplicationConfig.WEB_SOCKET_BANNED_VERSION);
        if (draft != null) {
            bannedVersion = draft.split(",");
            logger.debug("Blocked WebSocket Draft version {}", draft);
        }
    }

    @Override
    public void doFilter(ServletRequest request, 
                         ServletResponse response, 
                         FilterChain chain) 
        throws IOException, ServletException {

        HttpServletRequest r = HttpServletRequest.class.cast(request);
        if (Utils.webSocketEnabled(r)) {
            int draft =r.getIntHeader("Sec-WebSocket-Version");
            if (draft < 0) {
                draft = r.getIntHeader("Sec-WebSocket-Draft");
            }

            if (bannedVersion != null) {
                for (String s : bannedVersion) {
                    if (Integer.parseInt(s) == draft) {
                       HttpServletResponse.class.cast(response) 
                           .sendError(501, "Websocket protocol not supported"); 
                       return;
                    }
                }
            }
        }
        chain.doFilter(request, response);
    }

So if you aren’t using the Atmosphere Framework, make sure you have some sort of Filter than will block Safari from creating problem.

If you are planning to use WebSocket and Java, I strongly recommend you look at Atmosphere instead of using private native API and get stuck on a server forever. For more information, ping me on Twitter!

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