Thursday, February 24, 2011

A Peek at websockets

Had to write a small technical section on HTML5 WebSockets. Thought it will be easier explained with a full html5 websockets example rather than a mundane here is what you do with blah blah blah. So here i am writing this.
        Before (hmm even now i would say owing to the lack of browser support for html 5 and the non finalized spec) the web was quite unidirectional. The pages could send request to a server and never the other way around. There were quite a few "hacks" (in terms of approach) developed to simulate an async server in terms of short and long polling. Short polling typically done by the client to check in short durations for a data change on a server and on changes of data , the server provides you a response.The long polling on the other hand kept the underlying connection alive till a data change occurred and then after the server would respond.
   Here is where the html5 websockets step in.The server will now be able to push back information to the client. It becomes a full duplex channel of communication between the client and the server.Currently it supports both a secure and a non secure connection.
Enough of the primer let us jump in
(
I selected chrome and jetty for this websockets example. There are many the web would point out to you. My test bed had these
1. Chrome -version 9.0.597.98 (one of the browsers that support html5 AND for awesome tooling and for amazing scripting support and speed tracer and :)...)
2. Jetty -7.2.0.v20101020
3. On ubuntu (just wanted to mention it :) )
we have quite a few browsers like firefox, opera and safari which are compliant with html5 .

)
 I work on java and was quite excited to know that jetty from version 7.1 above has been supporting html5. This was clean for me as jetty would get you up and running on a web app smooth and easy , with maven and a couple of plugins and dependencies configured we are up and running a web app on html 5. So rather than explaining all the blah blah and blah , for the guys who know maven here is the pom i used


<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
  <modelVersion>4.0.0</modelVersion>
  <groupId>org.ajeesh.app</groupId>
  <artifactId>html5-webapp</artifactId>
  <packaging>war</packaging>
  <version>1.0-SNAPSHOT</version>
  <name>html5-webapp Maven Webapp</name>
  <url>http://maven.apache.org</url>
  <description>a project hosting an html5 webapp on jetty</description>
  <build>
    <finalName>html5-webapp</finalName>
    <plugins>
      <plugin>
        <!-- This plugin is needed for the servlet example -->
        <groupId>org.mortbay.jetty</groupId>
        <artifactId>jetty-maven-plugin</artifactId>
        <version>${jetty.version}</version>
        <configuration>
          <scanIntervalSeconds>10</scanIntervalSeconds>
          <webAppConfig>
            <contextPath>/html5-webapp</contextPath>
            <descriptor>${basedir}/src/main/webapp/web.xml</descriptor>
          </webAppConfig>
        </configuration>
      </plugin>
      <plugin>
        <groupId>org.apache.maven.plugins</groupId>
        <artifactId>maven-compiler-plugin</artifactId>
        <configuration>
          <fork>true</fork>
          <meminitial>128m</meminitial>
          <maxmem>512m</maxmem>
          <source>1.6</source>
          <target>1.6</target>
          <minmemory>256m</minmemory>
          <maxmemory>2048m</maxmemory>
        </configuration>
      </plugin>
    </plugins>
  </build>
  <properties>
    <jetty.version>7.2.0.v20101020</jetty.version>
  </properties>


  <dependencies>
    <dependency>
      <groupId>junit</groupId>
      <artifactId>junit</artifactId>
      <version>4.4</version>
      <scope>test</scope>
    </dependency>
    <dependency>
      <groupId>org.eclipse.jetty</groupId>
      <artifactId>jetty-server</artifactId>
      <version>${jetty.version}</version>
    </dependency>
    <dependency>
      <groupId>org.eclipse.jetty</groupId>
      <artifactId>jetty-websocket</artifactId>
      <version>${jetty.version}</version>
    </dependency>
  </dependencies>
</project>


To detail the steps:
What i have tried to build here is a client that requests for stock tickers and their prices and a server which responds to it in every 5 seconds with changed prices. The stock prices again being published back by the server to the client using a timer task to simulate the actual receipt from an external source.
1. Get access to an internet connection or a good repository inside your network.Use them in your maven settings.
2. Use the maven webapp archetype to create a webapp project structure for you maven-archetype-webapp.
3. Once done. Copy the pom provided above (change to your artifact and group id). The pom is actually quite simple. It has enough configurations provided to include the jetty server as a plugin so that we can start jetty with maven with the famous mvn jetty:run.For others who choose to do it in code through a test method can do it their way too.But i kind of took this due to the convenience and this was what i required to explain stuff easily. End of the day the web server start is like any other command.
4. Write an implemenation for your websocketservlet. Well if you can spell it then you can write one. That is how easy it is to write one due to the many examples in jetty and web sources.But the post is supposed to be a one stop shop. So here is the code for that

public class Html5Servlet extends WebSocketServlet {


private AtomicInteger index = new AtomicInteger();


private static final List<String> tickers = new ArrayList<String>();
static{
tickers.add("ajeesh");
tickers.add("peeyu");
tickers.add("kidillan");
tickers.add("entammo");
}


/**

*/
private static final long serialVersionUID = 1L;


protected WebSocket doWebSocketConnect(HttpServletRequest req, String resp) {
System.out.println("On server");
return new StockTickerSocket();
}
protected String getMyJsonTicker(){
StringBuilder start=new StringBuilder("{");
start.append("\"stocks\":[");
int counter=0;
for (String aTicker : tickers) {
counter++;

start.append("{ \"ticker\":\""+aTicker +"\""+","+"\"price\":\""+index.incrementAndGet()+"\" }");
if(counter<tickers.size()){
start.append(",");
}
}
start.append("]");
start.append("}");
return start.toString();
}
public class StockTickerSocket implements WebSocket
{


private Outbound outbound;
Timer timer; 
public void onConnect(Outbound outbound) {
this.outbound=outbound;
timer=new Timer();
}


public void onDisconnect() {
timer.cancel();
}


public void onFragment(boolean arg0, byte arg1, byte[] arg2, int arg3,
int arg4) {
// TODO Auto-generated method stub

}


public void onMessage(final byte frame, String data) {
if(data.indexOf("disconnect")>=0){
outbound.disconnect();
}else{
timer.schedule(new TimerTask() {

@Override
public void run() {
try{
System.out.println("Running task");
outbound.sendMessage(frame,getMyJsonTicker());
}
catch (IOException e) {
e.printStackTrace();
}
}
}, new Date(),5000);


}

}


public void onMessage(byte arg0, byte[] arg1, int arg2, int arg3) {
// TODO Auto-generated method stub

}

}



}
The websocketservlet is quite simple. It creates a stockTickerSocket (an extension of the websocket) which on receipt of a message starts publishing messages every 5 seconds back to the client ,till the client sends a "disconnect" message. The stock prices are mock created and returned like a json string which can be easily evaluated by a html client.
In step 6 there is one provided that takes advantage of the same
 5. Create a web.xml servlet mapping entry for the servlet. I choose hello-html5.
here goes that too

<!DOCTYPE web-app PUBLIC
 "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN"
 "http://java.sun.com/dtd/web-app_2_3.dtd" >


<web-app>
  <display-name>Archetype Created Web Application</display-name>
  <servlet>
    <servlet-name>HelloHtml5</servlet-name>
    <servlet-class>org.ajeesh.app.Html5Servlet</servlet-class>
  </servlet>
  <servlet-mapping>
    <servlet-name>HelloHtml5</servlet-name>
    <url-pattern>/hello-html5/*</url-pattern>
  </servlet-mapping>
</web-app>
6. Create the jsp to make the websocket call.


<html>
<meta charset="utf-8" />
<head>
<title>WebSocket Test</title>
<style type="text/css">
.info {
color: #01529B;
background-color: #BDE5F8;
}
.error {
color: #D8000C;
background-color: #FFBABA;
}
.warning {
color: #9F6000;
background-color: #FEEFB3;
}
.button{
    font: 11px verdana, arial, helvetica, sans-serif;
    border: 1px solid #ccc;
    color: #666;
    font-weight: bold;
    font-size: 10px;
    margin-top: 5px;
    overflow: hidden;
}


</style>
</head>
<body onload="init">
<h3>Hello HTML5 Web Socket</h3>
<input type="button" value="stop" name="stopBtn" class="button" onclick="javascript:stop();"/>
<div id="output">
</div>


<span class="warning">Behold websockets</span>




</body>
<script language="javascript" type="text/javascript">


  var wsUri = "ws://localhost:"+<%=request.getLocalPort()%>+"/html5-webapp/hello-html5";
  var output;


  function init()
  {
    output = document.getElementById("output");
    writeToScreen(" Not Connected to server",'warning');
    testWebSocket();
  }
  function stop()
  {
 websocket.send('disconnect');
  }
  function testWebSocket()
  {
    websocket = new WebSocket(wsUri);
    websocket.onopen = function(evt) { onOpen(evt) };
    websocket.onclose = function(evt) { onClose(evt) };
    websocket.onmessage = function(evt) { onMessage(evt) };
    websocket.onerror = function(evt) { onError(evt) };
  }


  function onOpen(evt)
  {
    writeToScreen("Connected to server");
    doSend("Hello are you there WebSocket Server");
  }


  function onClose(evt)
  {
    writeToScreen("...Kaboom...Im gone",'warning');
  }


  function onMessage(evt)
  {
var evalStocks = eval('(' + evt.data + ')');
var aTable="<table><tr><th>Ticker</th><th>Price</th></tr>";
for(i=0;i<evalStocks.stocks.length;i++){
aTable=aTable+"<tr>";
aTable=aTable+"<td>";
aTable=aTable+evalStocks.stocks[i].ticker;
aTable=aTable+"</td>";
aTable=aTable+"<td>";
aTable=aTable+evalStocks.stocks[i].price;
aTable=aTable+"</td>";
aTable=aTable+"</tr>";
}
aTable=aTable+"</table>";
    writeToScreen(aTable,'info');
  }


  function onError(evt)
  {
 writeToScreen(evt.data,'error');
  }


  function doSend(message)
  {
    writeToScreen("SENT: " + message); 
    websocket.send(message);
  }


  function writeToScreen(message,rule)
  {
    output.innerHTML=message;
    output.className=rule;
  }
  if(window.addEventListener){
 window.addEventListener("load", init, false);
  }else{
 window.attachEvent("onload", init);
  }
  


</script>
</html>
In the jsp provided notice the "ws" prefix for the url indicating a non secure connection. The "wss" is the prefix for a secure connection. The websocket created would evaluate the response and build the table that you see on the client side.(see the picture below for two different responses from server)
7. Now let us run it
for people who have taken my approach - type in mvn jetty:run.. This will cause your application to come up on jetty at 8080 (if you don't like 8080 try the -Djetty.port=9999 option :)  like me).
Now access the url to your page thus http://localhost:9999/html5-webapp/html5.jsp.
(html5-webapp as mentioned in the webappconfig for jetty in our pom would define the context path) and the jsp was html5.jsp 


Well thats it. Just post them your good comments. Will be nice. 

You can access the source for the getting started example for websockets above at github/html5-webapp