TomcatExpert

How Apache Tomcat Implemented WebSocket

posted by fhanik on May 1, 2012 07:13 AM

With the Apache Tomcat 7.0.27 release, the Apache Tomcat team introduced a WebSocket implementation. In a previous post, we took a look at what the WebSocket implementation means, including what benefits and limitations they present. Today, we will discuss specifically how WebSocket is implemented in Apache Tomcat 7.

Since WebSocket is a protocol sent over TCP after an initial HTTP handshake, you could effectively implement WebSocket using Tomcat’s Comet implementation. There is a back port to Tomcat 6 suggested that does exactly that with very minor changes.

The Apache Tomcat team however decided to go with a more substantial implementation with changes to the core of Tomcat’s network and protocol implementation. The reason for this was memory and scalability based. If Tomcat can recycle the HttpServletRequest/Response objects after the initial handshake, each WebSocket connection will take up less memory in the Java heap. It also opens up the Tomcat container for other future protocols that utilize the HTTP Upgrade feature.

The WebSocket implementation from an API standpoint is fairly straightforward. You really can only do two things:

  1. Send messages
  2. Receive messages

This means you have to implement a mechanism to send a message based on a specific action in your application and implement a callback listener when a message arrives from a client.

To get yourself familiarized with the best way is to walk through some of the examples within Tomcat. We will demonstrate the Chat example, and dive into the API a little bit deeper.

To get started with WebSocket you will have to extend Tomcat’s WebSocket class.

Step 1 – extend the WebSocketServlet class

public class ChatWebSocketServlet extends WebSocketServlet

Since it’s a servlet, you must map it to a URL 

<servlet>
<servlet-name>wsChat</servlet-name>
<servlet-class>
websocket.chat.ChatWebSocketServlet
<servlet-class>
</servlet> 
<servlet-mapping>
<servlet-name>wsChat</servlet-name>
<url-pattern>/websocket/chat</url-pattern>
</servlet-mapping>

In this step we have touched on several important aspects

  1. Since this is a servlet, Tomcat decides if a HTTP request is a WebSocket request based on the URL that you have mapped to your servlet in web.xml or using annotations.
  2. Your class has to import org.apache.catalina.websocket.WebSocketServlet
  3. You can protect access to this using standard methods (servlet security) or frameworks like Spring Security.
  4. It’s a servlet. Lifecycle events, initialization and request parameters, cookies, session data, all the goodies you already know how to use are available to you.

That’s the easy part, let’s move on to using the actual API to receive messages.

Step 2 – Implementing a message listener

Extending the WebSocketServlet requires you to implement the

protected StreamInbound createWebSocketInbound(String subProtocol) {

return new ChatMessageInbound();

}

method. The StreamInbound class has a few onXXX methods that can be implemented to be notified of events. The two required methods are

protected void onBinaryData(InputStream is);
protected void onTextData(Reader r);

 

WebSocket messages can come in binary form, onBinaryData, or text form, onTextData, and your application may choose to utilize one or both of these methods. Once you’ve created a class that implements both these methods your application is ready to receive messages.

An example implementation would look like this (only text data)

private final class ChatMessageInbound extends MessageInbound {
 
@Override
protected void onBinaryMessage(ByteBuffer message)
throws IOException {
//this application does not expect binary data
throw new UnsupportedOperationException(
"Binary message not supported.");
}
 
@Override
protected void onTextMessage(CharBuffer message) throws IOException {
String msg = message.toString();
//modify the message by adding a timestamp
msg = “(“ + System.currentTimeMillis()+”) “+ msg;
broadcast(msg);
}
 
private void broadcast(String message) {
//write some code to process the message
}
 
 
 

If you wish to be notified when a WebSocket connection is opened or closed, simply override the onOpen and onClose methods

@Override
protected void onOpen(WsOutbound outbound);
 
@Override
protected void onClose(int status);

In the Chat example, these two methods track when Chat clients arrive or leave the chat.

 

Step 3 – Writing data to the client

Writing data is pretty straightforward. Your StreamInbound implementation will have a reference to the sender component, WsOutbound. You simply retrieve it by calling

myStreamInbound.getWsOutbound()

at that point you can send either binary

public void writeBinaryData(int b);
public void writeBinaryMessage(ByteBuffer msgBb);

or textual data to the client

public void writeTextData(char c);
public void writeTextMessage(CharBuffer msgBb);

So why do we have two methods for sending textual and two methods for sending binary data? The answer is one of them is streaming, the other is to send a buffered message. When streaming messages, meaning you are using one of these two methods

public void writeTextData(char c);
public void writeBinaryData(int b);

These methods are mutually exclusive. Don’t call both of these methods and expect both binary and textual data to be sent. When you’re done streaming, simply call

public synchronized void flush() throws IOException;

If you previously called writeTextData(char c) you can switch to binary data after calling flush(). Calling flush() completes the WebSocket message. A message can be sent down to the client in multiple frames (or fragments), a frame being a WebSocket defined set of data. When you use the

public void writeTextMessage(CharBuffer msgBb);
public void writeBinaryMessage(ByteBuffer msgBb);

methods each set of data gets transferred as a message.

In the Apache Tomcat WebSocket Chat example, each time a message is received, it broadcasts that message to the other clients as a separate message.

private void broadcast(String message) {
for (ChatMessageInbound connection : connections) {
try {
CharBuffer buffer = CharBuffer. wrap(message); connection.getWsOutbound().writeTextMessage(buffer);
} catch (IOException ignore)
// Ignore
}
     }
}

Step 4 – Closing the connection to the client

The one additional piece of important information is that there are two ways a channel can close in both a “clean” and a “not clean” manner. Clean implies that a handshake has been completed over TCP, using the

public synchronized void close(int status, ByteBuffer data)

method when initiated on the server. Not clean closure means that the TCP connection was disconnected or aborted prior to the close handshake taking place. To be notified of when a channel has been closed by the client or uncleanly, you override with:

@Override
protected void onClose(int status);

Summary

Tomcat’s WebSocket implementation utilizes servlets as the URL based endpoint for WebSocket communications and can utilize all the features that are available within a servlet. A developer implementing a WebSocket application can receive and send messages in the org.apache.catalina.websocket.StreamInbound and org.apache.catalina.websocket.WsOutbound classes. Messages can be streamed or sent as entire messages using the writeTextMessage, writeTextData, writeBinaryMessage, writeBinaryData methods. The first released version of WebSocket in Apache Tomcat was released with Apache Tomcat 7.0.27 and the above Chat example can be found in the webapps/examples/websocket folder of the Apache Tomcat installation.

We encourage Tomcat users to try it out, and if you have feedback or issues please address them on the official Apache Tomcat user mailing lists.

Filip Hanik is a Senior Software Engineer for the SpringSource Division of VMware, Inc. (NYSE: VMW) and a key participant in the company's Apache Tomcat initiatives. Filip brings 15 years of extensive experience in architecture, design and development of distributed application frameworks and containers and is recognized for his top-quality system development skills and continuous participation of Open Source development projects. Filip is an Apache Software Foundation member and a committer to the Apache Tomcat project where he is a leading authority on Tomcat clustering and a key contributor to the core of the platform. Filip has made contributions to software initiatives for Walmart.com, Sony Music, France Telecom and has held a variety of senior software engineering positions with technology companies in both the United States and Sweden. He received his education at Chalmers University of Technology in Gothenburg, Sweden where he majored in Computer Science and Computer Engineering.

Comments

"connections" variable

In method broadcast() in Step3 above you loop on a variable called "connections" - where do you get this from? Thanks!

Paste this and GO! in you

Paste this and GO! in you browser while tomcat is running: http://localhost:8084(change to your port)/examples/websocket/

found

Ok found the full-servlet code! Awesome stuff, well done!

broadcasting a message through a specific websocket in java

Thanks for the tutorial. I got my app to work but I need help with retrieving an object of a specific websocket so that i can brodcast a message to it, within a java Spring Controller for example:
I have three sockets that are created by url-parttened in the web.xml as
/one
/two
/three
NB: these are in three different servlets
This means that I would have 3 websockets that users have choice to connect to. Also note that I am using Spring MVC 3.

My Question: When a user posts a form that has data going to the database & then upon successfully persisting the data, I would want to use a Controller to broadcast the message to websocket "one" and not the other two. How do I achieve that?

Your assistence is well appreciated. :)

How do you access the request parameters ?

I can only see the reference of HttpServletRequest in createWebSocketInbound. How can I access request parameters in the onTextMessage event ?

Post new comment

CAPTCHA
This question is for testing whether you are a human visitor and to prevent automated spam submissions.