Les Websockets : définition et implémentation dans une application Angular et Java Springboot

Les Websockets, c’est quoi ? Définition et implémentation dans une application Angular et Java Springboot
Solveig.jpg
Solveig LECARPENTIERMis à jour le 28 Mai 2020
websocket angular java spring boot

Vous souhaitez faire une application web interactive en temps réel ? Rien de mieux que les Websockets !

Les Websockets, c’est quoi ?

Quand on parle de websocket, on parle plutôt du protocole Websocket en réalité ! Cette technologie permet d’ouvrir de façon permanente un canal de communication entre un client, votre navigateur, et un serveur reposant sur le protocole TCP qui s’occupe du transport fiable des données.

Le client et le serveur peuvent communiquer entre eux (bidirectionnel) et en même temps (full-duplex).

Le client peut alors envoyer des messages au serveur qui répondra de manière évènementielle, sans avoir reçu de requête du client.

A quelles problématiques répondent les Websockets ?

Généralement, on utilise le protocole HTTP pour communiquer entre un client et un serveur. Il est caractérisé par :

  • Un canal unidirectionnel : pour recevoir des données, le client envoi une requête au serveur qui répond ensuite. Le serveur n’enverra pas d’informations sans avoir reçu de requête.
  • Half-duplex : le client et le serveur communiquent l’un après l’autre.
  • Obligation d’un header (en-tête de la requête/réponse) comprenant un certain nombre d’informations qui alourdit la requête/réponse.
  • Aucune information n’est gardée en mémoire ! Donc, à chaque nouvelle requête, une nouvelle connexion est établie suivie d’une déconnexion, ce qui engendre un temps de réponse plus important.

Bien que ce protocole permette de faire de nombreuses applications web, il n’est pas optimisé pour les applications dynamiques qui nécessitent des réponses rapides et interactives.

Les websockets ont été créées pour répondre à ces besoins.

Les Websockets avec Angular et Java Springboot, comment ça marche ?

On prendra en exemple l’implémentation du protocole Websocket dans une application web Angular (front) / JavaLangage de développement très populaire ! Spring Boot (back).

On verra la connexion Websocket, l’abonnement, l’envoi et la réception de messages et enfin le désabonnement.

1. Mise en place / connexion

Avant tout, il faut ajouter quelques dépendances au pom.xml :

    <dependencies>  
        <dependency>  
            <groupId>org.webjars</groupId>  
            <artifactId>sockjs-client</artifactId>  
            <version>1.0.2</version>  
        </dependency>  
        <dependency>  
            <groupId>org.webjars</groupId>  
            <artifactId>stomp-websocket</artifactId>  
            <version>2.3.3</version>  
        </dependency>  
        <dependency>  
            <groupId>org.springframework.boot</groupId>  
            <artifactId>spring-boot-starter-websocket</artifactId>  
            <version>RELEASE</version>  
        </dependency>
    </dependencies>

On a fait le choix d’utiliser SockJS et STOMP, on reviendra plus en détail sur ces deux librairies.

Configuration Spring Boot (back).

On crée une classe de configuration qui hérite de WebSocketMessageBrokerConfigurer. Les appels qui utilisent le protocole Websocket seront alors dirigés vers les endpoints (points de connexion) dédiés.

    @Configuration
    @EnableWebSocketMessageBroker
    public class WebSocketConfig implements WebSocketMessageBrokerConfigurer {
    
        @Override
        public void configureMessageBroker(MessageBrokerRegistry config) { 
            config.setApplicationDestinationPrefixes("/app")
              .enableSimpleBroker("/socket");
        }
        
        @Override public void registerStompEndpoints(StompEndpointRegistry registry) { 
            registry.addEndpoint("/socket")  
                .setAllowedOrigins("*")  
                .withSockJS();
        }
    }

Dans la première méthode, on définit donc deux préfixes pour filtrer les destinations.

Dans la deuxième méthode, on ajoute le point d’entrée pour le handshake : c’est une requête HTTP ayant dans son header, l’option Upgrade qui demande au serveur de se servir dorénavant du protocole Websocket. Le protocole HTTP sera utilisé uniquement ici, pour l’ouverture de la connexion.

Installation avec Angular (front).

On crée un service générique que l’on pourra utiliser partout dans notre application. On retrouve dans les imports les deux librairies ajoutées précédemment : SockJS et STOMP.

    import {Injectable} from@angular/core’;  
    import * as Stomp from ’stompjs’;  
    import * as SockJS from ’sockjs-client’;  
    import {SERVER_API_URL} from ’../../app.constants’;
    
    @Injectable() export class WebsocketService {
    
        private serverUrl = `${SERVER_API_URL}socket`;  
        private stompClient;  
        public mapEndpointSubscription: Map<string, any> = new Map();
          
        public async initWebSocket() { 
            return new Promise((resolve) => { 
                if (!this.stompClient) { 
                    const ws = new SockJS(this.serverUrl);  
                    this.stompClient = Stomp.over(ws);  
                    this.stompClient.connect({}, resolve);
                } else {  
                    resolve();
                }
        })
    }
}

SockJS fournit un objet Websocket qui fonctionnera sur tous les navigateurs, même ceux qui ne prennent pas en charge le protocole Websocket.

STOMP est un protocole de communication de texte brut avec lequel on pourra se connecter/déconnecter, s’abonner/se désabonner et envoyer des messages.

Dans la méthode initWebsocket(), on crée donc un objet websocket avec l’url pour communiquer avec le serveur et on ouvre une connexion avec la fonction connect(). On dispose ensuite de toutes les méthodes utiles à la communication entre le client et le serveur :

S’abonner : subscribe(name : string, fnc : (event) => void)

    public async subscribe(name: string, fnc: (event) => void) {
         const subscription = this.stompClient.subscribe(`/${name}`, (event) => {  
              fnc({...event, body: JSON.parse(event.body)})  
         });
         this.mapEndpointSubscription.set(name, subscription);
    }

Se désabonner : unsubscribeToWebSocketEvent(name: string)

    public unsubscribeToWebSocketEvent(name: string) {
        const subscription = this.mapEndpointSubscription.get(name);  
        if (subscription) {  
            subscription.unsubscribe();
        }  
    }

Envoyer un message : send(name: string, body: any)

    public send(name: string, body: any) {
        this.stompClient.send(`/app/socket/${name}`, {}, JSON.stringify(body));
    }

Se déconnecter : disconnect()

    public disconnect() {
        if (this.stompClient !== null) {
            this.stompClient.disconnect();
        }
    }

Le paramètre « name » correspond au endpoint.
La fonction « fnc » sera exécutée quand quelque chose sera envoyé au endpoint (c’est un callback). Ici, elle permet de récupérer les éléments du message.
Le paramètre « body » correspond au corps du message envoyé.

2. Abonnement, envoi et réception de messages

L’envoi de données, « data transfert », se fait au format texte ou binaire (les données de type texte sont encodées en UTF-8).

Côté back, on peut envoyer un message en ajoutant n’importe où dans notre code Java :

    private final MessageSendingOperations<String> wsTemplate;
    
    this.wsTemplate.convertAndSend("/socket/meetingStarts", meeting);

Les clients qui se seront inscrits au endpoint « socket/meetingStarts », recevront l’objet « meeting ».

Côté front, on initialise notre service générique de websockets dans un composant, et on utilise la méthode subscribe(name : string, fnc : (event) => void) pour s’abonner au endpoint « socket/meetingStarts » et récupérer notre objet précédemment envoyé :

    private readonly websocketService: WebsocketService;
    
    this.websocketService.initWebSocket().then(() => {
      this.websocketService.subscribe(’socket/meetingStarts’, (event) => { 
         this.currentMeeting = event.body;
        });
    });

On peut aussi envoyer des messages depuis le front en utilisant la méthode send(name : string, body : any) de notre service :

    this.websocketService.send(’someoneJoined’, ’Hello world’);

Les clients qui se seront inscrits au endpoint « socket/someoneJoined », recevront « Hello world ».

Dans ce cas-là, on crée alors un contrôleur côté back qui traitera l’action côté front pour l’envoyer au serveur (on retrouve notre endpoint « socket/someoneJoined ») :

    @Controller
    public class MeetingController
    
        @MessageMapping("/socket/someoneJoined")
        @SendTo("/socket/someoneJoined")
        public String someoneJoined(String message) {
            return message;
        }
    }

3. Désabonnement

Quand on n’a plus besoin de recevoir de notifications, on se désabonne du endpoint en utilisant la méthode de notre service :

this.websocketService.unsubscribeToWebSocketEvent(’socket/meetingStarts’);

Les points forts des websockets

  • Connexion permanente entre le client (navigateur) et le serveur : évite de devoir se connecter / déconnecter à chaque nouvelle requête
  • Requêtes plus rapides et plus légères (pas besoin de header)
  • Envoi de notifications du serveur au client, sans faire de requête au préalable
  • Tous les navigateurs récents supportent les websockets

Le point faible des Websockets

Le principal point faible dans l’utilisation des websockets, est l’instabilité, si l’application n’est pas single-page. Une application web qui fonctionne avec une page web unique (le contenu, CSSFeuilles de style qui permettent de mettre en forme des pages web., Javascript sont contenus dans un seul fichier HTMLHTML (HyperText Markup Language) est un langage permettant de décrire le découpage d'une page web.) ne se recharge pas entièrement à chaque action demandée par l’utilisateur. Ceci est rendu possible avec AngularAngular est un framework de développement JavaScript populaire basé sur TypeScript., ReactJS ou encore VueJS.

Dans le cas contraire, à chaque changement de page, une nouvelle connexion Websocket est créée, il faut donc bien penser à gérer cela sous forme de sessions.

Conclusion

Le protocole Websocket est plus efficace et performant pour la création d’applications dynamiques, par exemple l’intégration d’un chat au sein d’un site, nécessitera son utilisation. Grâce à la connexion permanente, les requêtes sont plus rapides et plus légères, les utilisateurs recevront donc en temps réel les messages envoyés sous forme de notifications de la part du serveur.

Les Websockets permettent de palier aux besoins auxquels le protocole HTTP ne pouvait répondre ! Et ça, c’est top !