The WebSocket protocol provides new capability for web applications: full-duplex, two-way communication. So in the system where the client and server need to exchange data at high frequency and with low latency, WebSocket is the best solution. In this tutorial, we’re gonna create an Angular 6 WebSocket Client to send/receive message with a Spring Boot Server.
WebSocket Application
Flow of messages
We create a Spring WebSocket Application with the flow of messages:
– WebSocket clients connect to the WebSocket endpoint at /gkz-stomp-endpoint
– Subscriptions to /topic/hi
pass through the response channel, then are forwarded to the In-memory broker (Simple Broker).
– User objects sent to /gkz/hello
pass through the request channel then are forwarded to the spring WebController. WebController will handle User
objects by @MessageMapping
and transform to Hello
messages then use @SendTo
returns the messages to /topic/hi
through the brokerChannel.
@MessageMapping("/hello") @SendTo("/topic/hi") public Hello greeting(User user) throws Exception { return new Hello(...); }
– The Simple Broker broadcasts messages to subscribers through the response channel.
Server side
In server side, we use SockJS and STOMP for our application.
What is SockJS?
-> SockJS lets applications use a WebSocket API but falls back to non-WebSocket alternatives when necessary at runtime, without the need to change application code.
We create a simple Java configuration to enable SockJS and Stomp in Spring application:
@Configuration @EnableWebSocketMessageBroker public class WebSocketConfig implements WebSocketMessageBrokerConfigurer { @Override public void configureMessageBroker(MessageBrokerRegistry config) { config.enableSimpleBroker("/topic"); config.setApplicationDestinationPrefixes("/gkz"); } @Override public void registerStompEndpoints(StompEndpointRegistry registry) { registry .addEndpoint("/gkz-stomp-endpoint") .setAllowedOrigins("http://localhost:4200") .withSockJS(); } }
Client side
We uses {sockjs-client, stompjs} for development on Client side:
import * as Stomp from 'stompjs'; import * as SockJS from 'sockjs-client';
– Make a connection:
connect() { const socket = new SockJS('http://localhost:8080/gkz-stomp-endpoint'); this.stompClient = Stomp.over(socket); const _this = this; this.stompClient.connect({}, function (frame) { _this.stompClient.subscribe('/topic/hi', function (hello) { _this.showGreeting(JSON.parse(hello.body).greeting); }); }); }
connect()
function uses SockJS
and Stomp
to open a connection to /gkz-stomp-endpoint
, which is where our SockJS
server is waiting for connections.
– Disconnection:
disconnect() { if (this.stompClient != null) { this.stompClient.disconnect(); } }
– Send messages:
sendName() { this.stompClient.send( '/gkz/hello', {}, JSON.stringify({ 'name': this.name }) ); }
Practice
Technologies
– Java 8
– Maven 3.3.9
– Spring Tool Suite 3.9.0.RELEASE
– Spring Boot: 2.0.4.RELEASE
– Spring WebSocket
– sockjs-client 1.1.5
– @stomp/stompjs 4.0.7
– Angular 6
– Bootstrap
1. Server side
1.1 Create SpringBoot project
Using Spring Tool Suite to create a Spring Starter Project, then add dependencies:
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-websocket</artifactId> </dependency>
1.2 Create models
Create 2 message models {User, Hello}
– User:
package com.ozenero.spring.websocket.model; public class User { private String name; public User() { } public void setName(String name) { this.name = name; } public String getName() { return this.name; } }
– Hello:
package com.ozenero.spring.websocket.model; public class Hello { private String greeting; public Hello() { } public Hello(String greeting) { this.greeting = greeting; } public void setGreeting(String greeting) { this.greeting = greeting; } public String getGreeting() { return this.greeting; } }
1.3 Configure SockJS and STOMP messaging
package com.ozenero.spring.websocket.config; import org.springframework.context.annotation.Configuration; import org.springframework.messaging.simp.config.MessageBrokerRegistry; import org.springframework.web.socket.config.annotation.EnableWebSocketMessageBroker; import org.springframework.web.socket.config.annotation.StompEndpointRegistry; import org.springframework.web.socket.config.annotation.WebSocketMessageBrokerConfigurer; @Configuration @EnableWebSocketMessageBroker public class WebSocketConfig implements WebSocketMessageBrokerConfigurer { @Override public void configureMessageBroker(MessageBrokerRegistry config) { config.enableSimpleBroker("/topic"); config.setApplicationDestinationPrefixes("/gkz"); } @Override public void registerStompEndpoints(StompEndpointRegistry registry) { registry .addEndpoint("/gkz-stomp-endpoint") .setAllowedOrigins("http://localhost:4200") .withSockJS(); } }
– @EnableWebSocketMessageBroker
enables WebSocket message handling, backed by a message broker.
– enableSimpleBroker()
enables a simple memory-based message broker.
– registerStompEndpoints()
is for registering the /gkz-stomp-endpoint
endpoint, enabling SockJS fallback options – an alternate transports in case WebSocket is not available. We also configure allowed Origin header values for browser clients.
1.4 Create WebController
package com.ozenero.spring.websocket.controller; import org.springframework.messaging.handler.annotation.MessageMapping; import org.springframework.messaging.handler.annotation.SendTo; import org.springframework.stereotype.Controller; import com.ozenero.spring.websocket.model.Hello; import com.ozenero.spring.websocket.model.User; @Controller public class WebController { @MessageMapping("/hello") @SendTo("/topic/hi") public Hello greeting(User user) throws Exception { return new Hello("Hi, " + user.getName() + "!"); } }
User objects are sent to /gkz/hello
pass through the request channel then are forwarded to the Spring WebController.
WebController will handle User
objects by @MessageMapping
and transform to Hello
messages then use @SendTo
to return the messages to ‘/topic/hi’ through the brokerChannel.
2. Client side
2.1 Create Angular project
– Using Angular CLI to create new Project: ng new Angular6WebSocket
.
– Install @stomp/stompjs and socksjs:
npm install @stomp/stompjs
npm install sockjs-client
– Add this code in the first line of polyfills.ts file:
(window as any).global = window;
2.2 Add FormsModule
app.module.ts
import { BrowserModule } from '@angular/platform-browser'; import { NgModule } from '@angular/core'; import { FormsModule } from '@angular/forms'; import { AppComponent } from './app.component'; @NgModule({ declarations: [ AppComponent ], imports: [ BrowserModule, FormsModule ], providers: [], bootstrap: [AppComponent] }) export class AppModule { }
2.3 HTML for UI
app.component.html
<!DOCTYPE html> <html> <head> <meta charset="utf-8"> <title>Spring Boot WebSocket</title> <base href="/"> <meta name="viewport" content="width=device-width, initial-scale=1"> <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css"> </head> <body> <div style="color: blue; text-align: center"> <h1>{{title}}</h1> <h3>{{description}}</h3> </div> <div class="container" style="width: 400px; margin-top: 20px;"> <form class="form-inline"> <div class="form-group"> <label for="connect">Make Connection:</label> <button id="connect" class="btn btn-default" type="button" [disabled]="!disabled" (click)="connect()">Connect</button> <button id="disconnect" class="btn btn-default" type="submit" [disabled]="disabled" (click)="disconnect()">Disconnect</button> </div> </form> <form class="form-inline" style="margin-top: 20px;"> <div class="form-group"> <label for="name">User's Name:</label> <input type="text" id="name" name="name" class="form-control" [(ngModel)]="name" /> </div> <button id="send" class="btn btn-default" type="button" (click)="sendName()">Send</button> </form> <table id="conversation" class="table table-striped" style="margin-top: 20px;"> <thead> <tr> <th>Greetings</th> </tr> </thead> <tbody *ngFor="let greeting of greetings"> <tr> <td>{{greeting}}</td> </tr> </tbody> </table> </div> </body> </html>
2.4 WebSocket functions
app.component.ts
import { Component } from '@angular/core'; import * as Stomp from '@stomp/stompjs'; import * as SockJS from 'sockjs-client'; @Component({ selector: 'app-root', templateUrl: './app.component.html', styleUrls: ['./app.component.css'] }) export class AppComponent { title = 'ozenero'; description = 'Angular-WebSocket Demo'; greetings: string[] = []; disabled = true; name: string; private stompClient = null; constructor() { } setConnected(connected: boolean) { this.disabled = !connected; if (connected) { this.greetings = []; } } connect() { const socket = new SockJS('http://localhost:8080/gkz-stomp-endpoint'); this.stompClient = Stomp.over(socket); const _this = this; this.stompClient.connect({}, function (frame) { _this.setConnected(true); console.log('Connected: ' + frame); _this.stompClient.subscribe('/topic/hi', function (hello) { _this.showGreeting(JSON.parse(hello.body).greeting); }); }); } disconnect() { if (this.stompClient != null) { this.stompClient.disconnect(); } this.setConnected(false); console.log('Disconnected!'); } sendName() { this.stompClient.send( '/gkz/hello', {}, JSON.stringify({ 'name': this.name }) ); } showGreeting(message) { this.greetings.push(message); } }
Run and check results
– SpringBoot project with command-lines: mvn clean install
and mvn spring-boot:run
.
– Angular project: npm install
and npm start
.
Open browser with url http://localhost:4200/
.
Click on Connect button and send User’s Name.
Then click on Disconnect button.
Source code
– SpringWebSocket-Server
– Angular6Websocket-Client