Http has been around since 1999, but the modern web applications‘ increasing demand for server push and a more efficient communication protocol, led to the definition of the Websocket protocol in 2011.
The questions asked at the Java Conference 2014 in Cluj were symptomatic for the new technologies:
This article advertises Websockets and gives an overview of how to use them and the reasons, why you should use them.
Websocket security is a major topic, but cannot be covered in this article. See Useful Links for further information.
Websocket implementations share a common interface definition between server, client and even across programming languages. The following code examples will implement a basic chat example by connecting both, a Java and a Javascript client to a Java server.
Use the following maven dependency in your Java server and client projects to include the JSR-356 API for Java Websockets:
<dependency>
<groupId>javax.websocket</groupId>
<artifactId>javax.websocket-api</artifactId>
<version>1.1</version>
</dependency>
First, we create the server, by using the annotated ServerEndpoint defined in JSR-356. There’s also a specification for programmatic endpoint creation, but that won’t be covered in this article.
Code first, explanations below:
@ServerEndpoint(value = "/service")
public class ChatServerEndpoint {
private static final Set connections =
new CopyOnWriteArraySet<>();
private Session session;
@OnOpen
public void start(Session session) {
this.session = session;
connections.add(this);
}
}
The value property oft he ServerEndpoint defines, on which path this
endpoint listens. The URL for connecting to this endpoint is
ws://
.
The @OnOpen
annotation marks the start method to be called, when a
client connects. You should do all the initialization, that‘s necessary.
In the chat example, the connecting client’s session gets stored into
the connections set for later use inside the broadcast method.
What happens, if a client disconnects? Define a method, annotate it with
the @OnClose
annotation and it gets called everytime a client
disconnects, regardless of the cause:
@OnClose
public void end() {
connections.remove(this);
}
In this simple chat example, the disconnected client is removed from the connections set.
The core of any data driven application implementing Websockets
is
sending and receiving data. The most basic solution for receiving
messages in an JSR Endpoint is annotating a method with the @OnMessage
annotation and the following signature:
@OnMessage
public void incoming(String message) {
broadcast(message);
}
There are more sophisticated ways of receiving (and sending) messages using encoders and decoders with text and binary messages, but they won’t be covered in this article. Have a look at the JSR-356 API for further information.
Sending messages is done via the session of the receiving client. The broadcast method steps through each connected client in the connections set and sends the message by using a BasicRemote:
private static void broadcast(String msg) {
for (ChatAnnotation client : connections) {
try {
synchronized (client) {
client.session.getBasicRemote().sendText(msg);
}
} catch (IOException e) {
connections.remove(client);
try {
client.session.close();
} catch (IOException e1) {
// Ignore
}
}
}
}
The BasicRemote acquired by the client session getBasicRemote() method sends the message in blocking mode. There’s a non-blocking implementation, called AsyncRemote, too. It’s acquired by using the getAsyncRemote() method of the client session.
We now have a server, that can receive messages from any client and forward them to all connected clients. Let’s have a look at the Java client implementation.
For creating a Websocket client in Java, a ClientEndpoint
has to be
defined in a similar way as a ServerEndpoint
:
@ClientEndpoint
public class RemoteWebsocketClient {
private Session session;
@OnOpen
public void onOpen(Session session) {
this.session = session;
}
@OnMessage
public void onMessage(String message) {
System.out.println(message);
//TODO: output message in a chat window
}
@OnClose
public void onClose(Session session, CloseReason reason) {
}
}
In this simple chat example, we only need to implement the @OnMessage annotated method. The method just outputs the received message to the console.
You will need a JSR-356 implementation on the client side, as there’s no implementation contained in the Java JRE. The standard implementation is Project Tyrus and it is used in this example.
Add the following dependencies to your client project’s POM for using Tyrus in standalone mode:
<dependency>
<groupId>org.glassfish.tyrus</groupId>
<artifactId>tyrus-server</artifactId>
<version>1.0</version>
</dependency>
<dependency>
<groupId>org.glassfish.tyrus</groupId>
<artifactId>tyrus-client</artifactId>
<version>1.0</version>
</dependency>
<dependency>
<groupId>org.glassfish.tyrus</groupId>
<artifactId>tyrus-container-grizzly</artifactId>
<version>1.0</version>
</dependency>
Let’s connect the client to the server. It’s as easy as calling connectToServer method from a ClientManager. See the full source of the ChatClient:
public class ChatClient {
private ClientManager manager;
public void connect(URI uri) {
this.manager = ClientManager.createClient();
try {
manager.connectToServer(ChatClientEndpoint.class, uri);
} catch (DeploymentException | IOException e) {
System.exit(-1);
}
}
public void run() {
String msg = System.in.readLine();
while (!"".equals(msg)) {
manager.getSession().getBasicRemote().send(msg);
msg = System.readLine();
}
}
public void main(String[] args) {
ChatClient cc = new ChatClient();
cc.connect(URI.create("ws://localhost:8080/chat/service"));
cc.run();
}
}
The chat client connects to the server, by calling connectToServer
providing a ClientEndpoint
implementation class and an URI to connect
to. The main loop waits for the user input to be sent to the client. The
message handling is done by the ChatClientEndpoint
.
The code for a Javascript Websocket client is nearly the same as the
Java ClientEndpoint. Connection is made in a browser-specific way, as
Mozilla Firefox has an individual implementation named MozWebsocket
instead of the standard Websocket
:
var connect = function(url) {
if ('WebSocket' in window) {
socket = new WebSocket(url);
} else if ('MozWebSocket' in window) {
socket = new MozWebSocket(url);
}
};
Message handling is done inside the socket.onmessage
method, that should
be overriden. For displaying the chat messages, we create a new
paragraph and append it to a div in the displaying html page.
socket.onmessage = function(msg) {
var log = document.getElementById('log');
var p = document.createElement('p');
p.style.wordWrap = 'break-word';
p.innerHTML = msg.data;
log.appendChild(p);
}
Sending is done by using the socket’s send method:
send: function(msg) {
socket.send(msg);
}
The Websockets specification addresses the issues, modern web applications face using the http protocol.
With Websockets you get:
The major drawback of the Websocket specification is, that there’s no application level protocol specified. That’s where we as developers are challenged to step in and develop some generic application communication protocols that fill this gap.
With a generic application protocol, the second drawback could be addressed, too: The websocket protocol does not specify a mechanism for guaranteed message delivery – a major requirement for business use.
While not unsolvable, those two issues are responsible for Webscokets not getting the momentum, they actually deserve.
In my opinion, Websockets do have really huge benefits compared to traditional http communication, and I’m looking forward to use Websockets in a commercial product in the future.
I‘d like to compare Websockets to XMLHttpRequest before it got hyped as part of AJAX: As soon as XMLHttpRequest became usable with easy and reliable to use third party libraries, the Web 2.0 started.
Websockets do need their issues worked around with some easy to use libraries, too, for eventually taking commercial client-server applications to a new level.