Spring Boot WebSocket with Angular 5 Client | SockJS + STOMP

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 a Spring Boot Application that uses WebSocket protocol to communicate with Angular 5 Client.

Update: Angular 6 WebSocket example with Spring Boot WebSocket Server | SockJS + STOMP

Spring WebSocket Application

Flow of messages

We create a Spring WebSocket Application with the flow of messages:

spring-websocket-architecture-ws

– WebSocket clients connect to the WebSocket endpoint at /jsa-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 /jsa/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("/jsa");
	}

	@Override
	public void registerStompEndpoints(StompEndpointRegistry registry) {
		registry
		.addEndpoint("/jsa-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/jsa-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 /jsa-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(
    '/jsa/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.2.RELEASE
– Spring WebSocket

– sockjs-client 1.1.4
– stompjs 2.3.3
– Angular 5
– Bootstrap

1. Server side

angular-6-websocket-example-spring-websocket-server-structure

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.javasampleapproach.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.javasampleapproach.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.javasampleapproach.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("/jsa");
	}

	@Override
	public void registerStompEndpoints(StompEndpointRegistry registry) {
		registry
		.addEndpoint("/jsa-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 /jsa-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.javasampleapproach.spring.websocket.controller;

import org.springframework.messaging.handler.annotation.MessageMapping;
import org.springframework.messaging.handler.annotation.SendTo;
import org.springframework.stereotype.Controller;

import com.javasampleapproach.spring.websocket.model.Hello;
import com.javasampleapproach.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 /jsa/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 AngularWebSocket.
– Install stompjs and socksjs:
npm install stompjs
npm install sockjs-client

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 'stompjs';
import * as SockJS from 'sockjs-client';

@Component({
  selector: 'app-root',
  templateUrl: './app.component.html',
  styleUrls: ['./app.component.css']
})
export class AppComponent {

  title = 'JavaSampleApproach';
  description = 'Angular-WebSocket Demo';

  greetings: string[] = [];
  disabled: boolean;
  name: string;
  private stompClient = null;

  constructor() { }

  setConnected(connected: boolean) {
    this.disabled = !connected;

    if (connected) {
      this.greetings = [];
    }
  }

  connect() {
    const socket = new SockJS('http://localhost:8080/jsa-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(
      '/jsa/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.

spring-websocket-angular-result-final

Source code

SpringBootWebSocket
AngularWebSocket

Leave a Reply

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