TomEE or how to use Websocket with CDI


Websocket is a new very interesting feature. The idea is to allow a bidirectional communication between the browser and the “other side” (let say for this article the server).

How does it work? Can it work with CDI? What about TomEE?

Client side

We’ll start by the client side since it is the simple one (at least for me). This part will be done in javascript since i think that’s the more common usage but other APIs exist.

First we need to connect to the “server”:

ws = new WebSocket(url);

Note: in old firefox you can replace WebSocket by MozWebSocket if it doesn’t exist. That’s why we often see:

if ('WebSocket' in window) {
    ws = new WebSocket(url);
} else if ('MozWebSocket' in window) {
    ws = new MozWebSocket(url);
} else {
    alert('WebSocket is not supported by this browser.');
    return;
}

The url is a “ws” url: ‘ws://localhost:8080/websocket-test-1.0-SNAPSHOT/ping’ for instance.

Once you got your websocket you have to configure its callbacks:

  • onopen: will be called when the connection is opened
  • onmessage(event): called when a message is received. To get the message use event.data
  • onclose: when closing the connection

To send a message simply invoke the method “send” on the websocket giving as parameter the message you want to send:

ws.send('message');

Here my whole client code (in a single html page for the test) highly inspired from Tomcat samples:

<!DOCTYPE html>
<html>
<head>
    <title>Apache TomEE WebSocket Examples</title>
    <style type="text/css">
        #connect-container {
            float: left;
            width: 400px
        }

        #connect-container div {
            padding: 5px;
        }

        #console-container {
            float: left;
            margin-left: 15px;
            width: 400px;
        }

        #console {
            border: 1px solid #CCCCCC;
            border-right-color: #999999;
            border-bottom-color: #999999;
            height: 170px;
            overflow-y: scroll;
            padding: 5px;
            width: 100%;
        }

        #console p {
            padding: 0;
            margin: 0;
        }
    </style>
    <script type="text/javascript">
        var ws = null;

        function setConnected(connected) {
            document.getElementById('connect').disabled = connected;
            document.getElementById('disconnect').disabled = !connected;
            document.getElementById('echo').disabled = !connected;
        }

        function connect() {
            if ('WebSocket' in window) {
                ws = new WebSocket('ws://localhost:8080/websocket-test-1.0-SNAPSHOT/ping');
            } else if ('MozWebSocket' in window) {
                ws = new MozWebSocket('ws://localhost:8080/websocket-test-1.0-SNAPSHOT/ping');
            } else {
                alert('WebSocket is not supported by this browser.');
                return;
            }
            ws.onopen = function () {
                setConnected(true);
                log('Info: WebSocket connection opened.');
            };
            ws.onmessage = function (event) {
                log('Received: ' + event.data);
            };
            ws.onclose = function () {
                setConnected(false);
                log('Info: WebSocket connection closed.');
            };
        }

        function disconnect() {
            if (ws != null) {
                ws.close();
                ws = null;
            }
            setConnected(false);
        }

        function echo() {
            if (ws != null) {
                var message = document.getElementById('message').value;
                log('Sent: ' + message);
                ws.send(message);
            } else {
                alert('WebSocket connection not established, please connect.');
            }
        }

        function log(message) {
            var console = document.getElementById('console');
            var p = document.createElement('p');
            p.style.wordWrap = 'break-word';
            p.appendChild(document.createTextNode(message));
            console.appendChild(p);
            while (console.childNodes.length > 25) {
                console.removeChild(console.firstChild);
            }
            console.scrollTop = console.scrollHeight;
        }
    </script>
</head>
<body>
    <noscript>
        <h2 style="color: #ff0000">
            Seems your browser doesn't support Javascript! Websockets rely on Javascript being enabled.
            Please enable Javascript and reload this page!
        </h2>
    </noscript>
<div>
    <div id="connect-container">
        <div>
            <textarea id="message" style="width: 350px">Here is a message!</textarea>
        </div>
        <div>
            <button id="connect" onclick="connect();">Connect</button>
            <button id="disconnect" disabled="disabled" onclick="disconnect();">Disconnect</button>
            <button id="echo" onclick="echo();" disabled="disabled">Echo message</button>
        </div>
    </div>
    <div id="console-container">
        <div id="console"></div>
    </div>
</div>
</body>
</html>

Server side (java)

Now we have to put something on our server to answer the client.

There is today no standard websocket API in java so that’s often container/library dependent. Here what is interesting in TomEE is you don’t need any particular library, all is in the container.

To develop the server side you’ll need to add some unusual dependencies: tomcat-catalina and tomcat-coyote. If you use maven do:

<dependency>
  <groupId>org.apache.tomcat</groupId>
  <artifactId>tomcat-catalina</artifactId>
  <version>7.0.27</version>
  <scope>provided</scope>
</dependency>
<dependency>
  <groupId>org.apache.tomcat</groupId>
  <artifactId>tomcat-coyote</artifactId>
  <version>7.0.27</version>
  <scope>provided</scope>
</dependency>

To create the server part we’ll simply create a servlet inheriting from org.apache.catalina.websocket.WebSocketServlet class.
It will override createWebSocketInbound(String) method to return a custom org.apache.catalina.websocket.MessageInbound. That’s in this message inbound that we will process the message coming from the client. The method to implement is onTextMessage(CharBuffer) (another exist for binary data, see onBinaryMessage(ByteBuffer)).

To implement the answer simply get the outbut side using getWsOutbound() method and write something using writeTextMessage(CharBuffer).

Maybe it looks theorical so here is a sample:

@WebServlet(urlPatterns = "/ping")
public class PongWebsocket extends WebSocketServlet {
    @Override
    protected StreamInbound createWebSocketInbound(final String s) {
        return new PongInBound();
    }

    protected static class PongInBound extends MessageInbound {
        @Override
        protected void onBinaryMessage(final ByteBuffer byteBuffer) throws IOException {
            getWsOutbound().writeBinaryMessage(byteBuffer);
        }

        @Override
        protected void onTextMessage(final CharBuffer charBuffer) throws IOException {
            getWsOutbound().writeTextMessage(CharBuffer.wrap("PONG"));
        }
    }
}

This servlet will simply return “PONG” to the client.

So here we are, each time the client will ask for something we’ll answer PONG.

That’s not bad but what about real applications? What’s about CDI?

The WebSocketServlet is a servlet so you should be able to get injections easily. First i add an empty beans.xml in WEB-INF. Then i create a message processor:

public class MessageProcessor {
    public String process(final String message) {
        return StringUtils.reverse(message); // anything useful
    }
}

And finally i update my websocket servlet to use the processor provided by CDI. I prefered to keep the processing in the servlet rather than in the message inbound to ease the usage of CDI scopes so my message inbound simply delegates to the servlet:

@WebServlet(urlPatterns = "/ping")
public class PongWebsocket extends WebSocketServlet {
    @Inject
    private MessageProcessor processor;

    @Override
    protected StreamInbound createWebSocketInbound(final String s) {
        return new PongInBound(this);
    }

    protected CharBuffer process(CharBuffer charBuffer) {
        final String reversed = processor.process(new String(charBuffer.array()));
        return CharBuffer.wrap(reversed);
    }

    protected static class PongInBound extends MessageInbound {
        private PongWebsocket servlet;

        public PongInBound(PongWebsocket pongWebsocket) {
            servlet = pongWebsocket;
        }

        @Override
        protected void onBinaryMessage(final ByteBuffer byteBuffer) throws IOException {
            getWsOutbound().writeBinaryMessage(byteBuffer);
        }

        @Override
        protected void onTextMessage(final CharBuffer charBuffer) throws IOException {
            getWsOutbound().writeTextMessage(servlet.process(charBuffer));
        }
    }
}

Nothing really complicated and it works:

Have fun with TomEE 😉

Advertisements

2 thoughts on “TomEE or how to use Websocket with CDI

  1. Terminix locations

    Hmm is anyone else experiencing problems with the pictures
    on this blog loading? I’m trying to figure out if its a problem on my end or if it’s the
    blog. Any responses would be greatly appreciated.

    Reply

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s