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 .