HTTP Client WebSocket Chat Application using Spring Boot

Have you ever thought about what the logic behind a chat application is? How could it be written and how does it work? If yes, here is the right place to look for an answer. It is called WebSocket! But what is this?

What is WebSocket?

WebSocket is a communication protocol exactly like HTTP providing a full duplex communication channel between server and client. The principle is that once a WebSocket connection between a server and a client exists, they can communicate and exchange information until one of them (the server or the client) disconnects. If there is the need for a communication and information exchange at high frequency and low latency, WebSocket should be preferred over HTTP. The WebSocket process is the following:

  • First of all, the client establishes a normal HTTP connection with the server.
  • Then it transforms the connection into a full duplex communication channel.
  • The step above is done by sending an Upgrade header which communicates to the server that the client wants to establish a WebSocket connection.

With Java 11

With the Java 11 HttpClient class, Java applications can not only execute synchronous or asynchronous HTTP requests. This library also supports the WebSocket protocol.

Well, Java 11 has a standardized HTTP client capable of WebSocket connections!

The example presented below is a chat application using HttpClient‘s WebSocket capabilities using Spring Boot for the server side application.

Let’s give it a try

We create a Spring Boot application, which helps us with the server side. The Spring Boot starter dependency for WebSocket is necessary for the application. I added it directly from my IDE. If you cannot do this, you can as well use the Spring Initializer web page to create a project with the necessary dependencies.

In Gradle this dependency declaration looks like this:

dependencies { implementation 'org.springframework.boot:spring-boot-starter-websocket' }

Once created, we can see the main class which was generated for us:

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class DemoApplication {

  public static void main(String[] args) {
    SpringApplication.run(DemoApplication.class, args);
  }
}

Let’s dive in with the developing part and of course the explanations:

We start with the ScheduledWebSocketHandler class which inherits from  TextWebSocketHandler to process text messages via WebSocket:

public class ScheduledWebSocketHandler extends TextWebSocketHandler{ ... }

 First, we define the active sessions list using the CopyOnWriteArrayList like this:

private List<WebSocketSession> activeSessions = new CopyOnWriteArrayList<>();

CopyOnWriteArrayList from the java.util.concurrent package  is the enhanced version of ArrayList where all updates like add, set, remove are implemented by making a fresh copy of the underlying array.

Because our class inherits from the TextWebSocketHandler we can  implement the three necessary methods:

  • void handleTextMessage(WebSocketSession session, TextMessage message)
  • void afterConnectionEstablished(WebSocketSession session)
  • void afterConnectionClosed(WebSocketSession session, CloseStatus status)

I will first focus on the first one (handleTextMessage): Here the message will be sent to all sessions except the current session. We iterate through all active sessions and send the message.

When a new session is created, the afterConnectionEstablished method is called in which new active session are added to the active sessions list (CopyOnWriteArrayList). When the connection is closed, the respective session will be removed from the active session list in the method afterConnectionClosed.

In the end, our ScheduledWebSocketHandler class will look like this:

public class ScheduledWebSocketHandler extends TextWebSocketHandler {

  private List<WebSocketSession> activeSessions = new CopyOnWriteArrayList<>();

  @Override
  public void handleTextMessage(WebSocketSession session, TextMessage message) throws IOException {

    activeSessions.forEach(session -> {
      try {
        if (!session.equals(session)) {
          session.sendMessage(message);
          System.out.println("Message from Client received: " + message);
        }
      } catch (IOException e) {
        e.printStackTrace();
      }
    });
  }

  @Override
  public void afterConnectionEstablished(WebSocketSession session) throws Exception {
    activeSessions.add(session);
  }

  @Override
  public void afterConnectionClosed(WebSocketSession session, CloseStatus status) throws Exception {
    activeSessions.remove(session);
  }
}

This was all about the server side. Now let’s move on to the client side:

First, we define the httpClient and then we create a  WebSocket builder using the HttpClient and the newWebSocketBuilder() method. After the request is sent asynchronously, the return value will of course be a CompletableFuture.

After the data has been received, the WebSocket invokes a receive method. The method onText(...) returns a CompletionStage which is completed when the message has been received by the listener. In this case, it prints out the message the server received from the client.  You can find out more about the HttpClient synchronous and asynchronous mechanisms by reading the first two blog posts of the HTTP client blog posts series (starting here).

But now let’s turn back to our subject. When a session has started, the message “WebSocket Client connected” is shown. The user can type in a message which will be sent to the server which in turn will forward it to the other active sessions.  If one of the sessions sends the word “quit”, then the respective session will be closed automatically.

The code for this is as follows:

public class WebSocketMain {

  @SuppressWarnings("resource")
  public static void main(String[] args) throws InterruptedException {
    Executor executor = ForkJoinPool.commonPool();
    HttpClient httpClient = HttpClient.newBuilder().executor(executor).build();

    Builder webSocketBuilder = httpClient.newWebSocketBuilder();
    CompletableFuture<WebSocket> webSocketFuture = webSocketBuilder
            .buildAsync(URI.create("ws://localhost:8081/messages"), new WebSocket.Listener() {
              @Override
              public CompletionStage<?> onText(WebSocket webSocket, CharSequence data, boolean last) {
                System.out.println("Message received from Server:"+" " + data);
                return Listener.super.onText(webSocket, data, last);
              }
            });

    WebSocket webSocket = webSocketFuture.join();
    System.out.println("WebSocket Client connected");
    String message;
    Scanner scanner = new Scanner(System.in);
    System.out.println("Write your message: ");
    do {
      message = scanner.nextLine();
      webSocket.sendText(message, true);

    } while (!message.equalsIgnoreCase("quit"));

    CompletableFuture<WebSocket> sendClose = webSocket.sendClose(WebSocket.NORMAL_CLOSURE, "ok");
    sendClose.thenRun(() -> System.out.println("Connection will be closed"));
  }
}

Congratulations, this was the last step. Now let’s see a demo of it to prove that the magic is done:

  • First of all we start our server by running the Spring Boot application.
  • Then we run the WebSocketMain as a Java application at least two times to have at least two active sessions. Please scroll through your code one more time to check if the ports are the same. Normally the used port is 8080 but in my case the used port is 8081. You can check this in the main class in the WebSocket builder URL: “ws://localhost:8081/messages”. If you want to use the same port as me on the server side you should enter the following line of code in the application.properties file: server.port = 8081. After the application has started, the following will show up:

Now let’s start chatting. The message from the first active session:

The message from the second active session:

This is the message the server will send to the first active session:

By writing the “quit” message, the connection will be closed as shown in the image below:

While all the above is done the following info is sent to the server:

I hope you enjoyed this demo and learned something from it. This article is part of the HTTP client blog post series. The HTTP client also supports push promises which will be the subject for the next blog post.  Another demo will be explained step by step. So stay tuned.

Views All Time
1425
Views Today
10
Short URL for this post: https://blog.oio.de/aFuHI
This entry was posted in Java Basics and tagged , , . Bookmark the permalink.

Leave a Reply

Your email address will not be published. Required fields are marked *