Thursday, 9 May 2013

HTML5 Websockets


Situation: Walk-in interview

HTTP way of interview process

John and Tom are candidates.
John to office staff:"Any openings for senior software engineers?"
Tom to office staff: "I want to attend interview for core Java openings."
No reply from office staff to both of them.
John and Tom repeats their question after each 5 minutes.
After sixth round of repetition, staff responds to John: Yes, we have. Please attend interview.
Again 8th round of repetition, staff responds to Tom: Sorry, core Java openings are closed.
After interview, John asks staff: What is the status?
No reply from staff.
He repeats this question after each 5 minutes.
After 4th round of repetition, staff responds to John: John, you have one more round of interview.

How do you feel? Irritated with staff’s response? Have you faced similar issue anywhere else? If not, think about your own web applications..

Now, websocket’s way of interview process

John to staff: "Any openings for senior software engineers"?
Staff: Will get back to you if we have any. 
After sometime he said to John that there are openings and he directs John to interview panel and John get feedback from panel to attend another round of interview.
Tom to staff: "I want to attend interview for core Java openings."
Staff: Sorry, currently there are no openings.
Isn’t cool? Staff had to face a very few questions from the candidates whereas candidates got immediate responses.
Okay. Now think about our web applications based on HTTP. As per this protocol, server can communicate to a client only upon a request from the same client. Suppose there are some events happening in server and the result has to be sent across all clients. What to do? Unfortunately nothing to do much. Each client has to request for the result from server without even knowing whether events has started or getting processed or completed. A full duplex communication is required to satisfy these needs.
Long polling was commonly adopted as a method for implementing duplex communications. Websockets were ignored even after its introduction due to lack of browser support. But nowadays Websockets are becoming popular as it is supported by latest version of major browsers. Its main attraction is a 2 way communication between client and server.
W3C websockets specification defines an API that enables Web pages to use the Web Sockets protocol for two-way communication with a remote host.

 Lets start learning Websockets!

Today, I would like to throw some light on a sample application using HTML5 at client side and Tomcat7 at server.

The websocket interface

[Constructor(in DOMString url, in optional DOMString protocol)]
interface WebSocket {
  readonly attribute DOMString URL;

  // ready state
  const unsigned short CONNECTING = 0;
  const unsigned short OPEN = 1;
  const unsigned short CLOSED = 2;
  readonly attribute unsigned short readyState;
  readonly attribute unsigned long bufferedAmount;

  // networking
           attribute Function onopen;
           attribute Function onmessage;
           attribute Function onclose;
  boolean send(in DOMString data);
  void close();
};
WebSocket implements EventTarget;
The WebSocket(url, protocol) constructor takes one or two arguments. The first argument, url, specifies the URL to which to connect. The second, protocol, if present, specifies a sub-protocol that the server must support for the connection to be successful.

WebSocket protocol handshake

To establish a WebSocket connection, the client sends a WebSocket handshake request, and the server sends a WebSocket handshake response, as shown in the following example:
GET /mychat HTTP/1.1
Host: x.y.com
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Key: x3JJHMbDL1EzLkh9GBhXDw==
Sec-WebSocket-Protocol: chat
Sec-WebSocket-Version: 13
Origin: http://y.com
Server response:(Server Architecture)
HTTP/1.1 101 Switching Protocols
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Accept: HSmrc0sMlYUkAGmm5OPpG2HaGWk=
Sec-WebSocket-Protocol: chat
The handshake resembles HTTP, but actually isn’t. It allows the server to interpret part of the handshake request as HTTP, then switch to WebSocket.

Websocket Attributes

  • The URL attribute must return the value that was passed to the constructor.
  • The readyState attribute represents the state of the connection. It can have the following values:
CONNECTING (numeric value 0) : The connection has not yet been established.
OPEN (numeric value 1) : The Web Socket connection is established and communication is possible.
CLOSED (numeric value 2) : The connection has been closed or could not be opened.
When the object is created its readyState must be set to CONNECTING (0).

Websocket Events

  • onopen : Occurs when socket connection is established.
  • onmessage : Occurs upon client receives data from server.
  • onerror : Occurs when there is an error in communication.
  • onclose : Occurs when websocket connection is closed.

Websocket APIs

  • send() : send(data) transmits data to server
  • close() : terminates a connection

Client implementation

Object initialization
var host,socket;
if (window.location.protocol == 'http:') {
 host = 'ws://' + url;
} else {
 host = 'wss://' + url;
}
if ('WebSocket' in window) { 
 socket = new WebSocket(host); 
} else if ('MozWebSocket' in window) { 
 socket = new MozWebSocket(host); 
} else { 
 console.log('Error: WebSocket not supported by this browser.'); 
 return false; 
}
Just like any other specifications, vendors choose their own way of implementation. Bear with it until the specification is finalized. :)

 Event handling

socket.onopen = function () {
 console.log('Info: WebSocket connection opened.');
};
socket.onerror = function () {
 console.log('Info: An error occured in WebSocket connection.');
};
socket.onclose = function () {
 console.log('Info: WebSocket closed.');
};
socket.onmessage = function (message) {
 //process message
};

Data transmit

 socket.send(JSON.stringify(json));
If you work on a json object, please stringify before it is sent.

Server implementation

After a detailed research and analysis, I decided to go ahead with Tomcat-Catalina websocket implementation, accepting the fact that there are several other vendors working on the same subject like NodeJS. In my application, I use NodeJS as a router. It even routes websocket connections and its upgrade requests.

 import java.io.IOException;  
 import java.nio.ByteBuffer;  
 import java.nio.CharBuffer;  
 import java.util.Set;  
 import java.util.concurrent.CopyOnWriteArraySet;  
 import java.util.concurrent.atomic.AtomicInteger;  
 import javax.servlet.http.HttpServletRequest;  
 import org.apache.catalina.websocket.MessageInbound;  
 import org.apache.catalina.websocket.StreamInbound;  
 import org.apache.catalina.websocket.WebSocketServlet;  
 import org.apache.catalina.websocket.WsOutbound;  
 public class CustomServlet extends WebSocketServlet {  
   private final AtomicInteger connectionIds = new AtomicInteger(0);  
   private final Set<CustomMessageInbound> connections = new CopyOnWriteArraySet<>();  
   @Override  
   protected StreamInbound createWebSocketInbound(String subProtocol, HttpServletRequest request) {  
     return new CustomMessageInbound(connectionIds.incrementAndGet());  
   }  
   private final class CustomMessageInbound extends MessageInbound {  
     private final String id;  
     private CustomMessageInbound(int id) {  
       this.id = id+”";  
     }  
     @Override  
     protected void onOpen(WsOutbound outbound) {  
       connections.add(this);  
       String message = id+ ”has joined.”;  
       broadcast(message);  
     }  
     @Override  
     protected void onClose(int status) {  
       connections.remove(this);  
       String message = id+” is terminated!”;  
       broadcast(message);  
     }  
     @Override  
     protected void onBinaryMessage(ByteBuffer message) throws IOException {  
       throw new UnsupportedOperationException(“Binary message not supported.”);  
     }  
     @Override  
     protected void onTextMessage(CharBuffer message) throws IOException {  
       broadcast(message.toString());  
     }  
     private void broadcast(String message) {  
       for (CustomMessageInbound connection : connections) {  
         try {  
           CharBuffer buffer = CharBuffer.wrap(message);  
           connection.getWsOutbound().writeTextMessage(buffer);  
         } catch (IOException ignore) {  
           // Handle error  
         }  
       }  
     }  
   }  

Please note that, you get access to HttpServletRequest only upon opening a connection. You will get an invalid state exception if it is accessed at any other point of time. In the above example, broadcast method, sends message to all clients. You can customize it by keeping a map of userId and connection objects and communicate with a specific client. A group of talented programmers in Chathurangam is currently working on a framework which simplifies the operations and act as a guide to manage your applications well.

Possible issues

This is an experimental technology.Because this technology’s specification has not stabilized, the syntax and behavior of an experimental technology is subject to change in future version of browsers as the spec changes. The most popular question about websockets are its dealings with proxy servers and security vulnerability due to the absence of http headers.
Websocket protocol itself is unaware of proxy servers and firewalls, it provides an HTTP-compatible handshake so that HTTP servers can share their default HTTP(80) and HTTPS(443) ports with a WebSocket server. In general, some proxy servers allows the websocket connections and others do not (especially for unsecured connections). In some cases, additional server configuration required to make it work.

 References

  1. http://www.w3.org/TR/2009/WD-websockets-20091222/
  2. http://tomcat.apache.org/tomcat-7.0-doc/web-socket-howto.html

No comments:

Post a Comment