Реактивни WebSockets с Spring 5

1. Общ преглед

В тази статия ще създадем бърз пример, използвайки новия API 5 на WebSockets Spring 5, заедно с реактивните функции, предоставени от 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:

 org.springframework.boot spring-boot-starter-integration   org.springframework.boot spring-boot-starter-webflux 

3. Конфигуриране на WebSocket през пролетта

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

@Autowired private WebSocketHandler webSocketHandler; 

Освен това, нека създадем HandlerMapping bean-анотиран метод, който ще отговаря за картографирането между заявки и обекти на манипулатора:

@Bean public HandlerMapping webSocketHandlerMapping() { Map map = new HashMap(); map.put("/event-emitter", webSocketHandler); SimpleUrlHandlerMapping handlerMapping = new SimpleUrlHandlerMapping(); handlerMapping.setOrder(1); handlerMapping.setUrlMap(map); return handlerMapping; }

URL адресът, към който можем да се свържем, ще бъде: ws: // localhost: / event-emitter.

4. Обработка на съобщения в WebSocket през пролетта

Нашият клас ReactiveWebSocketHandler ще отговаря за управлението на сесията WebSocket от страна на сървъра.

Той реализира интерфейса WebSocketHandler, за да можем да заменим метода за обработка , който ще се използва за изпращане на съобщението до клиента WebSocket:

@Component public class ReactiveWebSocketHandler implements WebSocketHandler { // private fields ... @Override public Mono 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.

 org.springframework.boot spring-boot-starter-webflux 

Тук използваме същия пролет-boot-starter-webflux, използван преди това за настройване на нашето реактивно приложение за сървър WebSocket.

5.2. Клиент на WebSocket

Сега нека създадем клас 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 “) заедно с искането за свързване.

Furthermore, the method send is invoked, expecting as a parameter a variable of type Publisher, which in our case our Publisher is Mono and T is a simple String “event-me-from-reactive-java-client-websocket“.

Moreover, the thenMany(…) method expecting a Flux of type String is invoked. The receive() method gets the flux of incoming messages, which later are converted into strings.

Finally, the block() method forces the client to disconnect from the server after the given time (10 seconds in our example).

5.3. Starting the Client

To run it, make sure the Reactive WebSocket Server is up and running. Then, launch the ReactiveJavaClientWebSocket class, and we can see on the sysout log the events being emitted:

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

We also can see in the log from our Reactive WebSocket server the message sent by the client during the connection attempt:

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

Also, we can see the message of terminated connection after the client finished its requests (in our case, after the 10 seconds):

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

6. Creating a Browser WebSocket Client

Let's create a simple HTML/Javascript client WebSocket to consume our reactive WebSocket server application.

 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 + "

"; }

Когато сървърът 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.