WebSocket 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 the cricket live score updates for every 5 secs.
Step 1 : Maven setup
<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/maven-v4_0_0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>1.3.0.RELEASE</version>
</parent>
<groupId>com.kswaughs.spring</groupId>
<artifactId>springboot-websocket</artifactId>
<version>1.0</version>
<name>springboot-websocket</name>
<description>Spring Boot WebSocket Application</description>
<dependencies>
<!-- Spring framework related jars -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
<version>1.3.0.RELEASE</version>
</dependency>
<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>
Step 2: Model class to hold the batsman & score details
package com.kswaughs.cricket.beans;
public class Batsman {
private String name;
private int runs;
private int balls;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getRuns() {
return runs;
}
public void setRuns(int runs) {
this.runs = runs;
}
public int getBalls() {
return balls;
}
public void setBalls(int balls) {
this.balls = balls;
}
}
Step 3: Create a Web Socket & STOMP Messaging Configuration
package com.kswaughs.cricket.config;
import org.springframework.context.annotation.Configuration;
import org.springframework.messaging.simp.config.MessageBrokerRegistry;
import org.springframework.web.socket.config.annotation.AbstractWebSocketMessageBrokerConfigurer;
import org.springframework.web.socket.config.annotation.EnableWebSocketMessageBroker;
import org.springframework.web.socket.config.annotation.StompEndpointRegistry;
@Configuration
@EnableWebSocketMessageBroker
public class LiveScoreSocketConfig extends AbstractWebSocketMessageBrokerConfigurer {
@Override
public void configureMessageBroker(MessageBrokerRegistry config) {
config.enableSimpleBroker("/topic");
config.setApplicationDestinationPrefixes("/app");
}
@Override
public void registerStompEndpoints(StompEndpointRegistry registry) {
registry.addEndpoint("/livescore-websocket").setAllowedOrigins("*"). withSockJS();
}
}
Step 4: Create a Message handling controller
package com.kswaughs.cricket.controller;
import java.util.List;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.messaging.handler.annotation.MessageMapping;
import org.springframework.messaging.handler.annotation.SendTo;
import org.springframework.stereotype.Controller;
import com.kswaughs.cricket.beans.Batsman;
import com.kswaughs.cricket.service.LiveScoreService;
@Controller
public class LiveCricketController {
@Autowired
private LiveScoreService service;
@MessageMapping("/score")
@SendTo("/topic/myscores")
public List<Batsman> getScores() {
List<Batsman> scoresList = service.getScore();
return scoresList;
}
}
Step 5: Business Logic Implementation
Create a business logic component to get the live updates from back-end service. In this example, this class initializes the list of batsman objects with pre-filled data and later for every request, it will increments the runs & balls by 1 of any one batsman randomly.
package com.kswaughs.cricket.service;
import java.util.ArrayList;
import java.util.List;
import java.util.Random;
import org.springframework.stereotype.Component;
import com.kswaughs.cricket.beans.Batsman;
@Component
public class LiveScoreService {
private List<Batsman> scoresList = initialScores();
public List<Batsman> getScore() {
Random rand = new Random();
int randomNum = rand.nextInt((2 - 1) + 1);
Batsman batsman = scoresList.get(randomNum);
batsman.setBalls(batsman.getBalls() + 1);
batsman.setRuns(batsman.getRuns() + 1);
return scoresList;
}
private List<Batsman> initialScores() {
Batsman sachin = new Batsman();
sachin.setName("Sachin Tendulkar");
sachin.setRuns(24);
sachin.setBalls(26);
Batsman ganguly = new Batsman();
ganguly.setName("Sourav Ganguly");
ganguly.setRuns(28);
ganguly.setBalls(30);
List<Batsman> scoresList = new ArrayList<Batsman>();
scoresList.add(sachin);
scoresList.add(ganguly);
return scoresList;
}
}
Step 6: Scheduler Configuration
Configure a Scheduler task to send the updated scores to subscribed channel. The task is configured to run every 5 seconds.
package com.kswaughs.cricket.config;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.messaging.simp.SimpMessagingTemplate;
import org.springframework.scheduling.annotation.EnableScheduling;
import org.springframework.scheduling.annotation.Scheduled;
import com.kswaughs.cricket.service.LiveScoreService;
@Configuration
@EnableScheduling
public class ScoreScheduler {
@Autowired
private SimpMessagingTemplate template;
@Autowired
LiveScoreService service;
@Scheduled(fixedRate = 5000)
public void publishUpdates(){
template.convertAndSend("/topic/myscores", service.getScore());
}
}
Step 7 : Create a main class which will initialize and runs the spring boot application.
package com.kswaughs.cricket.config;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.ComponentScan;
@SpringBootApplication
@ComponentScan({ "com.kswaughs.cricket" })
public class BootApp {
public static void main(String[] args) {
SpringApplication.run(new Object[] { BootApp.class }, args);
}
}
Testing : AngularJS WebSocket Example
Now we will connect to this application from HTML Page using AngularJS Stomp component and display the live score updates. You can download ng-stomp.standalone.min.js from https://github.com/beevelop/ng-stomp.
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>WebSocket Client</title>
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.5.5/angular.min.js"></script>
<script src="ng-stomp.standalone.min.js"></script>
<script type="text/javascript">
var app = angular.module('kswaughsLiveScore', ['ngStomp']);
app.controller('LiveController', function ($stomp, $scope) {
$scope.myres = [];
$stomp.connect('http://localhost:8080/livescore-websocket', {})
.then(function (frame) {
var subscription = $stomp.subscribe('/topic/myscores',
function (payload, headers, res) {
$scope.myres = payload;
$scope.$apply($scope.myres);
});
$stomp.send('/app/score', '');
});
});
</script>
<style>
.liveScore{
color : blue;
}
li{
list-style: none;
padding:0px 0px 10px 0px;
}
</style>
</head>
<body >
<div class="liveScore" ng-app="kswaughsLiveScore" ng-controller="LiveController">
<p>Cricket - Live Score</p>
<ul>
<li ng-repeat="x in myres">{{$index+1}} - {{x.name}} - <b>{{x.runs}}</b> runs (<b>{{x.balls}}</b>balls)</li>
</ul>
</div>
</body>
</html>
Output
Cricket - Live Score
- 1 - Sachin Tendulkar - 24 runs ( 26 balls)
- 2 - Sourav Ganguly - 28 runs ( 30 balls)
The above scores gets updated every 5 secs when the server application runs.