WebSockets réactifs avec Spring 5

1. Vue d'ensemble

Dans cet article, nous allons créer un exemple rapide en utilisant la nouvelle API Spring 5 WebSockets avec les fonctionnalités réactives fournies par Spring WebFlux.

WebSocket est un protocole bien connu qui permet une communication en duplex intégral entre le client et le serveur, généralement utilisé dans les applications Web où le client et le serveur doivent échanger des événements à haute fréquence et avec une faible latence.

Spring Framework 5 a modernisé la prise en charge des WebSockets dans le framework, ajoutant des capacités réactives à ce canal de communication.

Nous pouvons en trouver plus sur Spring WebFlux ici.

2. Dépendances de Maven

Nous allons utiliser les dépendances spring-boot-starters pour spring-boot-integration et spring-boot-starter-webflux, actuellement disponibles sur Spring Milestone Repository.

Dans cet exemple, nous utilisons la dernière version disponible, 2.0.0.M7, mais il faut toujours obtenir la dernière version disponible dans le référentiel Maven:

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

3. Configuration de WebSocket au printemps

Notre configuration est assez simple: nous injecterons le WebSocketHandler pour gérer la session socket dans notre application Spring WebSocket.

@Autowired private WebSocketHandler webSocketHandler; 

De plus, créons une méthode annotée par bean HandlerMapping qui sera responsable du mappage entre les requêtes et les objets de gestionnaire:

@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; }

L'URL à laquelle nous pouvons nous connecter sera: ws: // localhost: / event-emitter.

4. Gestion des messages WebSocket au printemps

Notre classe ReactiveWebSocketHandler sera responsable de la gestion de la session WebSocket côté serveur.

Il implémente l' interface WebSocketHandler afin que nous puissions remplacer la méthode handle , qui sera utilisée pour envoyer le message au client 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. Création d'un client WebSocket réactif simple

Créons maintenant un client Spring Reactive WebSocket qui pourra se connecter et échanger des informations avec notre serveur WebSocket.

5.1. Dépendance de Maven

Tout d'abord, les dépendances Maven.

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

Ici, nous utilisons le même spring-boot-starter-webflux utilisé précédemment pour configurer notre application serveur WebSocket réactive.

5.2. Client WebSocket

Maintenant, créons la classe ReactiveClientWebSocket , chargée de démarrer la communication avec le serveur:

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)); } }

Dans le code ci-dessus, nous pouvons voir que nous utilisons ReactorNettyWebSocketClient , qui est l' implémentation WebSocketClient à utiliser avec Reactor Netty.

En outre, le client se connecte au serveur WebSocket via l'URL ws: // localhost: 8080 / event-emitter, établissant une session dès qu'il est connecté au serveur.

Nous pouvons également voir que nous envoyons un message au serveur (« event-spring-reactive-client-websocket ») avec la demande de connexion.

De plus, la méthode send est invoquée, en attendant en paramètre une variable de type Publisher, qui dans notre cas notre Publisher est Mono et T est une simple String « event-me-from-reactive-java-client-websocket ».

De plus, la méthode thenMany (…) attend un Flux de type String est invoquée. La méthode receive () obtient le flux des messages entrants, qui sont ensuite convertis en chaînes.

Enfin, la méthode block () force le client à se déconnecter du serveur après le temps imparti (10 secondes dans notre exemple).

5.3. Démarrage du client

Pour l'exécuter, assurez-vous que le serveur WebSocket réactif est opérationnel. Ensuite, lancez la classe ReactiveJavaClientWebSocket , et nous pouvons voir sur le journal sysout les événements émis:

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

Nous pouvons également voir dans le journal de notre serveur WebSocket réactif le message envoyé par le client lors de la tentative de connexion:

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

En outre, nous pouvons voir le message de connexion terminée une fois que le client a terminé ses demandes (dans notre cas, après les 10 secondes):

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

6. Création d'un navigateur WebSocket Client

Créons un simple client HTML / Javascript WebSocket pour consommer notre application serveur WebSocket réactive.

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

"; }

Avec le serveur WebSocket en cours d'exécution, ouvrant ce fichier HTML dans un navigateur (par exemple: Chrome, Internet Explorer, Mozilla Firefox etc.), nous devrions voir les événements en cours d'impression à l'écran, avec un délai de 1 seconde par événement, comme défini dans notre serveur 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. Conclusion

Nous avons présenté ici un exemple de création d'une communication WebSocket entre le serveur et le client à l'aide de Spring 5 Framework, en implémentant les nouvelles fonctionnalités réactives fournies par Spring Webflux.

Comme toujours, l'exemple complet se trouve dans notre référentiel GitHub.