WebSocket Server Events

I have documented a small tutorial on how to push events from the server to the clients using WebSockets.

Using Websockets with Java EE 7 is extremely easy. You just need two things:

  • a ServerEndpoint class
  • a Client to send messages or receive events

To demonstrate several events I have created a base ServerEndpoint class and a simple client in Javascript.


ServerEndpoint class

@ServerEndpoint(value = "/wss", decoders = ChatMessageDecoder.class, encoders = ChatMessageEncoder.class)
public class ChatEndpoint {

    private static final Set<Session> peers = Collections.synchronizedSet(new HashSet<>());

    @OnOpen
    public void onOpen(Session session) {
        peers.add(session);
    }

    @OnMessage
    public void onMessage(ChatMessage message, Session session) {
        broadcastMessage(message);
    }

    @OnClose
    public void onClose(Session session) {
        peers.remove(session);
    }

    private void broadcastMessage(ChatMessage message) {
        synchronized (peers) {
            peers.stream().filter(Session::isOpen).forEach(session -> wrappedMessage(session, message));
        }
    }
}

In it’s simplest form this is all that is needed to have a “chatty” endpoint. We declare the annotations @ServerEndpoint for class scope and @onOpen, @onClose and @onMessage. The annotations and method responsibility is quite intuitive.

The full code for the encoders and decoders is:


Message class

public class ChatMessage {

    private String content;
    private String sender;
    private LocalDateTime received;

    public ChatMessage(String content, String sender) {
        this.content = content;
        this.sender = sender;
        this.received = LocalDateTime.now();
    }

    // getters
}

Encoder class

public class ChatMessageEncoder implements Encoder.Text<ChatMessage> {

    @Override
    public String encode(ChatMessage chatMessage) throws EncodeException {
        return Json.createObjectBuilder()
                .add("content", chatMessage.getContent())
                .add("sender", chatMessage.getSender())
                .add("received", chatMessage.getReceived().format(DateTimeFormatter.ofPattern("dd-MM-yyyy kk:mm:ss")))
                .build().toString();
    }
}

Decoder class

public class ChatMessageDecoder implements Decoder.Text<ChatMessage> {

    @Override
    public ChatMessage decode(String message) throws DecodeException {
        JsonReader reader = Json.createReader(new StringReader(message));
        JsonObject jsonObject = reader.readObject();
        String content = jsonObject.getString("content");
        String sender = jsonObject.getString("sender");

        return new ChatMessage(content, sender);
    }
}

Now that we have a way to encode and decode the messages, let’s move on to the server side events..

First to create a batch process lets modify our endpoint class to become a Singleton.


Startup Singleton

@Singleton
@Startup
@ConcurrencyManagement(ConcurrencyManagementType.BEAN)
@ServerEndpoint(value = "/wss", decoders = ChatMessageDecoder.class, encoders = ChatMessageEncoder.class)
public class ChatEndpoint

This creates a startup singleton EJB. We control the synchronization behaviour, hence the @ConcurrencyManagement(ConcurrencyManagementType.BEAN). We could have used Container managed concurrency with @Lock(LockType.READ) as well.

Now that we have a singleton EJB let’s create a batch process. This will all be done inside the class ChatEndpoint.


Batch Event

    @Schedule(hour = "*", minute = "*", second = "*/10")
    public void batch() {
        broadcastMessage(new ChatMessage("server event", SYSTEM));
    }

This creates a timer scheduled process that will run every ten seconds and send messages to the connected clients.


External Event

What if we want to broadcast messages based on events occurring in the system? With Java EE this becomes extremely simple.

    @Asynchronous
    public void event(@Observes String resourceMessage) {
        broadcastMessage(new ChatMessage(resourceMessage, RESOURCE));
    }

The external event is fired and the method event acts as listener. Notice we can even leverage asynchronous behaviour with a simple annotation @Asynchronous because we are using EJB (via @Singleton).

Now all that’s left is to create a simple client.


Client

    var app = angular.module("myApp", []);

    app.controller("myCtrl", function ($scope, $http) {
        $scope.form = {
            sender: '',
            message: ''
        };

        var websocket = new WebSocket("ws://localhost:8080/wss");

        $scope.sendDetails = function () {
            websocket.send(JSON.stringify($scope.form));
        }

        websocket.onmessage = function(msg) {
            var msg = JSON.parse(msg.data);
            var myEl = angular.element( document.querySelector( '#chat-messages' ) );
            var cssClass = '';
            switch (msg.sender){
                case 'system': cssClass = 'text-danger'; break;
                case 'resource': cssClass = 'text-success'; break;
                default:
            }
            myEl.append('<span class="'+cssClass+'"><b><i>' + msg.received + '</i>' + '  ' + msg.sender + '</b>: ' + msg.content + '<br/></span>');
        };
    });

HTML


     <div class="row">
        <div class="col-lg-6">
            <h3 class="title">Chat Room</h3>
            <div id="chat-messages"/>
        </div>
        <div class="col-lg-6">
            <form role="form">
                <legend>My Details</legend>
                <div class="form-group">
                    <label for="sender">Sender</label>
                    <input type="text" class="form-control" id="sender" ng-model="form.sender" placeholder="Name...">
                </div>

                <div class="form-group">
                    <label for="content">Message</label>
                    <input type="text" class="form-control" id="content" ng-model="form.content" placeholder="Message...">
                </div>

                <button class="btn btn-primary" ng-click="sendDetails()">Submit</button>
            </form>
        </div>
    </div>

This is using AngularJS but standard Javascript will do.

Hope this helps!


Written on May 4, 2017