Использование WebSocket для создания интерактивного веб-приложения

by moiseevrus

Contents

Seems your browser doesn’t support Javascript! Websocket relies on Javascript being
enabled. Please enable
Javascript and reload this page!

Seems your browser doesn’t support Javascript! Websocket relies on Javascript being
enabled. Please enable
Javascript and reload this page!

Seems your browser doesn’t support Javascript! Websocket relies on Javascript being
enabled. Please enable
Javascript and reload this page!

Это руководство проведет вас через процесс создания приложения «Hello, world», которое отправляет сообщения туда и обратно между браузером и сервером. WebSocket — это тонкий и легкий слой поверх TCP. Это делает его пригодным для использования «подпротоколов» для встраивания сообщений. В этом руководстве мы используем обмен сообщениями STOMP с Spring для создания интерактивного веб-приложения. STOMP — это подпротокол, работающий поверх WebSocket более низкого уровня.

Что вы будете строить

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

Что вам нужно

Как заполнить это руководство

Как и в большинстве руководств Spring Getting Started , вы можете начать с нуля и выполнить каждый шаг или обойти уже знакомые вам основные этапы настройки. В любом случае вы получите рабочий код.

Чтобы начать с нуля , перейдите к разделу Starting with Spring Initializr .

Чтобы пропустить основы , сделайте следующее:

Когда вы закончите , вы можете сравнить свои результаты с кодом в gs-messaging-stomp-websocket/complete.

Начиная с Spring Initializr

Вы можете использовать этот предварительно инициализированный проект и нажать «Создать», чтобы загрузить ZIP-файл. Этот проект настроен так, чтобы соответствовать примерам в этом руководстве.

Чтобы вручную инициализировать проект:

  1. Перейдите на https://start.spring.io . Этот сервис извлекает все зависимости, необходимые для приложения, и выполняет большую часть настройки за вас.
  2. Выберите Gradle или Maven и язык, который вы хотите использовать. В этом руководстве предполагается, что вы выбрали Java.
  3. Щелкните Зависимости и выберите Websocket .
  4. Нажмите «Создать» .
  5. Загрузите полученный ZIP-файл, представляющий собой архив веб-приложения, настроенного с учетом ваших предпочтений.
Если в вашей среде IDE есть интеграция с Spring Initializr, вы можете выполнить этот процесс в своей среде IDE.
Вы также можете разветвить проект из Github и открыть его в своей среде IDE или другом редакторе.

Добавление зависимостей

В этом случае Spring Initializr не предоставляет всего, что вам нужно. Для Maven нужно добавить следующие зависимости:

<dependency>
  <groupId>org.webjars</groupId>
  <artifactId>webjars-locator-core</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.1-1</version>
</dependency>

В следующем листинге показан готовый pom.xmlфайл:

<?xml version="1.0" encoding="UTF-8"?>
<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 https://maven.apache.org/xsd/maven-4.0.0.xsd">
	<modelVersion>4.0.0</modelVersion>
	<parent>
		<groupId>org.springframework.boot</groupId>
		<artifactId>spring-boot-starter-parent</artifactId>
		<version>2.7.1</version>
		<relativePath/> <!-- lookup parent from repository -->
	</parent>
	<groupId>com.example</groupId>
	<artifactId>messaging-stomp-websocket-complete</artifactId>
	<version>0.0.1-SNAPSHOT</version>
	<name>messaging-stomp-websocket-complete</name>
	<description>Demo project for Spring Boot</description>
	<properties>
		<java.version>1.8</java.version>
	</properties>
	<dependencies>
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-websocket</artifactId>
		</dependency>
		<dependency>
			<groupId>org.webjars</groupId>
			<artifactId>webjars-locator-core</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.1-1</version>
		</dependency>

		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-test</artifactId>
			<scope>test</scope>
		</dependency>
	</dependencies>

	<build>
		<plugins>
			<plugin>
				<groupId>org.springframework.boot</groupId>
				<artifactId>spring-boot-maven-plugin</artifactId>
			</plugin>
		</plugins>
	</build>

</project>

Если вы используете Gradle, вам необходимо добавить следующие зависимости:

implementation 'org.webjars:webjars-locator-core'
implementation 'org.webjars:sockjs-client:1.0.2'
implementation 'org.webjars:stomp-websocket:2.3.3'
implementation 'org.webjars:bootstrap:3.3.7'
implementation 'org.webjars:jquery:3.1.1-1'

В следующем листинге показан готовый build.gradleфайл:

plugins {
	id 'org.springframework.boot' version '2.7.1'
	id 'io.spring.dependency-management' version '1.0.11.RELEASE'
	id 'java'
}

group = 'com.example'
version = '0.0.1-SNAPSHOT'
sourceCompatibility = '1.8'

repositories {
	mavenCentral()
}

dependencies {
	implementation 'org.springframework.boot:spring-boot-starter-websocket'
	implementation 'org.webjars:webjars-locator-core'
	implementation 'org.webjars:sockjs-client:1.0.2'
	implementation 'org.webjars:stomp-websocket:2.3.3'
	implementation 'org.webjars:bootstrap:3.3.7'
	implementation 'org.webjars:jquery:3.1.1-1'
	testImplementation 'org.springframework.boot:spring-boot-starter-test'
}

test {
	useJUnitPlatform()
}

Создайте класс представления ресурсов

Теперь, когда вы настроили проект и систему сборки, вы можете создать службу сообщений STOMP.

Начните процесс с размышлений о сервисных взаимодействиях.

Служба будет принимать сообщения, содержащие имя в сообщении STOMP, тело которого является объектом JSON. Если имя Fred, сообщение может выглядеть следующим образом:

{
    "name": "Fred"
}

Чтобы смоделировать сообщение, которое несет это имя, вы можете создать простой старый объект Java со nameсвойством и соответствующим getName()методом, как src/main/java/com/example/messagingstompwebsocket/HelloMessage.javaпоказано в следующем листинге (из ):

package com.example.messagingstompwebsocket;

public class HelloMessage {

  private String name;

  public HelloMessage() {
  }

  public HelloMessage(String name) {
    this.name = name;
  }

  public String getName() {
    return name;
  }

  public void setName(String name) {
    this.name = name;
  }
}

После получения сообщения и извлечения имени служба обработает его, создав приветствие и опубликовав это приветствие в отдельной очереди, на которую подписан клиент. Приветствие также будет объектом JSON, как показано в следующем листинге:

{
    "content": "Hello, Fred!"
}

Чтобы смоделировать представление приветствия, добавьте еще один простой старый объект Java со contentсвойством и соответствующим getContent()методом, как src/main/java/com/example/messagingstompwebsocket/Greeting.javaпоказано в следующем листинге (из ):

package com.example.messagingstompwebsocket;

public class Greeting {

  private String content;

  public Greeting() {
  }

  public Greeting(String content) {
    this.content = content;
  }

  public String getContent() {
    return content;
  }

}

Spring будет использовать библиотеку Jackson JSON для автоматического маршалинга экземпляров типа Greetingв JSON.

Далее вы создадите контроллер для получения приветственного сообщения и отправки приветственного сообщения.

Создайте контроллер обработки сообщений

В подходе Spring к работе с обменом сообщениями STOMP сообщения STOMP могут направляться в @Controllerклассы. Например, GreetingController(from src/main/java/com/example/messagingstompwebsocket/GreetingController.java) отображается для обработки сообщений /helloадресату, как показано в следующем листинге:

package com.example.messagingstompwebsocket;

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

@Controller
public class GreetingController {


  @MessageMapping("/hello")
  @SendTo("/topic/greetings")
  public Greeting greeting(HelloMessage message) throws Exception {
    Thread.sleep(1000); // simulated delay
    return new Greeting("Hello, " + HtmlUtils.htmlEscape(message.getName()) + "!");
  }

}

Этот контроллер лаконичен и прост, но много чего происходит. Разбираем поэтапно.

Аннотация @MessageMappingгарантирует, что при отправке сообщения /helloполучателю greeting()вызывается метод.

Полезная нагрузка сообщения привязывается к HelloMessageобъекту, который передается в greeting().

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

После задержки в одну секунду greeting()метод создает Greetingобъект и возвращает его. Возвращаемое значение транслируется всем подписчикам /topic/greetings, как указано в @SendToаннотации. Обратите внимание, что имя из входного сообщения очищается, так как в этом случае оно будет возвращено обратно и повторно отображено в DOM браузера на стороне клиента.

Seems your browser doesn’t support Javascript! Websocket relies on Javascript being
enabled. Please enable
Javascript and reload this page!

Настройте Spring для обмена сообщениями STOMP

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

Создайте класс Java с именем WebSocketConfig, похожим на следующий листинг (из src/main/java/com/example/messagingstompwebsocket/WebSocketConfig.java):

package com.example.messagingstompwebsocket;

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("/app");
  }

  @Override
  public void registerStompEndpoints(StompEndpointRegistry registry) {
    registry.addEndpoint("/gs-guide-websocket").withSockJS();
  }

}

WebSocketConfigаннотируется @Configuration, чтобы указать, что это класс конфигурации Spring. Он также снабжен аннотацией @EnableWebSocketMessageBroker. Как следует из названия, @EnableWebSocketMessageBrokerвключает обработку сообщений WebSocket, поддерживаемую брокером сообщений.

Метод configureMessageBroker()реализует метод по умолчанию WebSocketMessageBrokerConfigurerдля настройки брокера сообщений. Он начинается с вызова enableSimpleBroker(), чтобы позволить простому брокеру сообщений на основе памяти передавать приветственные сообщения обратно клиенту по пунктам назначения с префиксом /topic. Он также обозначает /appпрефикс для сообщений, связанных с методами, аннотированными с помощью @MessageMapping. Этот префикс будет использоваться для определения всех сопоставлений сообщений. Например, /app/helloэто конечная точка, на которую GreetingController.greeting()сопоставляется метод.

Метод registerStompEndpoints()регистрирует /gs-guide-websocketконечную точку, включая резервные варианты SockJS, чтобы можно было использовать альтернативные транспорты, если WebSocket недоступен. Клиент SockJS попытается подключиться /gs-guide-websocketи использовать наилучший доступный транспорт (websocket, xhr-streaming, xhr-polling и т. д.).

Seems your browser doesn’t support Javascript! Websocket relies on Javascript being
enabled. Please enable
Javascript and reload this page!

Создать браузерный клиент

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

Создайте index.htmlфайл, аналогичный следующему листингу (из src/main/resources/static/index.html):

<!DOCTYPE html>
<html>
<head>
    <title>Hello 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>
<noscript><h2 style="color: #ff0000">Seems your browser doesn't support Javascript! Websocket relies on Javascript being
    enabled. Please enable
    Javascript and reload this page!</h2></noscript>
<div id="main-content" class="container">
    <div class="row">
        <div class="col-md-6">
            <form class="form-inline">
                <div class="form-group">
                    <label for="connect">WebSocket 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-6">
            <form class="form-inline">
                <div class="form-group">
                    <label for="name">What is your name?</label>
                    <input type="text" id="name" class="form-control" placeholder="Your name here...">
                </div>
                <button id="send" class="btn btn-default" type="submit">Send</button>
            </form>
        </div>
    </div>
    <div class="row">
        <div class="col-md-12">
            <table id="conversation" class="table table-striped">
                <thead>
                <tr>
                    <th>Greetings</th>
                </tr>
                </thead>
                <tbody id="greetings">
                </tbody>
            </table>
        </div>
    </div>
</div>
</body>
</html>

Этот файл HTML импортирует библиотеки SockJSи STOMPбиблиотеки javascript, которые будут использоваться для связи с нашим сервером через STOMP через веб-сокет. Мы также импортируем app.js, который содержит логику нашего клиентского приложения. Следующий листинг (из src/main/resources/static/app.js) показывает этот файл:

var stompClient = null;

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

function connect() {
    var socket = new SockJS('/gs-guide-websocket');
    stompClient = Stomp.over(socket);
    stompClient.connect({}, function (frame) {
        setConnected(true);
        console.log('Connected: ' + frame);
        stompClient.subscribe('/topic/greetings', function (greeting) {
            showGreeting(JSON.parse(greeting.body).content);
        });
    });
}

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

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

function showGreeting(message) {
    $("#greetings").append("<tr><td>" + message + "</td></tr>");
}

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

Основные части этого файла JavaScript, которые нужно понять, — это функции connect()и sendName().

Функция connect()использует SockJS и stomp.js для открытия соединения с /gs-guide-websocket, где наш сервер SockJS ожидает соединений. При успешном соединении клиент подписывается на /topic/greetingsпункт назначения, где сервер будет публиковать приветственные сообщения. Когда приветствие получено в этом месте назначения, оно добавит элемент абзаца в DOM для отображения приветственного сообщения.

Функция sendName()извлекает имя, введенное пользователем, и использует клиент STOMP, чтобы отправить его по /app/helloназначению (где GreetingController.greeting()оно будет получено).

Можно main.cssопустить, если хотите, или вы можете создать пустую, чтобы <link>можно было разрешить.

Сделать приложение исполняемым

Spring Boot создает для вас класс приложения. В этом случае дальнейшая модификация не требуется. Вы можете использовать его для запуска этого приложения. В следующем листинге (из src/main/java/com/example/messagingstompwebsocket/MessagingStompWebsocketApplication.java) показан класс приложения:

package com.example.messagingstompwebsocket;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class MessagingStompWebsocketApplication {

  public static void main(String[] args) {
    SpringApplication.run(MessagingStompWebsocketApplication.class, args);
  }
}

@SpringBootApplicationэто удобная аннотация, которая добавляет все следующее:

  • @Configuration: помечает класс как источник определений bean-компонентов для контекста приложения.
  • @EnableAutoConfiguration: Сообщает Spring Boot о начале добавления bean-компонентов на основе настроек пути к классам, других bean-компонентов и различных настроек свойств. Например, если spring-webmvcон находится в пути к классам, эта аннотация помечает приложение как веб-приложение и активирует основные функции, такие как настройка файла DispatcherServlet.
  • @ComponentScan: говорит Spring искать другие компоненты, конфигурации и сервисы в com/exampleпакете, позволяя ему найти контроллеры.

Метод main()использует метод Spring Boot SpringApplication.run()для запуска приложения. Вы заметили, что не было ни одной строчки XML? web.xmlФайла тоже нет . Это веб-приложение на 100 % состоит из чистой Java, и вам не нужно заниматься настройкой какой-либо сантехники или инфраструктуры.

Создайте исполняемый файл JAR

Вы можете запустить приложение из командной строки с помощью Gradle или Maven. Вы также можете создать один исполняемый файл JAR, содержащий все необходимые зависимости, классы и ресурсы, и запустить его. Создание исполняемого jar-файла упрощает отправку, версию и развертывание службы как приложения на протяжении всего жизненного цикла разработки, в различных средах и т. д.

Если вы используете Gradle, вы можете запустить приложение с помощью ./gradlew bootRun. Кроме того, вы можете создать файл JAR, используя ./gradlew buildи затем запустив файл JAR, следующим образом:

Java-jar build/libs/gs-messaging-stomp-websocket-0.1.0.jar

Если вы используете Maven, вы можете запустить приложение с помощью ./mvnw spring-boot:run. Кроме того, вы можете создать файл JAR с помощью ./mvnw clean packageи затем запустить файл JAR следующим образом:

Java-jar target/gs-messaging-stomp-websocket-0.1.0.jar
Описанные здесь шаги создают исполняемый файл JAR. Вы также можете создать классический файл WAR .

Отображается вывод журнала. Служба должна быть запущена в течение нескольких секунд.

Протестируйте сервис

Теперь, когда служба запущена, укажите в браузере адрес http://localhost:8080 и нажмите кнопку « Подключиться ».

При открытии соединения вас попросят ввести ваше имя. Введите свое имя и нажмите Отправить . Ваше имя отправляется на сервер в виде сообщения JSON через STOMP. После имитации задержки в одну секунду сервер отправляет обратно сообщение с приветствием «Привет», которое отображается на странице. На этом этапе вы можете отправить другое имя или нажать кнопку « Отключить » , чтобы закрыть соединение.

You may also like

Leave a Comment