Binary WebSockets Messaging with Hessian

From Resin 4.0 Wiki

Jump to: navigation, search

Email-48.pngCookbook-48.png

Contents

Overview

The Resin WebSocket API has two main components:

  • A WebSocketListener to receive messages from the peer
  • A WebSocketContext to start messages and control the connection

It also has a secondary component:

  • A WebSocketServletRequest to upgrade to the websocket connection.

The WebSocketListener

The WebSocketListener is a single-threaded callback class provided by the application developer. When Resin receives a new message, it will callback into the WebSocketListener. The listener will then read the message and when the message is complete, the application will return from the callback.

The message reading is blocking, using the standard InputStream API. This technique is efficient because it avoids extraneous buffering. It is also efficient because most websockets are idle, and when no messages are being delivered, the listener doesn't need a thread.

In the example, we read a Hessian object from the input message and send it back by creating a new binary message.

The example reuses the Hessian2Input and Hessian2Output to show some features of Hessian for high-performance. Hessian is a typed protocol, and sends the fields ahead of the instances (this is typically more compact for large objects.) If you use the initPacket, Hessian will remember the sent classes (or received classes) and not send them again. This means that following messages can be very compact.

Even if you don't use the initPacket, you'll still want to use the HessianFactory for performance because the HessianFactory saves the reflection information for serialization.

HessianEchoListener implements WebSocketListener

import com.caucho.websocket.*;
import com.caucho.hessian.io.*;

public class HessianEchoListener extends AbstractWebSocketListener {
  Hessian2Input _hIn;
  Hessian2Output _hOut;

  HessianEchoListener(HessianFactory factory)
  {
    _hIn = factory.createHessian2Input(null);
    _hOut = factory.createHessian2Output(null);
  }

  @Override
  public void onReadBinary(WebSocketContext context, InputStream is)
    throws IOException
  {
    _hIn.initPacket(is);

    Object obj = _hIn.readObject();

    OutputStream os = context.beginBinaryMessage();
    _hOut.initPacket(os);
    // hOut.setUnshared(true); // further speed for acyclic objects
    _hOut.writeObject(obj);
    _hOut.close();
  }
}

Note: a second technique would create a new Hessian2Input and Hessian2Output for each request, instead of reusing it with the initPacket. When you use initPacket, the Hessian streams clear the object references but *keep* the class references. So if you use initPacket, you further compress your hessian stream by only sending the class information once. If you use initPacket, you must only use it for the existing stream because class references must stay in sync between the reader and the writer. (It would be bad if #4 was qa.MyBean in the writer, but #6 for the reader. If you keep them in sync, both will be #4.)

Upgrading the WebSocket

HessianWebSocketServlet extends GenericServlet

 import com.caucho.websocket.*;
 import java.io.*;

 public class HessianWebSocketServlet extends GenericServlet {
 {
   private HessianFactory _factory = new HessianFactory();

   public void service(ServletRequest request, ServletResponse res)
     throws IOException, ServletException
   {
     WebSocketServletRequest req = (WebSocketServletRequest) request;
   
     req.startWebSocket(new HessianWebSocketListener(factory));
   }
 }
Personal tools
TOOLBOX
LANGUAGES