This 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 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 Later I updated my
Maven dependencies to Jetty 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 M 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.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 2 to Jetty 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) throwsIOException {
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 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-http.websockets-support-enabled=true
Now you can deploy your application and test it on http://localhost:8080/html5-webapp-glassfish/.
asadmin set configs.config.server-config.network-config.protocols.protocol.http-listener-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.
No comments:
Post a Comment