Web Socket
Web Socket is a very thin, lightweight layer above TCP used to build interactive web applications that send messages back and forth between a browser and the server.
The best examples are live updates websites, where once user access the website neither user nor browser sends request to the server to get the latest updates. Server only keeps sending the messages to the browser.
In this example, I am building a WebSocket application with Spring Boot using STOMP Messaging to provide automatic updates on the greeting message depending on the time of the day for every 3 seconds.
Server Application
Prerequisites
Java 12/19, Gradle 5.6, Maven 3.8.5, Spring Boot WebSocket 2.1.8/3.1.5
Project Setup
Create gradle or maven based project called spring-websocket in your favorite IDE.
For maven based project you can refer to the following pom.xml file:
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.roytuts</groupId>
<artifactId>spring-websocket</artifactId>
<version>0.0.1-SNAPSHOT</version>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<maven.compiler.source>19</maven.compiler.source>
<maven.compiler.target>19</maven.compiler.target>
</properties>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>3.1.5</version>
</parent>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-websocket</artifactId>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
The corresponding build.gradle script is updated with the below content to include required dependency and configurations.
buildscript {
ext {
springBootVersion = '2.1.8.RELEASE'
}
repositories {
mavenLocal()
mavenCentral()
}
dependencies {
classpath("org.springframework.boot:spring-boot-gradle-plugin:${springBootVersion}")
}
}
apply plugin: 'java'
apply plugin: 'org.springframework.boot'
sourceCompatibility = 12
targetCompatibility = 12
repositories {
mavenLocal()
mavenCentral()
}
dependencies {
implementation("org.springframework.boot:spring-boot-starter-websocket:${springBootVersion}")
}
POJO Class
I will create a simple POJO class to represent message that will be sent to the client as a push notification.
package com.roytuts.spring.websocket.vo;
public class Message {
private String msg;
public Message(String msg) {
this.msg = msg;
}
public String getMsg() {
return msg;
}
}
Web Socket Configuration
Configure Spring to enable Web Socket and STOMP messaging.
package com.roytuts.spring.websocket.config;
@Configuration
@EnableWebSocketMessageBroker
public class WebSocketConfig implements WebSocketMessageBrokerConfigurer {
@Override
public void registerStompEndpoints(StompEndpointRegistry registry) {
//registry.addEndpoint("/websocket").setAllowedOrigins("*").withSockJS(); // spring boot 2.x.x
registry.addEndpoint("/websocket").setAllowedOrigins("http://localhost:4200").withSockJS(); //spring boot 3.x.x
}
@Override
public void configureMessageBroker(MessageBrokerRegistry config) {
config.enableSimpleBroker("/topic");
config.setApplicationDestinationPrefixes("/app");
}
}
The above WebSocketConfig
class is annotated with @Configuration
to indicate that it is a Spring configuration class.
The class is also annotated @EnableWebSocketMessageBroker
and @EnableWebSocketMessageBroker
enables WebSocket message handling, backed by a message broker.
The configureMessageBroker()
method overrides the default method in WebSocketMessageBrokerConfigurer
interface to configure the message broker.
It starts by calling enableSimpleBroker()
to enable a simple memory-based message broker to carry the greeting messages back to the client on destinations prefixed with /topic
.
It also designates the /app
prefix for messages that are bound for @MessageMapping
annotated methods. This prefix will be used to define all the message mappings; for example, /app/hello
is the endpoint that the GreetingRestController.greeting()
method is mapped to handle.
The registerStompEndpoints()
method registers the /websocket
endpoint, enabling SockJS fallback options so that alternate transports may be used if WebSocket is not available.
The SockJS client will attempt to connect to /websocket
and use the best transport available (websocket, xhr-streaming, xhr-polling, etc).
Related Posts:
- Server Sent Events with Spring – Push Notifications
- Messaging with STOMP over WebSockets using Spring, Angular 8 and ActiveMQ
Service Class
This class will generate different greet messages depending upon the time of the day. I have also appended random generated string so that we will get different random string appended with actual greet message to differentiate from each other every 3 seconds and it will actually tell us that we are getting every time the new message.
package com.roytuts.spring.websocket.service;
@Service
public class GreetingService {
public Message getMessage() {
Calendar c = Calendar.getInstance();
int timeOfDay = c.get(Calendar.HOUR_OF_DAY);
StringBuilder sb = new StringBuilder();
String message = "Have a Good Day";
if (timeOfDay >= 0 && timeOfDay < 12) {
message = "Good Morning";
} else if (timeOfDay >= 12 && timeOfDay < 16) {
message = "Good Afternoon";
} else if (timeOfDay >= 16 && timeOfDay < 21) {
message = "Good Evening";
} else if (timeOfDay >= 21 && timeOfDay < 24) {
message = "Good Night";
}
sb.append(message).append(" - ").append(generateString());
return new Message(sb.toString());
}
private String generateString() {
String uuid = UUID.randomUUID().toString();
return uuid;
}
}
REST Controller Class
In Spring’s approach to working with STOMP messaging, STOMP messages can be routed to @Controller
or @RestController
classes.
For example the GreetingRestController
is mapped to handle messages to destination /hello
.
package com.roytuts.spring.websocket.rest.controller;
@RestController
public class GreetingRestController {
@Autowired
private GreetingService greetingService;
@MessageMapping("/hello")
@SendTo("/topic/greeting")
public Message greeting() {
return greetingService.getMessage();
}
}
The @MessageMapping
annotation ensures that if a message is sent to destination /hello
, then the greeting() method is called.
Scheduler Configuration
Create scheduler to push updates every 3 seconds to client.
package com.roytuts.spring.websocket.config;
@Configuration
@EnableScheduling
public class WebSocketSchedulerConfig {
@Autowired
private SimpMessagingTemplate template;
@Autowired
private GreetingService greetingService;
@Scheduled(fixedRate = 3000)
public void publishUpdates() {
System.out.println("Message: " + greetingService.getMessage().getMsg());
template.convertAndSend("/topic/greeting", greetingService.getMessage());
}
}
Running the Server Application
Run the main class file SpringWebSocketApp and you will see the greeting message is getting displayed in the console.
Console Output
...
Message: Good Evening - 683407fd-22ef-43a5-87f8-85d20b7d4668
Message: Good Evening - 31817e2e-084f-4529-ad2b-c85cc61f8e2b
Message: Good Evening - ba628369-6f3a-4605-a8cf-876acadd578a
Message: Good Evening - f39c0f0d-2bb3-4a74-93d7-82583f561500
...
That’s all about server application. Now I will create the client application.
Client Application
With the server side pieces in place, now let’s move to the client application that will send messages to and receive messages from the server side.
Prerequisites
Angular 8/17, Node 18.17.1, Npm 10.2.3
Creating Angular Project
Create an Angular project called angular-spring-websocket.
Installing Required Modules
Install the required modules with the following commands:
For Angular 17 use the following command format:
npm i --save-dev @types/sockjs-client
npm i --save-dev @types/jquery
npm i --save-dev @types/stompjs
npm i net -S
For Angular 8 use the following command format:
npm install stompjs
npm install sockjs-client
npm install jquery
npm i net -S
The stompjs
is required to connect over STOMP.
The sockjs-client
is required to establish connection with WebSocket server.
The jquery
is required to directly access DOM elements in the HTML page.
To avoid net
issue we need to install net
module.
Updating index.html
You need to declare window in the src/index.html file to avoid the following issue during runtime of the Angular application:
Uncaught ReferenceError: global is not defined
at Object../node_modules/sockjs-client/lib/utils/browser-crypto.js
The complete content of src/index.html file is given below:
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>AngularSpringWebsocket</title>
<base href="/">
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="icon" type="image/x-icon" href="favicon.ico">
<script>
if (global === undefined) {
var global = window;
}
</script>
</head>
<body>
<app-root></app-root>
</body>
</html>
Updating app.component.html
I will update the src/app/app.component.html file to put a div tag where greeting message will be updated.
<div class="msg"></div>
<router-outlet></router-outlet>
Updating app.component.ts
I will update src/app/app.component.ts file to consume the message over STOMP.
I set the page title by implementing OnInit
interface in the ngOnInit()
method.
I will establish connection to the WebSocket server, client socket subscribe to the topic /topic/greeting
destination, where the server will publish greeting messages and finally we update the div (having a class msg
) on HTML page.
For Angular 17, use the following code:
import { OnInit, Component } from '@angular/core';
import { Title } from '@angular/platform-browser';
import { CommonModule } from '@angular/common';
import { RouterOutlet } from '@angular/router';
import Stomp from 'stompjs';
import SockJS from 'sockjs-client';
import $ from 'jquery';
@Component({
selector: 'app-root',
standalone: true,
imports: [CommonModule, RouterOutlet],
templateUrl: './app.component.html',
styleUrls: ['./app.component.css']
})
export class AppComponent implements OnInit {
url = 'http://localhost:8080/websocket'
client: any;
ngOnInit() {
this.title.setTitle('Angular Spring Websocket');
}
constructor(private title: Title){
this.connection();
}
connection(){
let ws = new SockJS(this.url);
this.client = Stomp.over(ws);
let that: any = this;
this.client.connect({}, function(frame: any) {
that.client.subscribe("/topic/greeting", (message: any) => {
if(message.body) {
$(".msg").html(message.body)
}
});
});
}
}
For Angular 8, use the following code:
import { OnInit, Component } from '@angular/core';
import { Title } from '@angular/platform-browser';
import * as Stomp from 'stompjs';
import * as SockJS from 'sockjs-client';
import $ from 'jquery';
@Component({
selector: 'app-root',
templateUrl: './app.component.html',
styleUrls: ['./app.component.css']
})
export class AppComponent implements OnInit {
url = 'http://localhost:8080/websocket'
client: any;
greeting: string;
ngOnInit() {
this.title.setTitle('Angular Spring Websocket');
}
constructor(private title: Title){
this.connection();
}
connection(){
let ws = new SockJS(this.url);
this.client = Stomp.over(ws);
let that = this;
this.client.connect({}, function(frame) {
that.client.subscribe("/topic/greeting", (message) => {
if(message.body) {
this.greeting = message.body;
//$(".msg").append(this.greeting)
$(".msg").html(this.greeting)
//alert(this.greeting);
//console.log(this.greeting);
}
});
});
}
}
I do not want to send any message to the destination /app/hello
, so I don’t need to call this endpoint. I am here to just get the message from server as a push notification towards clients.
Testing the Client Application
With your server running now run the client application by executing command ng serve --open
.
Your application opens at http://localhost:4200 and you will see the message being updated every 3 seconds.