Monday, July 15, 2013

HTML5 WebSocket Sample with Java Servlet on Server Side


Document  Version 1.0

Copyright © 2012-2013 beijing.beijing.012@gmail.com


Beside Server-Sent Events, WebSocket is an another important feature introduced by HTML5 for browser to update web page content automatically from server.  The most important differences between WebSocket and Server-Sent Events are:  

  • Server-Sent Events is one way communication whereas WebSocket use full-duplex channel.
  • Server-Sent Event is HTTP,  WebSocket introduced new protocol "ws" protocol

Here I am  not going compare WebSocket and Server-Sent Events in detail, I will just show  you a simple WebSecket sample to get a feeling.


In the example below, browser opens a window, send text message to server. Server just send message back to browser, shown in the window. (by the way, the sample could be extended with small effort to work as a chat application ...)


The client is just a html page with embedded javascript. The Server is a Java Servlet web application running on Tomcat 7.0.29. (WebSocket, "ws" protocol support with Tomcat since version 7.0.24 ).



1. Create a simple web application "WebSocketSample"


User your favorite IDE (I use Eclipse) to create a Web Application. Be sure to use servlet 3.x style.  
The sample web application consists of :
  • two java classes, which are the server side code
  • one "html" page which is the client


2.  MyWebSocketServlet.java



The sample uses Tomcat implementation of of "WebSocket", so we need to have "catalina.jar" on class path.
Add servlet-api.jar to class path as well.


package sample;

import java.util.concurrent.ConcurrentHashMap;

import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpSession;

import org.apache.catalina.websocket.StreamInbound;
import org.apache.catalina.websocket.WebSocketServlet;

/**
 * WebSocketServlet is contained in catalina.jar. It also needs servlet-api.jar
 * on build path
 *
 * @author wangs
 *
 */
@WebServlet("/wsocket")
public class MyWebSocketServlet extends WebSocketServlet {

private static final long serialVersionUID = 1L;

// for new clients, <sessionId, streamInBound>
private static ConcurrentHashMap<String, StreamInbound> clients = new ConcurrentHashMap<String, StreamInbound>();

@Override
protected StreamInbound createWebSocketInbound(String protocol,
HttpServletRequest httpServletRequest) {

// Check if exists
HttpSession session = httpServletRequest.getSession();

// find client
StreamInbound client = clients.get(session.getId());
if (null != client) {
return client;

} else {
client = new MyInBound(httpServletRequest);
clients.put(session.getId(), client);
}

return client;
}

public StreamInbound getClient(String sessionId) {
return clients.get(sessionId);
}

public void addClient(String sessionId, StreamInbound streamInBound) {
clients.put(sessionId, streamInBound);
}
}




3.  MyInbund.java


Since Tomcat's WsOutbound has indirect dependency on "tomcat-koyote.jar", please add this jar to class path to resolve compile error like "the hierarchy of the type ... is inconsistent ..."


package sample;

import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.CharBuffer;

import javax.servlet.http.HttpServletRequest;

import org.apache.catalina.websocket.MessageInbound;
import org.apache.catalina.websocket.WsOutbound;

/**
 * Need tomcat-koyote.jar on class path, otherwise has compile error "the hierarchy of the type ... is inconsistent"
 * @author wangs
 *
 */
public class MyInBound extends MessageInbound{

private String name;

private WsOutbound myoutbound;

public MyInBound(HttpServletRequest httpSerbletRequest) {

}
@Override
public void onOpen(WsOutbound outbound) {
System.out.println("on open..");
this.myoutbound = outbound;
try {
this.myoutbound.writeTextMessage(CharBuffer.wrap("hi, what's your name?"));

} catch (Exception e) {
throw new RuntimeException(e);
}

}

@Override
public void onClose(int status) {
System.out.println("Close client");
//remove from list
}

@Override
protected void onBinaryMessage(ByteBuffer arg0) throws IOException {

}

@Override
protected void onTextMessage(CharBuffer inChar) throws IOException {

System.out.println("Accept msg");
CharBuffer outbuf = CharBuffer.wrap("- " + this.name + " says : ");
CharBuffer buf = CharBuffer.wrap(inChar);

if(name != null) {
this.myoutbound.writeTextMessage(outbuf);
this.myoutbound.writeTextMessage(buf);
} else {
this.name = inChar.toString();

CharBuffer welcome = CharBuffer.wrap("== Welcome " + this.name + "!");
this.myoutbound.writeTextMessage(welcome);
}

this.myoutbound.flush();

}

}




4.  wssockrt.html




<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Tomcat web socket</title>
<script type="text/javascript">
var ws = new WebSocket("ws://localhost:8080/WebSocketSample/wsocket");
ws.onopen = function () {
};

   ws.onmessage = function(message) {
document.getElementById("msgArea").textContent += message.data + "\n";              
   };

   function postToServer() {
ws.send(document.getElementById("msg").value);
document.getElementById("msg").value = "";
}

function closeConnect() {
ws.close();
}
</script>
</head>

<body>
  <div>
<textarea rows="4" cols="100" id="msgArea" readonly></textarea>
</div>
<div>
<input id="msg" type="text"/>
<button type="submit" id="sendButton" onclick="postToServer()">Send</button>
</div>
</body>
</html>


5.  Export ".war" and deploy it on a running Tomcat 



Export the "WebSocketSample.war"and deploy it on a running Tomcat server.

Open the link below with browser:

http://localhost:8080/WebSocketSample/wsocket.html

You will see:



Now type your name and send:

So it woks!


6.  The "WebSocket" Header  


When we take a close look at the headers of the Request and Response above:





We will see, browser is not speaking "http" with server any more,  now it speaks "websocket" protocol.

12 comments:

  1. hi beijing,

    i try this app but then i try to send (form html) there is no result. can not see any print in tomcat log as well. any idea where could be the problem ? how can i trouble shoot this issue ?

    ReplyDelete
    Replies
    1. Hi Haris,

      The reasons might be:
      1. Your browser does not support "ws" protocol. In such cases no request comes to tomcat, so nothing is shown in server.

      2. App is not deployed correctly. Add some log line to make sure servelt is initialized.
      Make sure your are the correct URI.

      3. Your tomcat might not support WebSocket yet, the just try downloading latest tomcat.

      Hope this helps.

      BR.

      Delete
  2. This comment has been removed by the author.

    ReplyDelete
  3. Fantastic...Its working absolutely correct
    My configuration are as:
    apache-tomcat-7.0.37
    FireFox 27.1

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

    ReplyDelete
  5. where can i find "tomcat-koyote.jar" file. i find "tomcat-coyote.jar" in my tomcat installation directory

    ReplyDelete
  6. Works fine in apache-tomcat 7.0.52, jdk 7. Thanks.

    ReplyDelete
  7. Hi.I am facing The hierarchy of the type MyInBound is inconsistent issue in Eclipse envt.
    I think catalina jar version and servlet jar version are conflicting.
    Please share the version of jars/links to download the jar.

    ReplyDelete
    Replies
    1. I am having the same problem. how did you solve it ?

      Delete
  8. i try this app but i seen this error : WebSocket connection to 'ws://localhost:8080/WebSocketSample/wsocket' failed: Error during WebSocket handshake: Unexpected response code: 404

    can you help me?

    ReplyDelete
  9. Hi, please give the web.xml file

    ReplyDelete
  10. Its really an Excellent post. I just stumbled upon your blog and wanted to say that I have really enjoyed reading your blog. Thanks for sharing....

    python training in chennai
    python course in chennai
    python training in bangalore

    ReplyDelete