WebSocket – Create Spring WebSocket Application with SpringBoot + 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. That is the reason for us to create a Spring WebSocket Application with JavaSampleApproach.

Related Post: Spring Boot WebSocket with Angular 5 Client | SockJS + STOMP

I. Spring WebSocket Application

1. Flow of messages

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

spring-websocket-architecture-ws

Detail explanations:
– 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.

2. 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.

Simple Java configuration to enable SockJS and Stomp in Spring application:


@Configuration
@EnableWebSocketMessageBroker
public class WebSocketConfig extends AbstractWebSocketMessageBrokerConfigurer {

    @Override
    public void configureMessageBroker(MessageBrokerRegistry config) {
        config.enableSimpleBroker("/topic");
        config.setApplicationDestinationPrefixes("/jsa");
    }

    @Override
    public void registerStompEndpoints(StompEndpointRegistry registry) {
        registry.addEndpoint("/jsa-stomp-endpoint").withSockJS();
    }
}

*Update: with new Spring WebSocket release, AbstractWebSocketMessageBrokerConfigurer is deprecated, we will implement the WebSocketMessageBrokerConfigurer interface instead:


@Configuration
@EnableWebSocketMessageBroker
public class WebSocketConfig implements WebSocketMessageBrokerConfigurer {

	@Override
	public void configureMessageBroker(MessageBrokerRegistry config) {
		// ...
	}

	@Override
	public void registerStompEndpoints(StompEndpointRegistry registry) {
		// ...
	}
}

3. Client side

About client side, we uses {sockjs-client, stomp-websocket} libs for development:

<script src="/webjars/sockjs-client/sockjs.min.js"></script>
<script src="/webjars/stomp-websocket/stomp.min.js"></script>

– Make a connection:


function connect() {
    var socket = new SockJS('/jsa-stomp-endpoint');
    stompClient = Stomp.over(socket);
    stompClient.connect({}, function (frame) {
        setConnected(true);
        stompClient.subscribe('/topic/hi', function (hello) {
			...
        });
    });
}

The connect() function uses SockJS and stomp.js to open a connection to /jsa-stomp-endpoint, which is where our SockJS server is waiting for connections.

– Dis-Connection:


function disconnect() {
    if (stompClient != null) {
        stompClient.disconnect();
    }
    setConnected(false);
}

– Send messages:


function sendName() {
    stompClient.send("/jsa/hello", {}, JSON.stringify({'name': $("#name").val()}));
}

II. Practice

0. Technologies

– Java 8
– Maven 3.6.1
– Spring Tool Suite: Version 3.8.4.RELEASE
– Spring Boot: 1.5.4.RELEASE
– Spring WebSocket
– SockJS
– Stomp
– JQuery
– 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>

<dependency>
  <groupId>org.webjars</groupId>
  <artifactId>webjars-locator</artifactId>
</dependency>
<dependency>
  <groupId>org.webjars</groupId>
  <artifactId>sockjs-client</artifactId>
  <version>1.0.2</version>
</dependency>
<dependency>
  <groupId>org.webjars</groupId>
  <artifactId>stomp-websocket</artifactId>
  <version>2.3.3</version>
</dependency>
<dependency>
  <groupId>org.webjars</groupId>
  <artifactId>bootstrap</artifactId>
  <version>3.3.7</version>
</dependency>
<dependency>
  <groupId>org.webjars</groupId>
  <artifactId>jquery</artifactId>
  <version>3.1.0</version>
</dependency>

1.2 Create models

Create 2 message models {User, Hello}

– User:


package com.javasampleapproach.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.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.websocket.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 WebSocketConfig extends AbstractWebSocketMessageBrokerConfigurer {

    @Override
    public void configureMessageBroker(MessageBrokerRegistry config) {
        config.enableSimpleBroker("/topic");
        config.setApplicationDestinationPrefixes("/jsa");
    }

    @Override
    public void registerStompEndpoints(StompEndpointRegistry registry) {
        registry.addEndpoint("/jsa-stomp-endpoint").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.

1.4 Create WebController


package com.javasampleapproach.websocket.controller;

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

import com.javasampleapproach.websocket.model.Hello;
import com.javasampleapproach.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() + "!");
	}
}

As above explanation, 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 index.html

<!DOCTYPE html>
<html>
<head>
    <title>Spring Boot WebSocket!</title>
    <link href="/webjars/bootstrap/css/bootstrap.min.css" rel="stylesheet">
    <link href="/main.css" rel="stylesheet">
    <script src="/webjars/jquery/jquery.min.js"></script>
   <script src="/webjars/sockjs-client/sockjs.min.js"></script>
   <script src="/webjars/stomp-websocket/stomp.min.js"></script>
    <script src="/app.js"></script>
</head>
<body>
<div class="container">
    <div class="row">
        <div class="col-md-4">
            <form class="form-inline">
                <div class="form-group">
                    <label for="connect">Make Connection:</label>
                    <button id="connect" class="btn btn-default" type="submit">Connect</button>
                    <button id="disconnect" class="btn btn-default" type="submit" disabled="disabled">Disconnect
                    </button>
                </div>
            </form>
        </div>
        <div class="col-md-4">
            <form class="form-inline">
                <div class="form-group">
                    <label for="name">User's Name:</label>
                    <input type="text" id="name" class="form-control">
                </div>
                <button id="send" class="btn btn-default" type="submit">Send</button>
            </form>
        </div>
    </div>
    <div class="row">
        <div class="col-md-3">
            <table id="conversation" class="table table-striped">
                <thead>
	                <tr>
	                    <th>Greetings</th>
	                </tr>
                </thead>
                <tbody id="hellos">
                </tbody>
            </table>
        </div>
    </div>
</div>
</body>
</html>

2.2 Implement WebSocket javascript functions

We implement 3 main functions for connection, disconnection and send messages:


var stompClient = null;

function setConnected(connected) {
    $("#connect").prop("disabled", connected);
    $("#disconnect").prop("disabled", !connected);
    if (connected) {
        $("#conversation").show();
    }
    else {
        $("#conversation").hide();
    }
    $("#hellos").html("");
}

function connect() {
    var socket = new SockJS('/jsa-stomp-endpoint');
    stompClient = Stomp.over(socket);
    stompClient.connect({}, function (frame) {
        setConnected(true);
        console.log('Connected: ' + frame);
        stompClient.subscribe('/topic/hi', function (hello) {
            showGreeting(JSON.parse(hello.body).greeting);
        });
    });
}

function disconnect() {
    if (stompClient != null) {
        stompClient.disconnect();
    }
    setConnected(false);
    console.log("Disconnected");
}

function sendName() {
    stompClient.send("/jsa/hello", {}, JSON.stringify({'name': $("#name").val()}));
}

function showGreeting(greeting) {
    $("#hellos").append("" + greeting + "");
}

$(function () {
    $("form").on('submit', function (e) {
        e.preventDefault();
    });
    $( "#connect" ).click(function() { connect(); });
    $( "#disconnect" ).click(function() { disconnect(); });
    $( "#send" ).click(function() { sendName(); });
});

3. Run and check results

Build and Run the SpringBoot project with command-lines: mvn clean install and mvn spring-boot:run

3.1 Make a connection

Make a request at http://localhost:8080/

-> Result:

spring websocket application- index

3.2 Send messages

Press Connet button, and input JavaSampleApproach, then press Send button:

-> Result:

spring websocket application - index - first send

Open a new tab, make a request http://localhost:8080/, press Connet button, input JSA, then press Send button:

-> Result:

3.3 Dis-connection

Press Disconnect a client, all greetings messages at the client are removed.

-> Result:

spring websocket architecture - index - 3 send.png

III. Source code

SpringWebSocket



By grokonez | June 28, 2017.

Last updated on May 6, 2021.



Related Posts


3 thoughts on “WebSocket – Create Spring WebSocket Application with SpringBoot + SockJS + STOMP”

  1. Tried this guide, but I only get CORS error:

    Access to XMLHttpRequest at ‘http://localhost:8080/test-stomp-endpoint/info?t=1544554396549’ from origin ‘http://localhost:4200’ has been blocked by CORS policy: No ‘Access-Control-Allow-Origin’ header is present on the requested resource.

    1. This might be a little too late but for anyone else with this issue, the following works:

      @Override
      public void registerStompEndpoints(StompEndpointRegistry registry) {
          registry.addEndpoint("/jsa-stomp-endpoint").setAllowedOrigins("*").withSockJS();
      }
      

      This is allowing all origins though.

  2. I can’t find a way to pass the token on SockJS handshake. I am searching it for weeks and no one seems to have a solution.

Got Something To Say:

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

*