22 min read

In this article by the authors, Michał Ćmil and Michał Matłoka, of Java EE 7 Development with WildFly, we will cover WebSockets and how they are one of the biggest additions in Java EE 7. In this article, we will explore the new possibilities that they provide to a developer. In our ticket booking applications, we already used a wide variety of approaches to inform the clients about events occurring on the server side. These include the following:

  • JSF polling
  • Java Messaging Service (JMS) messages
  • REST requests
  • Remote EJB requests

All of them, besides JMS, were based on the assumption that the client will be responsible for asking the server about the state of the application. In some cases, such as checking if someone else has not booked a ticket during our interaction with the application, this is a wasteful strategy; the server is in the position to inform clients when it is needed. What’s more, it feels like the developer must hack the HTTP protocol to get a notification from a server to the client. This is a requirement that has to be implemented in most nontrivial web applications, and therefore, deserves a standardized solution that can be applied by the developers in multiple projects without much effort.

WebSockets are changing the game for developers. They replace the request-response paradigm in which the client always initiates the communication with a two-point bidirectional messaging system. After the initial connection, both sides can send independent messages to each other as long as the session is alive. This means that we can easily create web applications that will automatically refresh their state with up-to-date data from the server. You probably have already seen this kind of behavior in Google Docs or live broadcasts on news sites. Now we can achieve the same effect in a simpler and more efficient way than in earlier versions of Java Enterprise Edition. In this article, we will try to leverage these new, exciting features that come with WebSockets in Java EE 7 thanks to JSR 356 (https://jcp.org/en/jsr/detail?id=356) and HTML5.

In this article, you will learn the following topics:

  • How WebSockets work
  • How to create a WebSocket endpoint in Java EE 7
  • How to create an HTML5/AngularJS client that will accept push notifications from an application deployed on WildFly

(For more resources related to this topic, see here.)

An overview of WebSockets

A WebSocket session between the client and server is built upon a standard TCP connection. Although the WebSocket protocol has its own control frames (mainly to create and sustain the connection) coded by the Internet Engineering Task Force in the RFC 6455 (http://tools.ietf.org/html/rfc6455), whose peers are not obliged to use any specific format to exchange application data. You may use plaintext, XML, JSON, or anything else to transmit your data. As you probably remember, this is quite different from SOAP-based WebServices, which had bloated specifications of the exchange protocol. The same goes for RESTful architectures; we no longer have the predefined verb methods from HTTP (GET, PUT, POST, and DELETE), status codes, and the whole semantics of an HTTP request.

This liberty means that WebSockets are pretty low level compared to the technologies that we used up to this point, but thanks to this, the communication overhead is minimal. The protocol is less verbose than SOAP or RESTful HTTP, which allows us to achieve higher performance. This, however, comes with a price. We usually like to use the features of higher-level protocols (such as horizontal scaling and rich URL semantics), and with WebSockets, we would need to write them by hand. For standard CRUD-like operations, it would be easier to use a REST endpoint than create everything from scratch.

What do we get from WebSockets compared to the standard HTTP communication? First of all, a direct connection between two peers. Normally, when you connect to a web server (which can, for instance, handle a REST endpoint), every subsequent call is a new TCP connection, and your machine is treated like it is a different one every time you make a request. You can, of course, simulate a stateful behavior (so that the server would recognize your machine between different requests) using cookies and increase the performance by reusing the same connection in a short period of time for a specific client, but basically, it is a workaround to overcome the limitations of the HTTP protocol. Once you establish a WebSocket connection between a server and client, you can use the same session (and underlying TCP connection) during the whole communication. Both sides are aware of it, and can send data independently in a full-duplex manner (both sides can send and receive data simultaneously). Using plain HTTP, there is no way for the server to spontaneously start sending data to the client without any request from its side. What’s more, the server is aware of all of its WebSocket clients connected, and can even send data between them!

The current solution that includes trying to simulate real-time data delivery using HTTP protocol can put a lot of stress on the web server. Polling (asking the server about updates), long polling (delaying the completion of a request to the moment when an update is ready), and streaming (a Comet-based solution with a constantly open HTTP response) are all ways to hack the protocol to do things that it wasn’t designed for and have their own limitations. Thanks to the elimination of unnecessary checks, WebSockets can heavily reduce the number of HTTP requests that have to be handled by the web server. The updates are delivered to the user with a smaller latency because we only need one round-trip through the network to get the desired information (it is pushed by the server immediately).

All of these features make WebSockets a great addition to the Java EE platform, which fills the gaps needed to easily finish specific tasks, such as sending updates, notifications, and orchestrating multiple client interactions. Despite these advantages, WebSockets are not intended to replace REST or SOAP WebServices. They do not scale so well horizontally (they are hard to distribute because of their stateful nature), and they lack most of the features that are utilized in web applications. URL semantics, complex security, compression, and many other features are still better realized using other technologies.

How does WebSockets work

To initiate a WebSocket session, the client must send an HTTP request with an upgraded, WebSocket header field. This informs the server that the peer client has asked the server to switch to the WebSocket protocol.

You may notice that the same happens in WildFly for Remote EJBs; the initial connection is made using an HTTP request, and is later switched to the remote protocol thanks to the Upgrade mechanism. The standard Upgrade header field can be used to handle any protocol, other than HTTP, which is accepted by both sides (the client and server). In WildFly, this allows to reuse the HTTP port (80/8080) for other protocols and therefore, minimise the number of required ports that should be configured.

If the server can understand the WebSocket protocol, the client and server then proceed with the handshaking phase. They negotiate the version of the protocol, exchange security keys, and if everything goes well, the peers can go to the data transfer phase. From now on, the communication is only done using the WebSocket protocol. It is not possible to exchange any HTTP frames using the current connection. The whole life cycle of a connection can be summarized in the following diagram:

A sample HTTP request from a JavaScript application to a WildFly server would look similar to this:

GET /ticket-agency-websockets/tickets HTTP/1.1
Upgrade: websocket
Connection: Upgrade
Host: localhost:8080
Origin: http://localhost:8080
Pragma: no-cache
Cache-Control: no-cache Sec-WebSocket-Key: TrjgyVjzLK4Lt5s8GzlFhA== Sec-WebSocket-Version: 13 Sec-WebSocket-Extensions: permessage-deflate; client_max_window_bits, x-webkit-deflate-frame User-Agent: Mozilla/5.0 (Windows NT 6.3; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/34.0.1847.116 Safari/537.36 Cookie: [45 bytes were stripped]

We can see that the client requests an upgrade connection with WebSocket as the target protocol on the URL /ticket-agency-websockets/tickets. It additionally passes information about the requested version and key.

If the server supports the request protocol and all the required data is passed by the client, then it would respond with the following frame:

HTTP/1.1 101 Switching Protocols
X-Powered-By: Undertow 1
Server: Wildfly 8
Origin: http://localhost:8080
Upgrade: WebSocket
Sec-WebSocket-Accept: ZEAab1TcSQCmv8RsLHg4RL/TpHw=
Date: Sun, 13 Apr 2014 17:04:00 GMT
Connection: Upgrade
Sec-WebSocket-Location: ws://localhost:8080/ticket-agency-websockets/tickets
Content-Length: 0

The status code of the response is 101 (switching protocols) and we can see that the server is now going to start using the WebSocket protocol. The TCP connection initially used for the HTTP request is now the base of the WebSocket session and can be used for transmissions. If the client tries to access a URL, which is only handled by another protocol, then the server can ask the client to do an upgrade request. The server uses the 426 (upgrade required) status code in such cases.

The initial connection creation has some overhead (because of the HTTP frames that are exchanged between the peers), but after it is completed, new messages have only 2 bytes of additional headers. This means that when we have a large number of small messages, WebSocket will be an order of magnitude faster than REST protocols simply because there is less data to transmit!

If you are wondering about the browser support of WebSockets, you can look it up at http://caniuse.com/websockets. All new versions of major browsers currently support WebSockets; the total coverage is estimated (at the time of writing) at 74 percent. You can see it in the following screenshot:

After this theoretical introduction, we are ready to jump into action. We can now create our first WebSocket endpoint!

Creating our first endpoint

Let’s start with a simple example:

package com.packtpub.wflydevelopment.chapter8.boundary;
import javax.websocket.EndpointConfig;
import javax.websocket.OnOpen;
import javax.websocket.Session;
import javax.websocket.server.ServerEndpoint;
import java.io.IOException;
@ServerEndpoint("/hello")
public class HelloEndpoint {
   @OnOpen
   public void open(Session session, EndpointConfig conf) throws IOException {
       session.getBasicRemote().sendText("Hi!");
   }
}

Java EE 7 specification has taken into account developer friendliness, which can be clearly seen in the given example. In order to define your WebSocket endpoint, you just need a few annotations on a Plain Old Java Object (POJO). The first POJO @ServerEndpoint(“/hello”) defines a path to your endpoint. It’s a good time to discuss the endpoint’s full address. We placed this sample in the application named ticket-agency-websockets. During the deployment of application, you can spot information in the WildFly log about endpoints creation, as shown in the following command line:

02:21:35,182 INFO [io.undertow.websockets.jsr] (MSC service thread 1-7)
UT026003: Adding annotated server endpoint class com.packtpub.wflydevelopment.
chapter8.boundary.FirstEndpoint for path /hello
02:21:35,401 INFO [org.jboss.resteasy.spi.ResteasyDeployment]
(MSC service thread 1-7) Deploying javax.ws.rs.core.Application: class
com.packtpub.wflydevelopment.chapter8.webservice.JaxRsActivator$Proxy$_$$_WeldClientProxy
02:21:35,437 INFO [org.wildfly.extension.undertow]
(MSC service thread 1-7) JBAS017534: Registered web context:
/ticket-agency-websockets

The full URL of the endpoint is ws://localhost:8080/ticket-agency-websockets/hello, which is just a concatenation of the server and application address with an endpoint path on an appropriate protocol.

The second used annotation @OnOpen defines the endpoint behavior when the connection from the client is opened. It’s not the only behavior-related annotation of the WebSocket endpoint. Let’s look to the following table:

Annotation

Description

@OnOpen

Connection is open. With this annotation, we can use the Session and EndpointConfig parameters. The first parameter represents the connection to the user and allows further communication. The second one provides some client-related information.

@OnMessage

This annotation is executed when a message from the client is being received. In such a method, you can just have Session and for example, the String parameter, where the String parameter represents the received message.

@OnError

There are bad times when some errors occur. With this annotation, you can retrieve a Throwable object apart from standard Session.

@OnClose

When the connection is closed, it is possible to get some data concerning this event in the form of the CloseReason type object.

There is one more interesting line in our HelloEndpoint. Using the Session object, it is possible to communicate with the client. This clearly shows that in WebSockets, two-directional communication is easily possible. In this example, we decided to respond to a connected user synchronously (getBasicRemote()) with just a text message Hi! (sendText (String)). Of course, it’s also possible to communicate asynchronously and send, for example, sending binary messages using your own binary bandwidth saving protocol. We will present some of these processes in the next example.

Expanding our client application

It’s time to show how you can leverage the WebSocket features in real life. We created the ticket booking application based on the REST API and AngularJS framework. It was clearly missing one important feature; the application did not show information concerning ticket purchases of other users. This is a perfect use case for WebSockets!

Since we’re just adding a feature to our previous app, we will describe the changes we will introduce to it.

In this example, we would like to be able to inform all current users about other purchases. This means that we have to store information about active sessions. Let’s start with the registry type object, which will serve this purpose. We can use a Singleton session bean for this task, as shown in the following code:

@Singleton
public class SessionRegistry {
   private final Set<Session> sessions = new HashSet<>();
   @Lock(LockType.READ)
   public Set<Session> getAll() {
       return Collections.unmodifiableSet(sessions);
   }
   @Lock(LockType.WRITE)
   public void add(Session session) {
       sessions.add(session);
   }
   @Lock(LockType.WRITE)
   public void remove(Session session) {
       sessions.remove(session);
   }
}

We could use Collections.synchronizedSet from standard Java libraries but it’s a great chance to remember what we described earlier about container-based concurrency. In SessionRegistry, we defined some basic methods to add, get, and remove sessions. For the sake of collection thread safety during retrieval, we return an unmodifiable view.

We defined the registry, so now we can move to the endpoint definition. We will need a POJO, which will use our newly defined registry as shown:

@ServerEndpoint("/tickets")
public class TicketEndpoint {
   @Inject
   private SessionRegistry sessionRegistry;
   @OnOpen
   public void open(Session session, EndpointConfig conf) {
       sessionRegistry.add(session);
   }
   @OnClose
   public void close(Session session, CloseReason reason) {
       sessionRegistry.remove(session);
   }
   public void send(@Observes Seat seat) {
       sessionRegistry.getAll().forEach(session -> session.getAsyncRemote().sendText(toJson(seat)));
   }
   private String toJson(Seat seat) {
       final JsonObject jsonObject = Json.createObjectBuilder()
               .add("id", seat.getId())
               .add("booked", seat.isBooked())
               .build();
       return jsonObject.toString();
   }
}

Our endpoint is defined in the /tickets address. We injected a SessionRepository to our endpoint. During @OnOpen, we add Sessions to the registry, and during @OnClose, we just remove them. Message sending is performed on the CDI event (the @Observers annotation), which is already fired in our code during TheatreBox.buyTicket(int). In our send method, we retrieve all sessions from SessionRepository, and for each of them, we asynchronously send information about booked seats. We don’t really need information about all the Seat fields to realize this feature. That’s the reason why we don’t use the automatic JSON serialization here. Instead, we decided to use a minimalistic JSON object, which provides only the required data. To do this, we used the new Java API for JSON Processing (JSR-353). Using a fluent-like API, we’re able to create a JSON object and add two fields to it. Then, we just convert JSON to the String, which is sent in a text message.

Because in our example we send messages in response to a CDI event, we don’t have (in the event handler) an out-of-the-box reference to any of the sessions. We have to use our sessionRegistry object to access the active ones. However, if we would like to do the same thing but, for example, in the @OnMessage method, then it is possible to get all active sessions just by executing the session.getOpenSessions() method.

These are all the changes required to perform on the backend side. Now, we have to modify our AngularJS frontend to leverage the added feature. The good news is that JavaScript already includes classes that can be used to perform WebSocket communication! There are a few lines of code we have to add inside the module defined in the seat.js file, which are as follows:

var ws = new WebSocket("ws://localhost:8080/ticket-agency-websockets/tickets");
ws.onmessage = function (message) {
   var receivedData = message.data;
   var bookedSeat = JSON.parse(receivedData);
   $scope.$apply(function () {
       for (var i = 0; i < $scope.seats.length; i++) {
          if ($scope.seats[i].id === bookedSeat.id) {
               $scope.seats[i].booked = bookedSeat.booked;
               break;
           }
       }
   });
};

The code is very simple. We just create the WebSocket object using the URL to our endpoint, and then we define the onmessage function in that object. During the function execution, the received message is automatically parsed from the JSON to JavaScript object. Then, in $scope.$apply, we just iterate through our seats, and if the ID matches, we update the booked state. We have to use $scope.$apply because we are touching an Angular object from outside the Angular world (the onmessage function). Modifications performed on $scope.seats are automatically visible on the website. With this, we can just open our ticket booking website in two browser sessions, and see that when one user buys a ticket, the second users sees almost instantly that the seat state is changed to booked.

We can enhance our application a little to inform users if the WebSocket connection is really working. Let’s just define onopen and onclose functions for this purpose:

ws.onopen = function (event) {
   $scope.$apply(function () {
       $scope.alerts.push({
           type: 'info',
           msg: 'Push connection from server is working'
       });
   });
};
ws.onclose = function (event) {
   $scope.$apply(function () {
       $scope.alerts.push({
           type: 'warning',
           msg: 'Error on push connection from server '
       });
   });
};

To inform users about a connection’s state, we push different types of alerts. Of course, again we’re touching the Angular world from the outside, so we have to perform all operations on Angular from the $scope.$apply function.

Running the described code results in the notification, which is visible in the following screenshot:

However, if the server fails after opening the website, you might get an error as shown in the following screenshot:

Transforming POJOs to JSON

In our current example, we transformed our Seat object to JSON manually. Normally, we don’t want to do it this way; there are many libraries that will do the transformation for us. One of them is GSON from Google. Additionally, we can register an encoder/decoder class for a WebSocket endpoint that will do the transformation automatically. Let’s look at how we can refactor our current solution to use an encoder.

First of all, we must add GSON to our classpath. The required Maven dependency is as follows:

<dependency>
   <groupId>com.google.code.gson</groupId>
   <artifactId>gson</artifactId>
   <version>2.3</version>
</dependency>

Next, we need to provide an implementation of the javax.websocket.Encoder.Text interface. There are also versions of the javax.websocket.Encoder.Text interface for binary and streamed data (for both binary and text formats). A corresponding hierarchy of interfaces is also available for decoders (javax.websocket.Decoder). Our implementation is rather simple. This is shown in the following code snippet:

public class JSONEncoder implements Encoder.Text<Object> {
   private Gson gson;
   @Override
   public void init(EndpointConfig config) {
       gson = new Gson(); [1]
   }
   @Override
   public void destroy() {
       // do nothing
   }
   @Override
   public String encode(Object object) throws EncodeException {
       return gson.toJson(object); [2]
   }
}

First, we create an instance of GSON in the init method; this action will be executed when the endpoint is created. Next, in the encode method, which is called every time, we send an object through an endpoint. We use JSON to create JSON from an object. This is quite concise when we think how reusable this little class is. If you want more control on the JSON generation process, you can use the GsonBuilder class to configure the GSON object before creation of the GsonBuilder class. We have the encoder in place. Now it’s time to alter our endpoint:

@ServerEndpoint(value = "/tickets", encoders={JSONEncoder.class})[1]
public class TicketEndpoint {
   @Inject
   private SessionRegistry sessionRegistry;
   @OnOpen
   public void open(Session session, EndpointConfig conf) {
       sessionRegistry.add(session);
   }
   @OnClose
   public void close(Session session, CloseReason reason) {
       sessionRegistry.remove(session);
   }
   public void send(@Observes Seat seat) {
       sessionRegistry.getAll().forEach(session -> session.getAsyncRemote().sendObject(seat)); [2]
   }
}

The first change is done on the @ServerEndpoint annotation. We have to define a list of supported encoders; we simply pass our JSONEncoder.class wrapped in an array. Additionally, we have to pass the endpoint name using the value attribute.

Earlier, we used the sendText method to pass a string containing a manually created JSON. Now, we want to send an object and let the encoder handle the JSON generation; therefore, we’ll use the getAsyncRemote().sendObject() method. That’s all! Our endpoint is ready to be used. It will work the same as the earlier version, but now our objects will be fully serialized to JSON, so they will contain every field, not only IDs and be booked.

After deploying the server, you can connect to the WebSocket endpoint using one of the Chrome extensions, for instance, the Dark WebSocket terminal from the Chrome store (use the ws://localhost:8080/ticket-agency-websockets/tickets address). When you book tickets using the web application, the WebSocket terminal should show something similar to the output shown in the following screenshot:

Of course, it is possible to use different formats other than JSON. If you want to achieve better performance (when it comes to the serialization time and payload size), you may want to try out binary serializers such as Kryo (https://github.com/EsotericSoftware/kryo). They may not be supported by JavaScript, but may come in handy if you would like to use WebSockets for other clients also. Tyrus (https://tyrus.java.net/) is a reference implementation of the WebSocket standard for Java; you can use it in your standalone desktop applications. In that case, besides the encoder (which is used to send messages), you would also need to create a decoder, which can automatically transform incoming messages.

An alternative to WebSockets

The example we presented in this article is possible to be implemented using an older, lesser-known technology named Server-Sent Events (SSE). SSE allows for one-way communication from the server to client over HTTP. It is much simpler than WebSockets but has a built-in support for things such as automatic reconnection and event identifiers. WebSockets are definitely more powerful, but are not the only way to pass events, so when you need to implement some notifications from the server side, remember about SSE.

Another option is to explore the mechanisms oriented around the Comet techniques. Multiple implementations are available and most of them use different methods of transportation to achieve their goals. A comprehensive comparison is available at http://cometdaily.com/maturity.html.

Summary

In this article, we managed to introduce the new low-level type of communication. We presented how it works underneath and compares to SOAP and REST introduced earlier. We also discussed how the new approach changes the development of web applications.

Our ticket booking application was further enhanced to show users the changing state of the seats using push-like notifications. The new additions required very little code changes in our existing project when we take into account how much we are able to achieve with them. The fluent integration of WebSockets from Java EE 7 with the AngularJS application is another great showcase of flexibility, which comes with the new version of the Java EE platform.

Resources for Article:


Further resources on this subject:


LEAVE A REPLY

Please enter your comment!
Please enter your name here