Saturday, November 6, 2010

Break the Request-Response Cycle! WebSockets are Coming!

Since the dawn of time, or at least since 1994, web developers have been forced to work within the confines of the request-response cycle. I've seen good developers be totally confused by the strange disconnected nature of the client and server. For over a decade now, we've been looking for ways to overcome this limitation to deliver web applications that are more like "regular" applications. Around the turn of the century, AJAX started to appear. Now the client could send and receive data from the server without having to interrupt the user with a page load. Great, but we still lacked a way for the server to initiate communication with the client. Comet, sometimes called Reverse-AJAX, seemed to be the answer in 2006. But long-polling HTTP requests are not perfect by any means, and the search continued for a better way. Could WebSockets be the ultimate solution to this problem? Let's give them a try and see how it works.

WebSockets (read the official spec here) are HTML5's new method of connecting the server with the client, via an "upgraded" HTTP connection. Basically, the client makes an HTTP request to the server and requests an upgrade to WebSockets. If the server agrees, the socket stays open and both the client and server can send information back and forth any time they want. In a compliant web browser a WebSocket API is available in JavaScript that makes this possible, much like the XMLHttpRequest API does for AJAX. So if you have Chrome, Safari, or Firefox 4, you're all set on the client side. But what about the server?

You'll need a web server that supports WebSockets in order to run an application that makes use of them. I wanted to find a way to use WebSockets with Tomcat, but had no luck. There is a project underway to add WebSockets support to Tomcat, but it is done via an add-on of what is essentially another server. I wanted something a little cleaner, so I went with Jetty 7.2. If you're going to serve a Java application with WebSockets I think Jetty is the way to go. Using Java's NIO, it has supported a highly efficient version of the Bayeux protocol (CometD) for a while now. WebSockets support is the logical next step, and it's nicely integrated. A WebSocketServlet class is available for you to extend which makes life easy. Here's a really simple one I made to test with.
import javax.servlet.http.HttpServletRequest;
import org.eclipse.jetty.websocket.WebSocket;
import org.eclipse.jetty.websocket.WebSocketServlet;

public class WebSocketTestServlet extends WebSocketServlet {

protected WebSocket doWebSocketConnect(HttpServletRequest request,
                                      String protocol) {
 return new TestWebSocket();
}

}
And here's the code for the TestWebSocket class:

import java.io.IOException;
import org.eclipse.jetty.websocket.WebSocket;

public class TestWebSocket implements WebSocket {

private Outbound ob;

public void onConnect(Outbound outbound) {
 this.ob = outbound;
 SocketStorage.getInstance().addSocket(this);
}

public void onDisconnect() {
 SocketStorage.getInstance().removeSocket(this);
}

public void onFragment(boolean arg0, byte arg1, byte[] arg2, int arg3,
  int arg4) {
 // ignore fragments for now
}

public void onMessage(byte arg0, String arg1) {

 System.out.println("--- received string \"" + arg1 + "\"");
 try {
  ob.sendMessage("thanks for the " + arg1);
 } catch (IOException e) {
  e.printStackTrace();
 }

}

public void onMessage(byte arg0, byte[] arg1, int arg2, int arg3) {
 // ignore byte arrays for now
}


}

That looks really simple, right? All you need to do is add a mapping for this servlet in your web.xml and you'll be able to connect via WebSockets to that URL. So how does this work? Well, if a request comes into the servlet with the right upgrade request headers, the request will be handed off to the doWebSocketConnect method instead of the regular doGet or doPost methods. Then it's up to you to return an instance of a class that implements the WebSocket interface. In the WebSocketServlet base class, Jetty will take care of holding on to that instance and making sure it gets it's data from the client. But remember, if you want to be able to send data asynchronously to those clients you need to keep track of that instance yourself somewhere. That's why you'll see the SocketStorage calls in onConnect and onDisconnect. SocketStorage is just a singleton I made that has a set of all the open WebSockets.

It should be obvious what most of the methods in TestWebSocket do. The Outbound instance that is passed in to the onConnect method is what you'll use to send data outbound to the client, so you need to hold on to it. The onMessage method is invoked whenver the client sends some data. In my example, I basically just echo back to the client whatever it sends me. If you were to add another method to TestWebSocket, you could easily expose the ability to send data to the client unrelated to what the client is sending to the server. Just call sendMessage on the Outbound instance and the data will find its way.

What about the client-side code? It's just as simple! Look at this JavaScript for the client:

     var ws;

  function connect() {

      ws = new WebSocket("ws://yourwebsite.com/path/to/websocket/servlet");
      ws.onopen = function() {
          console.log("connection openned");
      }
      ws.onmessage = function(m) {
          console.log(m.data);
      }
      ws.onclose = function() {
          console.log("connection closed");
      }

  }

  function sendMessage(msg) {
   ws.send(msg);
  }
Note the new protocol, "ws://", in the URL given to the WebSocket constructor. If you want secure WebSockets, "wss://" is also available. By default, the WebSocket ports are 80 and 443 (non-secure and secure, respectively) the same as HTTP.

So obviously my example here doesn't do much of anything. It was just a little test I put together to see how WebSockets work in real life. But now that we have this capability, what will we do with it? The common examples people always give are chat applications, stock alerts, things like that. But I'm thinking that there's a really unique idea out there just waiting to be built using new HTML5 technologies like this. I, for one, intend to use it to make some multi-player frog games.

2011 should be the year of the WebSocket. With Safari and Chrome already supporting them and Firefox 4 due out the beginning of the year, they'll be available in a huge percentage of clients. It looks like IE9 won't support them, but IE's market share will hopefully continue to decline. What I'd really like to see is some kind of Java standard approach for the server side. Jetty's implementation seems pretty good, so hopefully sometime next year Tomcat adds support for WebSockets in a similar fashion. I'd be happy to work on adding that if anyone asked me to...

No comments:

Post a Comment