Tuesday, May 31, 2022

Websocket concept with JAVA SpringBoot

 Generally in REST htp base application we have a concept in which request is raised from browser, and we get response from the server. This are stateless protocol and are unidirectional. But assume a scenario where we want a dedicated connection between client and server so that data can flow in two ways i.e. client to server and server to client in duplex fashion. This is what we get from websocket protocol. This protocol sit on top of http and hence can be used from browser as well. Consider this as a telephone protocol where both receiver and caller can talk to each other at the same time.

Now the question comes where this scenario can be used, i.e. what is the usecase for this. I can at least recall below three
1- Chatting
2- Share market i.e. live share cost and bid value changing without refreshing the whole page.
3- White board used for common discussion where everyone can get the change in content from anybody.
4- Gaming Score board where all the user can see the score of the other user without refreshing the screen.

In this blog, we will try to have a chat application that will show how message is transfer between the multiple user once they connected to the WebSocket connection.

We are using sprig boot, thymeleaf for ui, websocket and jquery

First create a springboot application with following dependencies
1- spring-boot-starter-thymeleaf
2- spring-boot-starter-websocket
3- lombok
4- jquery

This is our Pom.xml

1- Pom.xml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
<?xml version="1.0" encoding="UTF-8"?>
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.7.0</version>
        <relativePath /> <!-- lookup parent from repository -->
    </parent>
    <groupId>com.siddhu</groupId>
    <artifactId>websocket-example</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>websocket-example</name>
    <description>This example shows websocket functionality</description>
    <properties>
        <java.version>11</java.version>
    </properties>
    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-thymeleaf</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-websocket</artifactId>
        </dependency>
 
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
         
        <dependency>
            <groupId>org.webjars</groupId>
            <artifactId>jquery</artifactId>
            <version>3.4.1</version>
        </dependency>
    </dependencies>
 
    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
                <configuration>
                    <excludes>
                        <exclude>
                            <groupId>org.projectlombok</groupId>
                            <artifactId>lombok</artifactId>
                        </exclude>
                    </excludes>
                </configuration>
            </plugin>
        </plugins>
    </build>
 
</project>

2- SiddhuWebsocketExampleApplication

1
2
3
4
5
6
7
8
9
10
11
12
13
package com.siddhu;
 
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
 
@SpringBootApplication
public class SiddhuWebsocketExampleApplication {
 
    public static void main(String[] args) {
        SpringApplication.run(SiddhuWebsocketExampleApplication.class, args);
    }
 
}

3- SiddhuWebSocketConfig

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
package com.siddhu.config;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.socket.config.annotation.EnableWebSocket;
import org.springframework.web.socket.config.annotation.WebSocketConfigurer;
import org.springframework.web.socket.config.annotation.WebSocketHandlerRegistry;
 
import com.siddhu.handler.SiddhuTextWebSocketHandler;
  
@Configuration
@EnableWebSocket
public class SiddhuWebSocketConfig implements WebSocketConfigurer {
  
    @Override
    public void registerWebSocketHandlers(WebSocketHandlerRegistry webSocketHandlerRegistry) {
        webSocketHandlerRegistry.addHandler(new SiddhuTextWebSocketHandler(), "/web-socket");
    }
}

we have SiddhuWebSocketController which will direct our url having /websocket to index.html

4- SiddhuWebSocketController

1
2
3
4
5
6
7
8
9
10
11
12
package com.siddhu.controller;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
  
@Controller
public class SiddhuWebSocketController {
     
    @RequestMapping("/websocket")
    public String getWebSocket() {
        return "index";
    }
}

We have handler SiddhuTextWebSocketHandler which will be used to add and remove the session once the user click on connect and disconnect button on the screen.

5- SiddhuTextWebSocketHandler

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
package com.siddhu.handler;
import java.io.IOException;
import java.util.List;
import java.util.concurrent.CopyOnWriteArrayList;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.web.socket.CloseStatus;
import org.springframework.web.socket.TextMessage;
import org.springframework.web.socket.WebSocketSession;
import org.springframework.web.socket.handler.TextWebSocketHandler;
  
public class SiddhuTextWebSocketHandler extends TextWebSocketHandler {
  
    private static final Logger LOGGER = LoggerFactory.getLogger(SiddhuTextWebSocketHandler.class);
     
    private final List<WebSocketSession> sessions = new CopyOnWriteArrayList<>();
  
    @Override
    public void afterConnectionEstablished(WebSocketSession session) throws Exception {
        sessions.add(session);
        super.afterConnectionEstablished(session);
    }
  
    @Override
    public void afterConnectionClosed(WebSocketSession session, CloseStatus status) throws Exception {
        sessions.remove(session);
        super.afterConnectionClosed(session, status);
    }
  
    @Override
    protected void handleTextMessage(WebSocketSession session, TextMessage message) throws Exception {
        super.handleTextMessage(session, message);
        sessions.forEach(webSocketSession -> {
            try {
                webSocketSession.sendMessage(message);
            } catch (IOException e) {
                LOGGER.error("Error occurred.", e);
            }
        });
    }
}

6- index.html

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
<!DOCTYPE HTML>
<html xmlns:th="http://www.thymeleaf.org">
    <head>
    <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1.0">
        <link rel="stylesheet" type="text/css" th:href="@{/webjars/bootstrap/4.4.1/css/bootstrap.css}"/>
        <link rel="stylesheet" type="text/css" th:href="@{/css/main.css}"/>
        <script th:src="@{/webjars/jquery/3.4.1/jquery.js}" ></script>
        <title>Plain WebSocket Example</title>    
    </head>
     
    <body>
        <div class="container">
            <div >
                <h2>Simple Web socket Example</h2>               
            </div>
            <div class="row">
                <div class="col-md-6">
                    <div class="row mb-3">
                        <div >
                            Press Connect button for Web socket connection:&nbsp; and for opposite press Disconnect button
                            <div>
                                <button type="button" id="connect" onclick="connect()">Connect</button>
                            <button type="button" id="disconnect" onclick="disconnect()" disabled>Disconnect</button>
                            </div>                       
                        </div>
                    </div>
                    <div >
                        <div class="input-group" id="sendmessage" style="display: none;">
                            <input type="text" id="name" class="form-control" placeholder="Name">
                            <input type="text" id="message" class="form-control" placeholder="Message">
                            <div class="input-group-append">
                                <button id="send" class="btn btn-primary" onclick="send()">Send</button>
                            </div>
                        </div>
                    </div>
                </div>
                <div>
                    <div id="content"></div>                   
                </div>
            </div>
        </div>
  
       
         
        <script>
            var ws;
            function setConnected(connected) {
                $("#connect").prop("disabled", connected);
                $("#disconnect").prop("disabled", !connected);
                if (connected) {
                    $("#sendmessage").show();
                } else {
                    $("#sendmessage").hide();
                }
            }
  
            function connect() {
                /*<![CDATA[*/
                var url = /*[['ws://'+${#httpServletRequest.serverName}+':'+${#httpServletRequest.serverPort}+@{/web-socket}]]*/ 'ws://localhost:8080/web-socket';
                /*]]>*/
                ws = new WebSocket(url);
                ws.onopen = function () {
                    showBroadcastMessage('<div class="alert alert-success">Connected to server</div>');
                };
                ws.onmessage = function (data) {                   
                    showBroadcastMessage(createTextNode(data.data));
                };
                setConnected(true);
            }
  
            function disconnect() {
                if (ws != null) {
                    ws.close();
                    showBroadcastMessage('<div class="alert alert-warning">Disconnected from server</div>');
                }
                setConnected(false);
            }           
  
            function send() {
                if($("#name").val() != '' &&  $("#message").val() != '')
                {
                     ws.send($("#name").val() + ':' + $("#message").val());
                     $("#message").val("");
                     $("#name").val("");
                }
                
            }
  
            function createTextNode(msg) {
                return '<div class="alert alert-info">' + msg + '</div>';
            }
             
            function showBroadcastMessage(message) {
                $("#content").html($("#content").html() + message);
            }
        </script>
    </body>
</html>

Click on Connect button and it will help you to connect to using websocket

Add the user name and message

click on send button

Now open the other browser and so the same

As soon as you click on send button you will find a message is send on the screen but at the same time you will also get the same message on first screen also

Now send message from here and see if we get the message in another tab.

This how the live chat application work.

Note:- You can download the sourcecode from
https://github.com/shdhumale/websocket-example.git