Java - Push Notification with websocket

LinkedIn Tweet Facebook
Java - Push Notification with websocket

Prerequisites

We will demonstrate how to build websockets using Tomcat 8 and HTML5 websocket API.

Websocket is TCP HTTP

Websockets are designed to work like TCP, providing full duplex communication but they are not TCP. Websockets are built on top of HTTP. That's why they work on the web. The client needs to make its regular HTTP request (handshake) to initiate websocket connection. After the successful handshake connection, only the duplex connection is established.

So ws:// is just like http:// and wss:// uses same SSL certificate used by https://

Server Socket

The below server socket accepts the connection via a blank message and sends the message back to the client after repeated intervals.

This socket demonstrates how the client has to listen to messages from the server without further sending any information to the server. This works the opposite way if compared with HTTP requests, where the client has to request each time it expects a reply from the server.

import java.io.IOException;
import java.util.Timer;
import java.util.TimerTask;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.websocket.OnMessage;
import javax.websocket.Session;
import javax.websocket.server.ServerEndpoint;

@ServerEndpoint("/push/")
public class PushSocket {

    @OnMessage
    public void onMessage(String message, final Session session) {

        System.out.println("Message from " + session.getId() + ": " + message);
        TimerTask timerTask = new TimerTask() {
            @Override
            public void run() {
                try {
                    session.getBasicRemote().sendText("Push");
                } catch (IOException ex) {
                    Logger.getLogger(PushSocket.class.getName()).log(Level.SEVERE, null, ex);
                }
            }
        };
        Timer timer = new Timer(true);
        timer.scheduleAtFixedRate(timerTask, 0, 3 * 1000);
    }
}

Creating a socket in Java is similar to creating a servlet. The message function has to be annotated with @OnMessage. This method will be invoked when the client sends a message.

The sendText() function can be used wherever server wishes to push messages to the client.

Client Socket

WebSocket object in javascript points to the socket endpoint.


var pushSocket = new WebSocket("ws://localhost:8080/websocket/push/")

pushSocket.onmessage = function (event) {
  console.log(event.data);
  //do ui update here
}

pushSocket.onopen = function (event) {
    //send empty message to initialize socket connnection
    pushSocket.send("");
};

pushSocket.onclose = function (event) {
    //send empty message to initialize socket connnection
    alert("Socket Closed by Server");
};

Handling multiple Clients

Above we saw how we can keep pushing messages to a single client. What about multiple clients connecting to the server. How do we push separately to each client?

The Socket API provides two methods @OnOpen and @OnClose to store the list of existing clients connected to the server.

Add incoming session in @OnOpen to list. Remove session from the list in @OnClose

List<Session> socketSessions = Collections.synchronizedList(new ArrayList<Session>());

We will store the list in a SessionManager and add and remove elements w.

@OnOpen
public void open(Session session) {
  sessionManager.addSession(session);
}

@OnClose
public void close(Session session) {
  sessionManager.removeSession(session);
}

So SessionManager can have a broadcast method that can be used to broadcast the message to all clients connected.

public void broadcast(String message){
    for(Session session: sessionList){
        session.getBasicRemote().sendText(message);
    }
}

You can use the sessionManager.broadcast() method wherever the push event is triggered.

If you noticed, we don't need @OnMessage anymore, since we are storing the sessions. We can push to any client, which is available in the list i.e. connected to the server.

Identifying Sessions from different Clients

We can store user info to identify the connected user in the map provided by the Session object


@OnOpen
public void open(@PathParam("userid") String userId, Session session){
    session.getUserProperties().put("USER_ID" , userId);
}

At this stage, we have multiple clients we can broadcast to. But we want to send a specific notification to a single client. We have to loop over all the sessions to identify the userId match. This is not an efficient solution.

So we rather handle the identification at handshake level. So we can discard unauthenticated connections.

And we convert over Synchronized ArrayList into a concurrent hashmap.

Further Reading

Read more about how to send data to websockets in javascript https://developer.mozilla.org/en-US/docs/Web/API/WebSockets_API/Writing_WebSocket_client_applications

Websocket is JSR 356 implementation. https://www.oracle.com/technetwork/articles/java/jsr356-1937161.html