Marrying Socket.IO Client with Java EE 7’s WebSocket

just_marriedSocket.IO is a JavaScript library that uses a variety of transport (WebSocket, XHR polling, etc) to allow you to develop real-time web application. Socket.IO hides the underlying transport mechanism by figuring out what is the best transport to use between the browser and the server. It performs a handshake to determine this. Most Socket.IO servers are written in JavaScript running in node.js.

Java EE 7 was released recently. One of the key themes of EE 7 is better support for HTML 5 application. The support for modern web application started in Java EE 6 with the inclusion of RESTful web services. In EE 7, the powers that be added WebSocket and JSON.

I was recently learning and mucking around with Socket.IO. A thought struck me; can I connect a Socket.IO client to a Java EE server? I did a little investigation; I found 2 server side Java implementation of Socket.IO. They are netty-socketio and Socket.IO-Java. While both are excellent implementations and have been used in production, they are tied to specific servers. netty-socketio is based on Red Hat’s async IO framework called Netty and Socket.IO-Java runs on top of Jetty leveraging its WebSocket and continuation support.

I wanted something cross platform using standard API and  more importantly, for me at least, to use Java EE’s programming model (what I’m familiar with) and utilize its resources (JPA, EJB, etc).

The following is a sort of step by step guide on how you integrate a Socket.IO client to Java EE 7 using the new WebSocket API (JSR-356). What I’m doing here is based on looking at Socket.IO-Java sources.

Something Old

When a Socket.IO client connects, it does 2 things

  • Sends a HTTP GET to /socketio to download the socket.io.js JavaScript file. This is the result of <script>
  • Sends a second HTTP GET to /socketio/1/ for the handshake. The server should respond with a colon delimited string comprising of the session id, heart beat timeout, default timeout and a list of supported transport separated by comma. Eg 123456:15000:10000:websocket,xhr-polling

The easiest way to handle the handshake part is to use Servlet and that has to be mapped to /socket.io/*. Override doGet() and doPost() to handle the client request.

//Bare minimum, no error checking

@WebServlet(urlPatterns={“/socket.io/*”})

public class SocketIOServlet extends HttpServlet {

   private static final String CONFIG = “:15000:10000:websocket”;

   @Override

   protected void doGet(HttpServletRequest req, HttpServletResponse resp)

         throws IOException, ServletException {

      serve(req, resp);

   }

   @Override

   protected void doPost(HttpServletRequest req, HttpServletResponse resp)

         throws IOException, ServletException {

      serve(req, resp);

   }

   private void serve(HttpServletRequest req, HttpServletResponse resp)

         throws IOException, ServletException {

      String path = req.getPathInfo().trim();

      path = path.startsWith(“/”)? path.substring(1): path;

      String[] paths = path.split(“/”);

      //GET /socket.io/socket.io.js – returns the socket.io.js library

      if (“GET”.equals(req.getMethod()) && “socket.io.js”.equals(path[0])) {

         resp.setContentType(“text/javascript”);

         //Copy socket.io.js back to client, then return, not shown

        ….

      } else if (parts.length <= 2) {

         //Return handshake

         try (PrintWriter pw = resp.getWriter()) {

            pw.print(req.getSession().getId() + CONFIG);

         }

      }

   }

}

The important thing to note is that in the handshake back to the client, we indicated that we only support WebSocket. See CONFIG constant.

Something New

Once the handshake is successful, the client will now attempt to communicate with us using WebSocket. WebSocket is the newest API in the EE 7 stack. Its very similar, in terms of programming style, to JAX-RS. See here for a comprehensive WebSocket tutorial.

To communicate with the client (Socket.IO), the WebSocket end point must be mapped to /socket.io/protocol_version/websocket/sessionId eg /socket.io/1/websocket/123456. The session id is the variable part of the path so we need to create an endpoint with path parameter to match that but we will not be using it unless you are interested in specific clients.

@ServerEndpoint(value=”/socket.io/1/websocket/{sessionId}”)

public class SocketIOWebSocketHandler {

   @OnOpen

   public void open(Session session) {

      session.getBasicRemote().sendObject(connect());

   }

   @OnClose

   public void close(Session session) {

      session.getBasicRemote().sendObject(close());

      session.close();

   }

   @OnMessage

   public void message(Session session, String msg) {

      System.out.println(msg);

   }

}

So if you look at handling WebSocket transport with the client, its actually quite straightforward. The part that is might be a little confusing is why sendObject()? And what is in connect() and close(). Another question might be the format of msg.

 

Something Borrowed

Socket.IO defines a frame/packet based wire protocol that it uses for all communications between the 2 ends. A typical frame looks something like this

   3:::hello world

So if we were to receive some data from the client and print that out, we will see something similar to above.

As I’ve mentioned at the start of this blog, the code here is largely based on studying Socket.IO-Java sources. Instead of writing my own parser for the Socket.IO frames, I’m going to just use Socket.IO-Java’s. The class in question is SocketIOFrame. Its quite easy to use.

To parse a message from the client

List<SocketIOFrame> frames = SocketIOFrame.parse(msg);

SocketIOFrame has the following methods to identify the frame

  • getFrameType() – whether its a data frame or control frames
  • getMessageType() – text or JSON message
  • getData() – the data

So message() method above can be written as follows (we’ll only be covering the message)

@OnMessage

public void message(Session session, String msg) {

   for (SocketIOFrame f: SocketIOFrame.parse(msg))

      switch (f.getFrameType()) {

            …

         case EVENT:

         case JSON_MESSAGE:

         case MESSAGE:

      }

}

Lets look at how a Socket.IO client sends each of these message type and how we handle it on the server. Assume that socket is a Javascript Socket.IO socket.

Client

socket.send(“hello world”);

Server

case MESSAGE:

   System.out.println(f.getData());

Client

socket.json.send({message: “hello world”})

Server

case JSON_MESSAGE:

   StringReader data = new StringReader(f.getData());

   JsonReader reader = Json.createReader(data);

   JsonObject jsonData = reader.readObject();

   System.out.println(jsonData.getString(“message”));

Client

socket.emit(“greetings”, {message: “hello world”});

Server

case EVENT:

   StringReader data = new StringReader(f.getData());

   JsonReader reader = Json.createReader(data);

   JsonObject jsonData = reader.readObject();

   String eventName = jsonData.getString(“name”); // greetings

   JsonArray payload = jsonData.getJsonArray(“args”); // { … }

When you are sending data back to the client, the message have to be framed as well. Assume we have the following message handlers on the client

socket.on(“message”, function(data) {

   console.log(“MESSAGE: “ + data);

});

socket.on(“greetings”, function(data) {

   console.log(“greetings: “ + data);

});

The following shows how to send message to each of the above handlers

Sending as text

SocketIOFrame f = new SocketIOFrame(

      SocketIOFrame.FrameType.MESSAGE

      , SocketIOFrame.TEXT_MESSAGE_TYPE, message);

session.getBasicRemote().sendObject(f);

Sending as JSON

StringWriter w = new StringWriter();

try (JsonWriter writer = Json.createWriter()) {

   writer.write(json);

}

SocketIOFrame f = new SocketIOFrame(

      SocketIOFrame.FrameType.JSON_MESSAGE

      , SocketIOFrame.JSON_MESSAGE_TYPE, w.toString());

session.getBasicRemote().sendObject(f);

Sending as custom event

JsonArray array = Json.createArrayBuilder()

      .add(/* add data as JsonObject to array */).build();

JsonObject json = Json.createObjectBuilder()

      .add(“name”, “greetings”)

      .add(“args”, array).build();

StringWriter w = new StringWriter();

try (JsonWriter writer = Json.createWriter()) {

   writer.write(json);

}

SocketIOFrame f = new SocketIOFrame(

      SocketIOFrame.FrameType.EVENT

      , SocketIOFrame.JSON_MESSAGE_TYPE, w.toString());

session.getBasicRemote().sendObject(f);

A connect SocketIO frame (connect() method) is created like so

new SocketIOFrame(FrameType.CONNECT

      , SocketIOFrame.TEXT_MESSAGE_TYPE, “”)

and close SocketIO frame (close() method) is

new SocketIOFrame(FrameType.CLOSE

      , SocketIOFrame.TEXT_MESSAGE_TYPE, “server”)

Something Blue

The Socket.IO client makes the following assumption when communicating with the server

  • All communication endpoint are at socket.io viz. when you do io.connect(“http://myserver:8080”), it automatically look for the server side end point to be at http://myserver:8080/socket.io
  • All data send between the client and the server are enclosed in a frame defined by Socket.IO library
  • Following from the first point, the client always talks to the same end point. In the above example, all data are directed to http://myserver:8080/socket.io. So the handler must look at the data and dispatch to appropriate handlers. It would be nice if Socket.IO can use end points to discriminate the message handlers eg.  http://myserver:8080/socket.io/news

Lets address each of the above issues. When you deploy a Java EE web application, you always deploy it under an application context. Lets say the web application uses jsocketio as the application context, then you can use the resource option to remap the endpoint.

By default if you do the following

var socket = io.connect(“http://localhost:8080”);

socket.io will look for the end point at http://localhost:8080/socket.io as discussed above. To include the web application context, use the resource option

var socket = io.connect(“http://localhost:8080”

      { resource: “jsocketio/socket.io”});

See here for a detailed description of other options.

The workaround for SocketIO frames can handle using WebSocket’s encoders and decoders.

An incoming message first be passed to a decoder which converts the messages into a List<SocketIOFrame>. We then inject this list into the @OnMessage method. Note that in the above example we perform the decoding ourself. We can actually delegate this to decoders.

For outgoing messages, we have to create SocketIOFrame. Then use sendObject() to send it back to the client. The SocketIOFrame encoder will intercept this object and convert it to a frame.

public class SocketIOFrameDecoder

      implements Decoder.Text<List<SocketIOFrame>> {

   @Override

   public List<SocketIOFrame> decode(String value)

         throws DecodeException {

      return (SocketIOFrame.parse(value));

   }

   @Override

   public boolean willDecode(String value) { return (true); }

   …

}

public class SocketIOFrameEncoder

      implements Encoder.Text<SocketIOFrame> {

   @Override

   public String encode(SocketIOFrame frame)

         throws EncodeException {

      return (frame.encode());

   }

   …

}

We now need to associate the encoder/decoder pair with SocketIOWebSocketHandler

@ServerEndpoint(value=”/socket.io/1/websocket/{sessionId}”

      , encoders = { SocketIOFrameEncoder.class}

      , decoders = {SocketIOFrameDecoder.class}

)

public class SocketIOWebSocketHandler {

   @OnMessage

   public void message(Session session, List<SocketIOFrame> frames) {

      //Process inbound messages

      for (SocketIOFrame f: frames)

         //Do something

      …

      //Send reply back

      SocketIOFrame sioFrame = new SocketIOFrame(…);

      session.getBasicRemote().sendObject(sioFrame);

   }

You can find more details on encoders and decoders here.

The third and final caveat when working with a Socket.IO client is that all messages are directed at the same endpoint URL. If you look at how Socket.IO is used with node.js, you’ll notice that you can attach handlers to events. I’ve talked about this above so I won’t be repeating myself here. It would be nice to be able to map event names, in particular those send by using emit(), to WebSocket handlers.

 

I’m currently working on a simple framework so I’ll post the complete code when I complete the framework.

Everything that I’ve described I’ve tested with Chrome, Glassfish 4 and NetBeans 7.4 beta. For some strange reason, Socket.IO client on refuses to use WebSocket on Firefox.

Till next time.