Creating a WebSocket-Chat-Application with Jetty and Glassfish
This article describes how to create a simple HTML5 chat application using WebSockets to connect to a Java back-end.
Join the DZone community and get the full member experience.
Join For FreeThis article describes how to create a simple HTML5 chat application using WebSockets to connect to a Java back-end. I decided to include a Jetty 8 and a GlassFish 3.1 example to demonstrate the current approaches for server side WebSocket implementations. While a Servlet can easily be migrated from one Servlet container to an other, a WebSocket implementation can not (at the moment). Even with one server provider I had trouble to run an application with different server versions. I started my tests with Jetty 7.2. Later I updated my Maven dependencies to Jetty 7.4 and some interfaces already changed! To avoid trouble with this article I decided to use the latest available Jetty implementation which is currently Jetty 8 M3. For my GlassFish tests I found a useful resource to dive into GlassFish WebSocket implementation. I attached the source code of both examples, so you can easily test it on your own and use it for further explorations of Java WebSockets.
What this chat application will do:
The chat application we create is as simple as possible. We have one single window with two input fields (1 & 3) and one message output field (2). In the first input field on top (1) you can register your chat name. The output field in the middle (2) is used to display the incoming messages posted by all connected users. The input field at the bottom (3) is your message input field where you can type your message. So this is basically the whole application on client side.
To establish a WebSocket connection we simply create a WebSocket instance on client side that is pointing to our Java WebSocket server location.
var ws;
$(document).ready(
function() {
ws = new WebSocket("ws://localhost:8080/../WebSocketChat");
ws.onopen = function(event) {
}
ws.onmessage = function(event) {
var $textarea = $('#messages');
$textarea.val($textarea.val() + event.data + "\n");
}
ws.onclose = function(event) {
}
});
Listing 1
The URL prefix “ws” defines a default WebSocket connection. To open a secure one, the prefix “wss” is used. The WebSocket API defines mainly three callback methods: onopen, onmessage and onclose. The event argument passed by the methods contains a data field with the payload of the server.
To pass a message to the server we define a small function like this:
function sendMessage() {
var message = $('#username').val() + ":" + $('#message').val();
ws.send(message);
$('#message').val('');
}
Listing 2
Here we simply concatenate the user name and the message and call the WebSocket “send” method to transfer the message to server which is then pushing it to all connected users.
The back-end
So, what do we need on server side to create a running application? First of all we need a servlet to connect your client and secondly one socket per client connected to your chat. The WebSocket protocol defines a HTTP-like handshake which allows the server to interpret the handshake as HTTP and to switch to the WebSocket protocol. Once the servlet associates you to a WebSocket all your chat traffic can be handled through this full duplex connection.
The Jetty Implementation
As mentioned before, the server API differs currently on every server. For the Jetty server you create a WebSocketServlet to have a specified entry point where to associate your WebSocket to the client.
public class WebSocketChatServlet extends WebSocketServlet {
public final Set users = new CopyOnWriteArraySet();
protected void doGet(HttpServletRequest request,
HttpServletResponse response) throws ServletException, IOException {
}
@Override
public WebSocket doWebSocketConnect(HttpServletRequest arg0, String arg1) {
return new ChatWebSocket(users);
}
}
Listing 3
As you can see in Listing 3 the doGet method is not implementing anything. Instead we are using the “doWebSocket” to return a “ChatWebSocket” instance referencing a set with all current users in the chat.
Now one single WebSocket is associated with your client. Once the “doWebSocket” method is called, all following operations in our chat will be performed on our newly created “ChatWebSocket” object.
The users set we passed to the “ChatWebSocket” contains all connected users in our simple chat application. When we receive a client message we will iterate this set and notify every single registered user.
So, let’s have a look at the ChatWebSocket:
public class ChatWebSocket implements OnTextMessage {
private Connection connection;
private Set users;
public ChatWebSocket(Set users ) {
this.users = users;
}
public void onMessage(String data) {
for (ChatWebSocket user : users) {
try {
user.connection.sendMessage(data);
} catch (Exception e) {
}
}
}
@Override
public void onOpen(Connection connection) {
this.connection = connection;
users.add(this);
}
@Override
public void onClose(int closeCode, String message) {
users.remove(this);
}
}
Listing 4
First of all you may see that the “ChatWebSocket” is implementing an “OnTextMessage” interface. This interface extends the WebSocket interface and is one of those changes I had to deal with when jumped from Jetty 7.2 to Jetty 8. Instead of implementing different kinds of “onMessage” methods you decide whether you want to handle text or binary messages. Use “OnBinaryMessage” when you have to deal with binary data and “OnTextMessage” when you create applications like this chat application.
The “onOpen” method on line 18 associates the current connection to the WebSocket object and adds this object to the global set of connected users. The “onClose” method simply removes the user from the set when disconnecting.
The key method in this WebSocket class is “onMessage”. We use it to notify all connected users by iterating on the users set and calling the “connection.send()” method. Remember the client side code where the massage was also sent to the server by calling this method. Now the server pushes the message to each client. Note that no client side polling is involved here, instead of this we have a real full duplex connection and a server push.
The GlassFish implementation
Like the Jetty implementation, GlassFish uses a Servlet to define an entry point, too. But instead of involving a new interface we use an standard HttpServlet and register a WebSocket adapter (ChatApplication) on first initialisation.
public class WebSocketChatServlet extends HttpServlet {
private final ChatApplication app = new ChatApplication();
@Override
public void init(ServletConfig config) throws ServletException {
WebSocketEngine.getEngine().register(app);
}
}Listing 5
public class ChatApplication extends WebSocketApplication {
@Override
public WebSocket createSocket(WebSocketListener... listeners)
throws IOException {
return new ChatSocket(listeners);
}
@Override
public void onMessage(WebSocket socket, DataFrame frame) throws IOException {
final String data = frame.getTextPayload();
for (final WebSocket webSocket : getWebSockets()) {
try {
webSocket.send(data);
} catch (IOException e) {
e.printStackTrace();
}
}
}
}Listing 6
The “WebSocketApplication” class is an adapter, but hiding most of the WebSocket specific code. Extending this class means “overwrite the WebSocket creation and the message handling”. The ChatSocket itself is extending a “BaseServerWebSocket” and does not contain any specific code or methods to override. Instead, the “BaseServerWebSocket” is delegating all “onMessage, onOpen, onClose” calls to the WebSockat adapter (WebSocketApplication).
The “createSocket” method (line 3) in this example is simply associating a socket to a client.
The “onMessage” method in line 9 differs to the Jetty example. While we implemented the WebSocket methods in the previous example we now receive the socket as a parameter. Furthermore we do not define an “onTextMessage” or “onBinaryMessage” interface, we simply use the data frame with the payload.
In line 10 you can see that we call “getTextPaylod” to receive our chat text message. Alternatively you can use the “getBinaryPayload” method which gives you the binary payload similar to the “onBinaryMessage” interface in Jetty.
In listing 4, line 9 (Jetty WebSocket) we iterated on our user set. Here (listing 6) we do not have to handle a self created set because the WebSocketApplication provides already a set of all connected WebSockets (listing 6, line 11). Take this set and notify (webSocket.send(message); line 13) all connected clients.
Running the Jetty example
To run the Jetty example is quite simple. Go to the root folder of the project containing the pom.xml and type mvn clean install jetty:run. This will install all missing dependencies, deploy the web-project, and run the Jetty server. Now you can open the chat application on http://localhost:8080/html5-webapp-jetty/.
Running the GlassFish example
To run the GlassFish example, some additional steps are required. The provided pom.xml in the project root folder creates only a war file (mvn build). The created war file can be found in the target folder of your project. To run it you need an installed GlassFish 3.1 and you have to enable the WebSocket support. To do this, type:
asadmin set configs.config.server-config.network-config.protocols.protocol.http-listener-1.http.websockets-support-enabled=true
Now you can deploy your application and test it on http://localhost:8080/html5-webapp-glassfish/.
Conclusion:
WebSockets are a very promising approach for real-time web applications. The Client API will be standardized by W3C and the WebSockets Protocol by IETF, so you have an open and a potentially wide spread support on client and on server side. But currently the major problem is the missing uniform serverside API. You may risk that your WebSocket application will not run without changes on later server versions. Furthermore you cant switch from one server provider to an other without changing essential parts of your application.
Opinions expressed by DZone contributors are their own.
Comments