03 January 2014

Java WebSocket Sample

I have not been updating this blog for some time but I was unsatisfied with my search on Java Websockets topic on the client implementation in Java and configuration so I decided to post one.

There is valuable info on several blogs and presentation slides on Java WebSockets, but generally the samples are about the implementation of server side and websocket client is implemented on html/JavaScript, I wanted a client implementation in Java.

I decided to provide a complete  sample, with  both a Java websocket server and java client together with the html/javascript one.

The sample application is a virtual USD Exchange rate publishing server and the websocket client consumes the new USD rate as it becomes available.

How to Configure:

1. For the server implementation: WebSockets standard is included in JEE7 so you app.server must be supporting JEE7.
WebSphere Liberty 8.5.5 did not run this so I went with GlasssFish 4.0 (build 89)

Just deploy the application as a war or ear file. Since annotation based approach is used no need to configure a web.xml/deployment descriptor.

2. For the Java client implementation: JSE7 does not include WebSockets so you should add necesessary jar files yourself. I used Tyrus as the websocket api implementation.

Use javax.websocket-api.jar and tyrus-standalone-client-1.3.3.jar from https://tyrus.java.net/

3. Java client should have a file named javax.websocket.ContainerProvider in MEFA-INF/services folder with the content org.glassfish.tyrus.client.ClientManager to introduce Tyrus to JSE7 environment as WebSocketContainer provider . (see ServiceLoader API for details)

4. For the html/javascript implementation: IE9 does not support websockets,IE version must be at least 10.
For Firefox and Chrome...they have been supporting websockets for a very long time so it won't be a problem.
see caniuse.com for more info on browser support.

Here is the server application


package websocket.server;

import java.text.DecimalFormat;
import java.util.*;
import java.util.concurrent.*;
import javax.websocket.*;
import javax.websocket.server.*;

@ServerEndpoint("/ratesrv")
public class CustomEndPoint {
private static Queue<Session> queue = new ConcurrentLinkedQueue<Session>();
private static Thread rateThread ;

static
{//rate publisher thread, generates a new value for USD rate every 2 seconds.
rateThread=new Thread(){
public void run() {
DecimalFormat df = new DecimalFormat("#.####");
while(true)
{
double d=2+Math.random();
if(queue!=null)
sendAll("USD Rate: "+df.format(d));
try {
sleep(2000);
} catch (InterruptedException e) {
}
}
};
} ;
rateThread.start();
}

@OnMessage
public void onMessage(Session session, String msg) {
try {
System.out.println("received msg "+msg+" from "+session.getId());
} catch (Exception e) {
e.printStackTrace();
}
}

@OnOpen
public void open(Session session) {
queue.add(session);
System.out.println("New session opened: "+session.getId());
}

@OnError
public void error(Session session, Throwable t) {
queue.remove(session);
System.err.println("Error on session "+session.getId());
}

@OnClose
public void closedConnection(Session session) {
queue.remove(session);
System.out.println("session closed: "+session.getId());
}

private static void sendAll(String msg) {
try {
/* Send updates to all open WebSocket sessions */
ArrayList<Session > closedSessions= new ArrayList<>();
for (Session session : queue) {
if(!session.isOpen())
{
System.err.println("Closed session: "+session.getId());
closedSessions.add(session);
}
else
{
session.getBasicRemote().sendText(msg);
}
}
queue.removeAll(closedSessions);
System.out.println("Sending "+msg+" to "+queue.size()+" clients");
} catch (Throwable e) {
e.printStackTrace();
}
}
}

and here is the Java WebSocket Client:

@ClientEndpoint
public class WSClient  {

  private static Object waitLock = new Object();

    @OnMessage
    public void onMessage(String message) {
       System.out.println("Received msg: "+message);        
    }

  @OnOpen
    public void onOpen(Session p) {
        try {
            p.getBasicRemote().sendText("Session established");           
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

private static void  wait4TerminateSignal()
{
synchronized(waitLock)
{
try {
waitLock.wait();
} catch (InterruptedException e) {
}
}
}

    public static void main(String[] args) {
    WebSocketContainer container=null;
    Session session=null;
try{
container = ContainerProvider.getWebSocketContainer();
//WS1 is the context-root of my web.app 
//ratesrv is the path given in the ServerEndPoint annotation on server implementation
session=container.connectToServer(WSClient.class, URI.create("ws://localhost:8080/WS1/ratesrv"));
wait4TerminateSignal();
} catch (Exception e) {
e.printStackTrace();
}
finally{
if(session!=null){
try {
session.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}

The html/javascript client is:

<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>WebSocket Client</title>
 <script type="text/javascript">

      var wsocket;
      
      function connect() {        
          wsocket = new WebSocket("ws://localhost:8080/WS1/ratesrv");        
          wsocket.onmessage = onMessage;          
      }
           
      function onMessage(evt) {             
         document.getElementById("rate").innerHTML=evt.data;          
      }
      
      window.addEventListener("load", connect, false);
  </script>
</head>
<body>

<table>
<tr>
<td> <label id="rateLbl">Current Rate:</label></td>
<td> <label id="rate">0</label></td>
</tr>
</table>
</body>
</html>

I hope this helps