Статьи

Использование Websocket со Spring Framework и Vuejs

Веб-сокеты представляют собой полнодуплексные (постоянные) соединения между клиентом и сервером, так что оба могут обмениваться информацией друг с другом без необходимости повторного установления нового соединения. Это устраняет необходимость повторного опроса клиента для получения обновлений с сервера.

Не все браузеры поддерживают Websockets, и поэтому мы используем библиотеку JavaScript SockJS для создания соединений WebSocket. SockJS действует как уровень абстракции, который сначала проверяет, есть ли встроенная поддержка WebSockets, если нет поддержки, он будет пытаться имитировать поведение, подобное WebSocket, используя протоколы, поддерживаемые браузером.

Spring обеспечивает поддержку Websocket с использованием протокола STOMP , поэтому мы будем использовать STOMP.js , реализацию javascript для протокола STOMP, для взаимодействия с сервером.

В этом посте клиент установит соединение веб-сокета с сервером и вызовет конечные точки веб-сокета, зарегистрированные в серверном приложении, для получения некоторых сообщений. В дополнение к этому сервер будет отправлять клиенту некоторые сообщения в реальном времени о фоновых действиях, запущенных на сервере.

Давайте сначала настроим сервер. Сначала зайдите в start.spring.io и создайте новый весенний загрузочный проект, используя следующие настройки:

Настройка Websocket

Базовая конфигурация веб-сокета включает в себя:

  • создание адреса темы для размещения сообщений ( /topic/messages )
  • необязательный префикс для URL-адресов, используемых клиентом для вызова конечных точек WebSocket на сервере ( /ws )
  • определение URL-адреса, используемого клиентом для настройки соединения WebSocket с сервером. ( /connect )
01
02
03
04
05
06
07
08
09
10
11
12
13
14
@Configuration
@EnableWebSocketMessageBroker
public class WebsocketConfiguration implements WebSocketMessageBrokerConfigurer {
    @Override
    public void registerStompEndpoints(StompEndpointRegistry registry) {
        registry.addEndpoint("/connect").withSockJS();
    }
 
    @Override
    public void configureMessageBroker(MessageBrokerRegistry registry) {
        registry.enableSimpleBroker("/topic/messages");
        registry.setApplicationDestinationPrefixes("/ws");
    }
}

Создание конечных точек Websocket

Мы создадим контроллер Spring, который будет иметь две конечные точки WebSocket, как показано ниже. Одна из этих конечных точек создаст бесконечно работающую задачу, отправляющую сообщения клиенту, а другая конечная точка отменит запущенную задачу.

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
@Controller
public class WebsocketController {
 
    @Autowired SimpMessagingTemplate simpMessagingTemplate;
    String destination = "/topic/messages";
 
    ExecutorService executorService =
            Executors.newFixedThreadPool(1);
    Future<?> submittedTask;
 
    @MessageMapping("/start")
    public void startTask(){
        if ( submittedTask != null ){
            simpMessagingTemplate.convertAndSend(destination,
                    "Task already started");
            return;
        }
        simpMessagingTemplate.convertAndSend(destination,
                "Started task");
        submittedTask = executorService.submit(() -> {
            while(true){
                simpMessagingTemplate.convertAndSend(destination,
                        LocalDateTime.now().toString()
                                +": doing some work");
                Thread.sleep(10000);
            }
        });
    }
 
    @MessageMapping("/stop")
    @SendTo("/topic/messages")
    public String stopTask(){
        if ( submittedTask == null ){
            return "Task not running";
        }
        try {
            submittedTask.cancel(true);
        }catch (Exception ex){
            ex.printStackTrace();
            return "Error occurred while stopping task due to: "
                    + ex.getMessage();
        }
        return "Stopped task";
    }
}

Я использовал два подхода выше, чтобы отправить сообщение на URL темы, определенный в нашей конфигурации:

  1. по возвращаемому значению метода, аннотированного как @MessageMapping
  2. используя SimpMessagingTemplate

Spring boot настраивает экземпляр SimpMessagingTemplate который мы можем использовать для отправки сообщений в тему.

Конечные точки веб-сокета аннотируются с помощью @MessageMapping , передавая URL-адрес конечной точки так же, как мы определяем конечные точки REST API или видим конечные точки.

Создание клиента Websocket в Javascript

Сначала мы создадим страницу HTML, которая содержит кнопки для инициирования соединения, а затем вызовем конечные точки веб-сокета, которые мы определили, как показано ниже:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
<div class="content" id="websocket">
  <div> </div>
  <div class="row" >
    <div class="col">
      <button class="btn btn-sm btn-info" @click="connect">Create connection</button>
      <button class="btn btn-sm btn-success" @click="startTask">Start Task</button>
      <button class="btn btn-sm btn-danger" @click="stopTask">Stop Task</button>
      <button class="btn btn-sm btn-primary" @click="disconnect">Close connection</button>
    </div>
  </div>
  <div> </div>
  <div class="row">
    <div class="col">
      <ul class="list-group" style="height: 500px; overflow:scroll;">
        <li class="list-group-item d-flex justify-content-between align-items-center"
            v-for="(m,idx) in messages" :key="'m-'+idx">
          {{m}}
        </li>
      </ul>
    </div>
  </div>
</div>

Важно обратить внимание на библиотеки sockjs и STOMP js, связанные в HTML-коде выше.

Вся работа происходит в коде Javascript, как показано ниже:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
var stompClient = null;
$(function(){
    new Vue({
        el: "#websocket",
        data: {
            messages: []
        },
        methods: {
            connect: function(){
                var socket = new SockJS('/connect');
                stompClient = Stomp.over(socket);
                var that = this;
                stompClient.connect({}, function(frame) {
 
                    that.handleMessageReceipt("Connected");
                    stompClient.subscribe('/topic/messages',
                        function(messageOutput) {
                        that.handleMessageReceipt(messageOutput.body);
                    });
                });
            },
            disconnect: function(){
                if(stompClient != null) {
                    stompClient.disconnect();
                }
                this.handleMessageReceipt("Disconnected");
            },
            startTask: function(){
                if ( stompClient != null ){
                    stompClient.send("/ws/start");
                }else{
                    alert("Please connect first");
                }
            },
            stopTask: function(){
                if ( stompClient != null ){
                    stompClient.send("/ws/stop");
                }else{
                    alert("Please connect first");
                }
            },
            handleMessageReceipt: function (messageOutput) {
                this.messages.push(messageOutput);
            }
        }
    });
});

Метод connect инициирует подключение через веб-сокет с помощью конечной точки /connect . Методы запуска задачи и остановки вызывают две конечные точки websocket, которые мы определили в WebsocketController

Сообщения, полученные stompClient , передаются методом handleMessageReceipt.

После запуска приложения вы можете создать соединение, запустить задачу, остановить задачу и распечатать сообщения, как показано ниже:

Код для полной заявки можно найти здесь .

Опубликовано на Java Code Geeks с разрешения Мохамеда Санауллы, партнера нашей программы JCG . Смотрите оригинальную статью здесь: Использование Websocket с Spring Framework и Vuejs

Мнения, высказанные участниками Java Code Geeks, являются их собственными.