For a project I had the need to implement a monitoring functionality based on HTML5 and WebSockets. It is quite trivial with JavaEE 7, as I will explain below.
Let us assume the easy requirement of a simple monitoring which sends periodic status information to web clients. The web client shall show the information on a web page (inside a <div>…</div> for instance). For that scenario, the technical details are shown below…
The JavaScript code is quite easy and can be taken from JavaScript WebSocket books and tutorials. (A good introduction is for instance Java WebSocket Programming by Oracle Press). A simple client might look like:
var websocket = new WebSocket("ws://<host>:<port>/socket"); websocket.onmessage = function (event) { var outputElement = document.getElementById("output"); outputElement.innerHTML = event.data; }
The functions for onopen, onclose and onerror are neglected, because we want to focus on JavaEE. The important stuff is shown above: We connect with new WebSocket to the URL which shall provide the periodic updated and with onmessage we put the data somewhere into our web page. That’s it from the client site.
For JavaEE, there is a lot of documentation which shows how to create @ServerEndpoint classes. For instance:
import javax.websocket.CloseReason; import javax.websocket.EndpointConfig; import javax.websocket.OnClose; import javax.websocket.OnError; import javax.websocket.OnMessage; import javax.websocket.OnOpen; import javax.websocket.Session; import javax.websocket.server.ServerEndpoint; @ServerEndpoint(value = "/server", // encoders = { ... }, // decoders = { ... }// ) public class PurifinityServerSocket { private Session session = null; @OnOpen public void open(Session session, EndpointConfig config) { this.session = session; } @OnClose public void close(CloseReason reason) { } @OnMessage public ServerStatus getServerStatus( StatusRequest request) { return <status message>; } @OnError public void handleError(Throwable throwable) { } }
But, how to make it send periodic messages easily? After some testing on WildFly 8.2, I came to this simple solution:
import javax.ejb.Schedule; import javax.ejb.Singleton; import javax.websocket.CloseReason; import javax.websocket.EncodeException; import javax.websocket.EndpointConfig; import javax.websocket.OnClose; import javax.websocket.OnError; import javax.websocket.OnMessage; import javax.websocket.OnOpen; import javax.websocket.Session; import javax.websocket.server.ServerEndpoint; @ServerEndpoint(value = "/server", // encoders = { ... }, // decoders = { ... }// ) @Singleton public class PurifinityServerSocket { private Session session = null; @OnOpen public void open(Session session, EndpointConfig config) { this.session = session; } @OnClose public void close(CloseReason reason) { } @OnMessage public ServerStatus getServerStatus( StatusRequest request) { return <status message>; } @OnError public void handleError(Throwable throwable) { } @Schedule(hour = "*", minute = "*", second = "*/5") public void periodicUpdate() { if (session != null) { ServerStatus status = <server status information>; for (Session client : session.getOpenSessions()) { try { client.getBasicRemote().sendObject(status); } catch (IOException | EncodeException e) { // Exception handling... } } } } }
The trick is to make the @ServerEndpoint class also an EJB @Singleton. The @Singleton assures that only one instance is living at a time and this instance can keep also the session provided during @OnOpen. In other words: The actual server endpoint instance is exactly the same where the scheduler is running on. If it would not be @Singleton, multiple instances will or may exist and the session field is not set in @Schedule and might lead to a NullPointerException if not checked for.