Введение

WebSockets обеспечивают полнодуплексный канал связи по одному долгоживущему соединению. Это мощный инструмент, обеспечивающий связь в реальном времени между клиентом и сервером. В экосистеме Java Spring Framework предлагает первоклассную поддержку связи на основе WebSocket. В этом сообщении блога мы рассмотрим, как начать работу с WebSocket в приложении Spring, используя аннотацию @EnableWebSocket.

Почему вебсокет?

Ограничения HTTP

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

Рождение WebSocket

WebSocket был введен для преодоления этих ограничений и обеспечения более интерактивного взаимодействия с Интернетом. Он устанавливает полнодуплексный двусторонний канал связи между клиентом и сервером через одно долговременное соединение. Это не похоже на HTTP, где каждое взаимодействие требует нового цикла запрос-ответ, часто через новое соединение. Сам протокол WebSocket построен на основе HTTP и использует первоначальное подтверждение HTTP для установления соединения WebSocket.

Снижение сетевых издержек

Одним из основных преимуществ WebSocket является снижение нагрузки на сеть. В HTTP каждый запрос сопровождается набором заголовков, содержащих метаданные о запросе. Эти заголовки отправляются каждый раз, когда делается запрос, что подходит для случайных взаимодействий, но неэффективно для частого взаимодействия. При использовании WebSocket заголовки отправляются только один раз во время первоначального установления связи, что уменьшает объем избыточных данных, передаваемых по сети.

Передача данных в реальном времени

WebSocket отлично подходит для сценариев, требующих обновлений в реальном времени. Будь то приложение для чата, обновление спортивных результатов в прямом эфире или панель мониторинга фондового рынка, WebSocket может предоставлять новые данные, как только они станут доступны. Это крайне важно в средах, где даже секундная задержка может оказаться дорогостоящей. Например, в торговом приложении цены на акции должны обновляться в режиме реального времени, чтобы трейдеры могли принимать точные решения.

Масштабируемость и использование ресурсов

В приложении на основе HTTP с частыми обновлениями серверу приходится одновременно обрабатывать несколько входящих запросов и исходящих ответов, что потребляет больше ресурсов ЦП и памяти. Благодаря WebSocket постоянное одно соединение позволяет лучше использовать ресурсы, поскольку серверу не нужно постоянно открывать и закрывать несколько соединений. Это приводит к улучшенной масштабируемости, позволяя обслуживать больше клиентов с теми же аппаратными ресурсами.

Двунаправленная связь

WebSocket не только позволяет серверу отправлять обновления клиенту, но также позволяет клиенту взаимодействовать с сервером в режиме реального времени. Эта двунаправленная возможность делает WebSocket отличным выбором для приложений для совместной работы. Например, в приложении, подобном Google Docs, несколько пользователей могут редактировать один и тот же документ, а изменения, внесенные одним пользователем, могут мгновенно отражаться на экранах всех остальных.

Интеграция с современными веб-технологиями

WebSocket хорошо интегрируется с другими современными веб-технологиями, такими как React, Angular и Vue.js, что упрощает разработчикам создание сложных веб-приложений, работающих в режиме реального времени. Многие библиотеки и платформы предлагают встроенную поддержку WebSocket, что еще больше упрощает его реализацию.

Настройка приложения Spring Boot

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

Создание скелета проекта

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

  • Spring Initializr. Это веб-инструмент, который генерирует проект Spring Boot с выбранными вами зависимостями, языком и параметрами упаковки. Вы просто выбираете параметры, и вам предоставляется загружаемый zip-файл, который поможет вам начать работу.
  • Поддержка IDE. Если вы используете IDE, например IntelliJ IDEA или Eclipse, вы найдете встроенную поддержку для создания проектов Spring Boot. Обычно для этого используется пошаговый мастер, позволяющий выбрать зависимости и другие настройки.
  • Ручная настройка. Если вы предпочитаете делать все вручную, вы можете создать базовый проект Maven или Gradle и самостоятельно добавить необходимые зависимости и файлы конфигурации.

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

Следующий шаг включает добавление необходимых зависимостей в ваш файл Maven pom.xml. Для поддержки WebSocket Spring предлагает зависимость spring-boot-starter-websocket. Вот как включить его в вашу конфигурацию Maven:

<dependency>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-starter-websocket</artifactId>
</dependency>

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

Понимание начальных зависимостей

Стартовые зависимости в Spring Boot предназначены для упрощения управления зависимостями. Пакет spring-boot-starter-websocket — это стартовый пакет, то есть он включает набор зависимостей, которые обычно используются для приложений WebSocket. Это позволяет избежать хлопот с определением и управлением отдельными версиями каждой зависимости, что может стать громоздким и подверженным ошибкам.

Настройка application.properties или application.yml

Хотя базовая установка не требует каких-либо дополнительных настроек в application.properties или application.yml, возможно, вам захочется добавить конфигурации позже, когда вы усложните свое приложение. Например, вы можете настроить собственную конечную точку WebSocket, установить разрешенные источники, если вы имеете дело с CORS (совместное использование ресурсов между источниками), или даже настроить размеры буфера для повышения производительности.

Структура проекта

Как только скелет и зависимости готовы, вы обычно переходите к структурированию своего проекта. Типичный проект Spring Boot имеет стандартный макет с каталогами для контроллеров, сервисов, репозиториев и конфигураций. Для проекта WebSocket у вас также может быть каталог, предназначенный для обработчиков и конфигураций WebSocket.

Класс конфигурации с @EnableWebSocket

В Spring классы конфигурации служат основой для настройки различных функций. Эти классы помечены @Configuration, сигнализируя Spring, что они определяют bean-компоненты и другие настройки, необходимые для проекта. При работе с WebSocket аннотация @EnableWebSocket имеет решающее значение для активации необходимых компонентов и подготовки почвы для взаимодействия в реальном времени.

Роль @EnableWebSocket

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

Реализация WebSocketConfigurer

Класс конфигурации, целью которого является использование @EnableWebSocket, обычно реализует интерфейс WebSocketConfigurer. Этот интерфейс предоставляет вам метод registerWebSocketHandlers(WebSocketHandlerRegistry registry), который вам необходимо переопределить. В этом методе вы указываете сопоставления URL-адресов и связываете их с соответствующими обработчиками WebSocket.

@Configuration
@EnableWebSocket
public class WebSocketConfig implements WebSocketConfigurer {
  
  @Override
  public void registerWebSocketHandlers(WebSocketHandlerRegistry registry) {
    registry.addHandler(myHandler(), "/myHandler");
  }
  
  // Other configurations and beans
}

Добавление пользовательских настроек

WebSocketHandlerRegistry позволяет вам добавлять собственные настройки и конфигурации, специфичные для ваших обработчиков WebSocket. Например, вы можете указать разрешенные источники, чтобы контролировать, из каких источников можно получить доступ к вашему серверу WebSocket, как показано ниже:

@Override
public void registerWebSocketHandlers(WebSocketHandlerRegistry registry) {
  registry.addHandler(myHandler(), "/myHandler")
          .setAllowedOrigins("http://example.com");
}

Определение компонентов

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

Например, вы можете определить компонент WebSocketHandler:

@Bean
public WebSocketHandler myHandler() {
  return new MyHandler();
}

В приведенном выше фрагменте определен bean-компонент типа WebSocketHandler, которым будет управлять Spring. Этот обработчик зарегистрирован в методе registerWebSocketHandlers.

Важность структуры классов конфигурации

Хорошая структуризация класса конфигурации помогает управлять сложностью вашего приложения. Вы можете разделить различные задачи и функции, организовав их в разные классы конфигурации. Например, все настройки и компоненты, связанные с WebSocket, могут находиться в одном классе, а конфигурации базы данных или безопасности могут размещаться в разных классах конфигурации. Такое разделение упрощает навигацию, тестирование и поддержку кодовой базы.

Реализация WebSocketHandler

После того как основа вашего приложения Spring Boot с поддержкой WebSocket заложена, следующим важным шагом является реализация интерфейса WebSocketHandler. Этот интерфейс лежит в основе обработки сообщений WebSocket в Spring. Он определяет основные методы, которые ваше приложение будет использовать для управления сеансами WebSocket и событиями сообщений.

Интерфейс WebSocketHandler

Интерфейс WebSocketHandler в Spring предлагает различные методы, которые вы можете переопределить для обработки различных событий WebSocket, таких как открытие соединения, отправка сообщений, получение сообщений и обработка ошибок.

  • afterConnectionEstablished(WebSocketSession session): Этот метод вызывается после установления нового сеанса WebSocket.
  • handleTextMessage(WebSocketSession session, TextMessage message): Этот метод обрабатывает входящие текстовые сообщения.
  • handleBinaryMessage(WebSocketSession session, BinaryMessage message): Этот метод обрабатывает двоичные сообщения.
  • afterConnectionClosed(WebSocketSession session, CloseStatus status): Этот метод вызывается после закрытия сеанса WebSocket.

Создание пользовательского обработчика

Создание собственного обработчика WebSocket включает реализацию интерфейса WebSocketHandler и переопределение его методов в соответствии с вашими конкретными требованиями. Ниже приведен скелетный пример пользовательского WebSocketHandler:

import org.springframework.web.socket.TextMessage;
import org.springframework.web.socket.WebSocketSession;
import org.springframework.web.socket.handler.TextWebSocketHandler;

public class MyHandler extends TextWebSocketHandler {

  @Override
  public void afterConnectionEstablished(WebSocketSession session) {
    // Handle new session established actions
  }

  @Override
  protected void handleTextMessage(WebSocketSession session, TextMessage message) {
    // Handle incoming messages
  }

  @Override
  public void afterConnectionClosed(WebSocketSession session, CloseStatus status) {
    // Handle session close actions
  }
  
  // Additional methods for error handling, etc.
}

Интеграция с бизнес-логикой

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

@Override
protected void handleTextMessage(WebSocketSession session, TextMessage message) {
  String payload = message.getPayload();
  someService.processPayload(payload);
}

Обработка ошибок

В реальном мире все может пойти не так. Соединения могут прерываться, а сообщения могут быть отправлены некорректно. Поэтому надежная обработка ошибок имеет жизненно важное значение. Вы можете переопределить метод handleTransportError для обработки таких сценариев:

@Override
public void handleTransportError(WebSocketSession session, Throwable exception) {
  // Log the error and handle it
}

Атрибуты сеанса и идентификация пользователя

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

@Override
public void afterConnectionEstablished(WebSocketSession session) {
  String username = session.getAttributes().get("username").toString();
  // Do something with the username
}

Потокобезопасность и параллелизм

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

Зарегистрируйте обработчик

Процесс регистрации обработчика WebSocket — это последняя часть головоломки, которая позволяет вашему приложению Spring Boot обрабатывать сообщения WebSocket. Эта операция выполняется в вашем классе конфигурации, где вы уже включили поддержку WebSocket с помощью аннотации @EnableWebSocket. Процесс регистрации привязывает ваш пользовательский обработчик к определенной конечной точке URL-адреса, позволяя клиентам устанавливать соединения WebSocket с этой конечной точкой.

Реестр WebSocketHandlerRegistry

Как упоминалось ранее в разделе «Класс конфигурации», WebSocketHandlerRegistry — это центральная точка, где регистрируются все обработчики WebSocket. Именно через этот реестр ваш пользовательский обработчик становится известен Spring и сопоставляется с определенным путем в вашем приложении.

Как зарегистрировать обработчик

Чтобы зарегистрировать свой обработчик, вы обычно вызываете метод addHandler для объекта WebSocketHandlerRegistry, переданного в метод registerWebSocketHandlers. Первый параметр addHandler — это объект обработчика, а второй параметр — сопоставление URL-адресов, по которому обработчик должен быть доступен.

Вот простой пример:

@Override
public void registerWebSocketHandlers(WebSocketHandlerRegistry registry) {
  registry.addHandler(myHandler(), "/ws");
}

Настройка параметров обработчика

Метод addHandler возвращает объект WebSocketHandlerRegistration, который позволяет связать дополнительные настройки. Например, вы можете установить разрешенные источники, чтобы указать, каким веб-сайтам разрешено подключаться к вашей конечной точке WebSocket.

@Override
public void registerWebSocketHandlers(WebSocketHandlerRegistry registry) {
  registry.addHandler(myHandler(), "/ws")
          .setAllowedOrigins("http://example.com");
}

Перехватчики

Перехватчики предлагают способ выполнения действий до и после подтверждения WebSocket, предоставляя вам место для реализации таких функций, как аутентификация или ведение журнала. Чтобы зарегистрировать перехватчик, вы можете использовать метод addInterceptors объекта WebSocketHandlerRegistration.

@Override
public void registerWebSocketHandlers(WebSocketHandlerRegistry registry) {
  registry.addHandler(myHandler(), "/ws")
          .addInterceptors(new MyHandshakeInterceptor());
}

Условная регистрация

Иногда вам может потребоваться условно зарегистрировать обработчики на основе настроек или профилей времени выполнения. Программный подход Spring дает вам гибкость в динамическом принятии этих решений.

@Override
public void registerWebSocketHandlers(WebSocketHandlerRegistry registry) {
  if (someCondition) {
    registry.addHandler(myHandler(), "/ws");
  }
}

Несколько обработчиков

В более сложных приложениях у вас может быть несколько обработчиков WebSocket, обслуживающих разные функции. Регистрация нескольких обработчиков проста:

@Override
public void registerWebSocketHandlers(WebSocketHandlerRegistry registry) {
  registry.addHandler(myChatHandler(), "/chat");
  registry.addHandler(myNotificationHandler(), "/notifications");
}

Важность правильной регистрации

Неправильная регистрация обработчика может привести к тому, что ваше приложение вообще не сможет обрабатывать сообщения WebSocket или обработает их неправильно. Поэтому понимание того, как правильно регистрировать обработчики, имеет решающее значение для общей функциональности приложения.

Тестирование конфигурации

После настройки конфигурации, реализации обработчика WebSocket и его регистрации вы можете подумать, что все готово. Однако остается один критический аспект: тестирование. Без тщательного тестирования конфигураций и обработчиков WebSocket вы, по сути, летите вслепую, не подозревая о потенциальных проблемах, которые могут возникнуть в производственной среде.

Важность тестирования

Протокол WebSocket представляет сложности, с которыми обычно не сталкиваются традиционные службы на основе HTTP. Учитывая, что WebSocket обеспечивает постоянное соединение с отслеживанием состояния между клиентом и сервером, его тестирование имеет решающее значение для обеспечения эффективной обработки вашим приложением таких сценариев, как разрывы соединения, упорядочивание сообщений и проблемы параллелизма.

Модульное тестирование

Самый простой способ начать тестирование конфигурации WebSocket — выполнить модульные тесты для отдельных компонентов. Надежная среда тестирования Spring упрощает модульное тестирование обработчиков WebSocket.

Вот пример модульного теста, который можно использовать для проверки WebSocketHandler:

@SpringBootTest
public class MyHandlerTest {

  @Autowired
  private MyHandler myHandler;

  @Test
  public void testHandleTextMessage() {
    WebSocketSession session = mock(WebSocketSession.class);
    TextMessage message = new TextMessage("Test message");
    myHandler.handleTextMessage(session, message);
    
    // Your assertions here
  }
}

Интеграционное тестирование

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

Пример:

@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
public class WebSocketIntegrationTest {

  @Autowired
  private TestWebSocketClient webSocketClient;

  @LocalServerPort
  private int port;

  @Test
  public void testWebSocketConnection() {
    WebSocketHttpHeaders headers = new WebSocketHttpHeaders();
    // Add any necessary headers
    
    URI uri = URI.create("ws://localhost:" + port + "/ws");
    ListenableFuture<WebSocketSession> future =
        this.webSocketClient.doHandshake(myHandler, headers, uri);
    
    // Perform your tests
  }
}

Сквозное тестирование

Хотя среда тестирования Spring предлагает множество утилит для комплексного тестирования, иногда нет замены сквозному тестированию. Вы можете использовать сторонние библиотеки, такие как Selenium, для автоматизированных комплексных тестов, имитирующих реальное поведение пользователя.

Тестирование обработки, порядка и согласованности сообщений

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

Тестирование аспектов безопасности

Если ваше приложение предполагает аутентификацию и авторизацию, не менее важно проверить, как ваш обработчик WebSocket ведет себя в сценариях, связанных с нарушениями безопасности, несанкционированным доступом или истечением срока действия сеанса.

@Test
public void testUnauthorizedAccess() {
  // Test connecting to WebSocket without authentication
  // Expect failure or disconnection
}

Заключение

Spring Framework упрощает реализацию WebSocket с помощью простых в использовании аннотаций, таких как @EnableWebSocket. В этом сообщении блога представлено краткое руководство по установке и настройке WebSocket в Spring, начиная с начальной настройки и заканчивая полной настройкой.

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

  1. WebSockets — Веб-API | МДН
  2. Spring Framework — WebSocket
  3. Spring Boot — Тестирование

Спасибо, что дочитали до конца. Пожалуйста, подумайте о том, чтобы подписаться на автора и эту публикацию. Посетите Stackademic, чтобы узнать больше о том, как мы демократизируем бесплатное образование в области программирования во всем мире.