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

20 comments:

  1. Excellent Tutorial - please provide completed project as a whole download - copy & paste can be error prone. Thank You, Burr

    ReplyDelete
  2. ws travelling . hence the delay ..uploaded on github..

    ReplyDelete
  3. here is the url https://github.com/ajeeshpu/html5-webapp

    ReplyDelete
  4. Good tutorial introduction to Websockets. Keep up the good work !

    ReplyDelete
  5. Hi Ajeesh,

    Looking for people who have 2+yrs exp in java, html5, websockets for Bangalore, kindly help referring. rssridhar17@yahoo.com

    ReplyDelete
  6. It would be great to get an idea, how to set up the required dev infrastructure, I mean do I install netbeans or Eclipse , what plugins etc..

    ReplyDelete
  7. for the example i wrote it with eclipse,jdk.16
    locally installed maven (maven plugin for eclipse),
    html5 compliant web browser (chrome),
    git to download/upload source
    rest is all maven dependencies via internet repos(available in pom,even jetty)-you could choose to install jetty separately as well and deploy the app as a war ..thats all

    ReplyDelete
  8. Please provoide the source code for this tutirial

    ReplyDelete
  9. at the bottom of the post you have the link or again you may find it here
    https://github.com/ajeeshpu/html5-webapp

    ReplyDelete
  10. Hi,

    Thanks for this ready to test project. But, I couldn't get it running :(
    I always have this '...Kaboum...Im gone'
    Everything seems to be fine: jetty 7.2, all plugins, eclipse project built with success with Maven.
    But when deploying the war, kaboum.
    Any ideas what the issue is?
    Thanks.

    ReplyDelete
  11. I just searching this kind of things in search engines. My searching was ending here. Keep up your good work. I bookmarked it for general updates.
    html5 video player

    ReplyDelete
  12. Hi Dear All,
    I am new to web sockets. I want to use web sockets in my eclipse project.
    when i import this project there are imports issues.
    what i have done is
    1) Stand alone Jetty 7.4 with jetty wtp plugin in eclipse.
    2) I have no idea about mevan project.
    Kindly provide me the simplest eclipse with jars included.
    I will be very very thankful to you all.. :)

    ReplyDelete
  13. I tried you tutorial but I have an issue.
    I am on XP, Chrome 17.0.963.66, jetty 7.2.0.v20101020.
    I wrote the class Html5Servlet.java. I did compile with javac cmd by console nad put the class files into webapps\html5-webapp\WEB-INF\classes.
    I wrote the web.xml (in webapps\html5-webapp\WEB-INF) and html5.jsp (in webapps\html5-webapp)

    I did access to http://localhost:8080/html5-webapp/html5.jsp.

    It says unexpected response 405.

    with some log into the console, looks like evt object received by client is evt:[object CloseEvent].
    But in jetty's console, the server never shows as if it has been started...
    I don't have anymore ideas...

    ReplyDelete
  14. Hi!
    Nice example but i have issues. I start the jetty server and the i follow the link "http://localhost:8080/html5-webapp/" in google chrome. All the time I get the message " Not Connected to server" and "...Kaboom...Im gone", so I cannot open a connection with the server. Do you have any ideeas why?

    ReplyDelete
  15. This comment has been removed by the author.

    ReplyDelete
  16. Hey guys, this is due to the updates since when this was first written until now. If you would like to get it running these are the steps I took.

    1. Update the jetty.version in the pom.xml to 8.1.0.v20120127
    2. Update the StockTickerSocket in Html5Servlet to implement OnFrame rather than WebSocket. Note: OnFrame is an extension of WebSocket. The newer version of Jetty provides different flavors of implementations.
    3. Import the new interface methods. onConnect translates to onOpen, onDisconnect translates to onClose, onMessage translates to onFrame exept you will ahve to convert the byte array to a string in order for it to work properly.
    4. Change the Outbound outbound object's type to org.eclipse.jetty.websocket.WebSocket.Connection
    5. rerun jetty:run

    One note i was only able to get it to work in firefox and chrome, not IE or Opera. I think its probably a settings issue on my side.

    Thanks!

    ReplyDelete
  17. This comment has been removed by the author.

    ReplyDelete
  18. HOw to fix .. kabooom ..I am gone problem .. please help

    ReplyDelete
  19. been not watching the post.. apologies
    You could take SlowBump's route or just git pull the latest source. it should work

    ReplyDelete
  20. Summary

    1. Update the jetty.version in the pom.xml to 8.1.0.v20120127

    Code change
    package org.ajeesh.app;

    import java.io.IOException;
    import java.util.ArrayList;
    import java.util.Date;
    import java.util.List;
    import java.util.Timer;
    import java.util.TimerTask;
    import java.util.concurrent.atomic.AtomicInteger;

    import javax.servlet.http.HttpServletRequest;

    import org.eclipse.jetty.websocket.WebSocket;
    import org.eclipse.jetty.websocket.WebSocketServlet;

    public class Html5Servlet extends WebSocketServlet {

    private AtomicInteger index = new AtomicInteger();

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

    /**
    *
    */
    private static final long serialVersionUID = 1L;

    public 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=0){
    connection.close();
    timer.cancel();
    }else{
    sendMessage();

    }
    }

    private void sendMessage() {
    if(connection==null||!connection.isOpen()){
    System.out.println("Connection is closed!!");
    return;
    }
    timer.schedule(new TimerTask() {

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

    }
    }

    ReplyDelete