Перейти к основному содержимому

Реактивные веб-сокеты с Spring 5

· 4 мин. чтения

1. Обзор

В этой статье мы собираемся создать быстрый пример, используя новый Spring 5 WebSockets API вместе с реактивными функциями, предоставляемыми Spring WebFlux.

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

Spring Framework 5 модернизировал поддержку WebSockets в рамках, добавив реактивные возможности в этот канал связи.

Мы можем найти больше о Spring WebFlux здесь .

2. Зависимости Maven

Мы собираемся использовать зависимости spring-boot-starters для spring-boot-integration и spring-boot-starter-webflux , которые в настоящее время доступны в Spring Milestone Repository .

В этом примере мы используем последнюю доступную версию 2.0.0.M7, но всегда следует получать последнюю версию, доступную в репозитории Maven:

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

3. Конфигурация WebSocket весной

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

@Autowired
private WebSocketHandler webSocketHandler;

Кроме того, давайте создадим аннотированный bean-компонентом метод HandlerMapping , который будет отвечать за сопоставление между запросами и объектами обработчика:

@Bean
public HandlerMapping webSocketHandlerMapping() {
Map<String, WebSocketHandler> map = new HashMap<>();
map.put("/event-emitter", webSocketHandler);

SimpleUrlHandlerMapping handlerMapping = new SimpleUrlHandlerMapping();
handlerMapping.setOrder(1);
handlerMapping.setUrlMap(map);
return handlerMapping;
}

URL-адрес, к которому мы можем подключиться, будет следующим: ws://localhost:<port>/event-emitter.

4. Обработка сообщений WebSocket в Spring

Наш класс ReactiveWebSocketHandler будет отвечать за управление сеансом WebSocket на стороне сервера.

Он реализует интерфейс WebSocketHandler , поэтому мы можем переопределить метод handle , который будет использоваться для отправки сообщения клиенту WebSocket:

@Component
public class ReactiveWebSocketHandler implements WebSocketHandler {

// private fields ...

@Override
public Mono<Void> handle(WebSocketSession webSocketSession) {
return webSocketSession.send(intervalFlux
.map(webSocketSession::textMessage))
.and(webSocketSession.receive()
.map(WebSocketMessage::getPayloadAsText)
.log());
}
}

5. Создание простого реактивного клиента WebSocket

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

5.1. Зависимость от Maven

Во-первых, зависимости Maven.

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

Здесь мы используем тот же spring-boot-starter-webflux, который использовался ранее для настройки нашего реактивного серверного приложения WebSocket.

5.2. Веб-сокет-клиент

Теперь давайте создадим класс ReactiveClientWebSocket , отвечающий за запуск связи с сервером:

public class ReactiveJavaClientWebSocket {

public static void main(String[] args) throws InterruptedException {

WebSocketClient client = new ReactorNettyWebSocketClient();
client.execute(
URI.create("ws://localhost:8080/event-emitter"),
session -> session.send(
Mono.just(session.textMessage("event-spring-reactive-client-websocket")))
.thenMany(session.receive()
.map(WebSocketMessage::getPayloadAsText)
.log())
.then())
.block(Duration.ofSeconds(10L));
}
}

В приведенном выше коде мы видим, что мы используем ReactorNettyWebSocketClient , который является реализацией WebSocketClient для использования с Reactor Netty.

Кроме того, клиент подключается к серверу WebSocket через URL-адрес ws://localhost:8080/event-emitter, устанавливая сеанс сразу после подключения к серверу.

Мы также можем видеть, что мы отправляем сообщение на сервер (« event-spring-reactive-client-websocket ») вместе с запросом на подключение.

Кроме того, вызывается метод send , ожидая в качестве параметра переменную типа Publisher<T>, в нашем случае наш Publisher<T> — это Mono<T> , а T — это простая строка « event-me-from-reactive- java-клиент-веб-сокет «.

Более того, вызывается метод thenMany(…) , ожидающий Flux типа String . Метод Receive() получает поток входящих сообщений, которые позже преобразуются в строки.

Наконец, метод block() заставляет клиента отключиться от сервера по истечении заданного времени (10 секунд в нашем примере).

5.3. Запуск клиента

Чтобы запустить его, убедитесь, что Reactive WebSocket Server запущен и работает. Затем запустите класс ReactiveJavaClientWebSocket , и мы увидим в журнале sysout генерируемые события:

[reactor-http-nio-4] INFO reactor.Flux.Map.1 - 
onNext({"eventId":"6042b94f-fd02-47a1-911d-dacf97f12ba6",
"eventDt":"2018-01-11T23:29:26.900"})

Мы также можем увидеть в журнале нашего сервера Reactive WebSocket сообщение, отправленное клиентом во время попытки подключения:

[reactor-http-nio-2] reactor.Flux.Map.1: 
onNext(event-me-from-reactive-java-client)

Также мы можем увидеть сообщение об окончании соединения после того, как клиент завершил свои запросы (в нашем случае, через 10 секунд):

[reactor-http-nio-2] reactor.Flux.Map.1: onComplete()

6. Создание клиента WebSocket для браузера

Давайте создадим простой HTML/Javascript-клиент WebSocket для использования нашего реактивного серверного приложения WebSocket.

<div class="events"></div>
<script>
var clientWebSocket = new WebSocket("ws://localhost:8080/event-emitter");
clientWebSocket.onopen = function() {
console.log("clientWebSocket.onopen", clientWebSocket);
console.log("clientWebSocket.readyState", "websocketstatus");
clientWebSocket.send("event-me-from-browser");
}
clientWebSocket.onclose = function(error) {
console.log("clientWebSocket.onclose", clientWebSocket, error);
events("Closing connection");
}
clientWebSocket.onerror = function(error) {
console.log("clientWebSocket.onerror", clientWebSocket, error);
events("An error occured");
}
clientWebSocket.onmessage = function(error) {
console.log("clientWebSocket.onmessage", clientWebSocket, error);
events(error.data);
}
function events(responseEvent) {
document.querySelector(".events").innerHTML += responseEvent + "<br>";
}
</script>

При запущенном сервере WebSocket при открытии этого HTML-файла в браузере (например, Chrome, Internet Explorer, Mozilla Firefox и т. д.) мы должны увидеть события, распечатываемые на экране, с задержкой в 1 секунду для каждого события, как определено в наш сервер WebSocket.

{"eventId":"c25975de-6775-4b0b-b974-b396847878e6","eventDt":"2018-01-11T23:56:09.780"}
{"eventId":"ac74170b-1f71-49d3-8737-b3f9a8a352f9","eventDt":"2018-01-11T23:56:09.781"}
{"eventId":"40d8f305-f252-4c14-86d7-ed134d3e10c6","eventDt":"2018-01-11T23:56:09.782"}

7. Заключение

Здесь мы представили пример того, как создать связь WebSocket между сервером и клиентом с помощью Spring 5 Framework, реализуя новые реактивные функции, предоставляемые Spring Webflux.

Как всегда, полный пример можно найти в нашем репозитории GitHub .