Article
· Mars 3 9m de lecture

Un tutoriel sur les WebSockets

Intro

La plupart des communications serveur-client sur le web sont basées sur une structure de demande et de réponse. Le client envoie une demande au serveur et le serveur répond à cette demande. Le protocole WebSocket fournit un canal de communication bidirectionnel entre un serveur et un client, permettant aux serveurs d'envoyer des messages aux clients sans recevoir de demande au préalable. Pour plus d'informations sur le protocole WebSocket et son implémentation dans InterSystems IRIS, voir les liens ci-dessous.

Ce tutoriel est une mise à jour de ["Asynchronous Websockets -- a quick tutorial"] (https://community.intersystems.com/post/asynchronous-websockets-quick-tu...) pour Caché 2016.2+ et InterSystems IRIS 2018.1+.

Fonctionnement asynchrone ou synchrone

Dans InterSystems IRIS, une connexion WebSocket peut être mise en œuvre de manière synchrone ou asynchrone. Le mode de fonctionnement de la connexion WebSocket entre le client et le serveur est déterminé par la propriété "SharedConnection" de la classe %CSP.WebSocket.

  • SharedConnection=1 : Fonctionnement asynchrone

  • SharedConnection=0: Fonctionnement synchrone

Une connexion WebSocket entre un client et un serveur hébergé sur une instance InterSystems IRIS comprend une connexion entre l'instance IRIS et la passerelle Web. En mode synchrone, la connexion utilise un canal privé. Dans le cas d'une opération WebSocket asynchrone, un groupe de clients WebSocket partage un pool de connexions entre l'instance IRIS et la passerelle Web. L'avantage d'une implémentation asynchrone des WebSockets est évident lorsque de nombreux clients se connectent au même serveur, car cette implémentation n'exige pas que chaque client soit traité par une connexion exclusive entre la passerelle Web et l'instance IRIS.

Dans ce tutoriel, nous mettrons en œuvre les WebSockets de manière asynchrone. Ainsi, toutes les fenêtres de chat ouvertes partagent un pool de connexions entre la passerelle Web et l'instance IRIS qui héberge la classe de serveur WebSocket.

Présentation de l'application Chat

Le "hello world" des WebSockets est une application de chat dans laquelle un utilisateur peut envoyer des messages qui sont diffusés à tous les utilisateurs connectés à l'application. Dans ce tutoriel, les composants de l'application de chat sont les suivants :

  • Serveur : implémenté dans une classe qui étend %CSP.WebSocket

  • Client: mis en œuvre par une page CSP

La mise en œuvre de cette application de chat permettra d'atteindre les objectifs suivants :

  • Les utilisateurs peuvent diffuser des messages à toutes les fenêtres de chat ouvertes

  • Les utilisateurs en ligne apparaissent dans la liste "Utilisateurs en ligne" de toutes les fenêtres de chat ouvertes.

  • Les utilisateurs peuvent changer leur nom d'utilisateur en composant un message commençant par le mot-clé "alias". Ce message ne sera pas diffusé mais mettra à jour la liste des "Utilisateurs en ligne".

  • Lorsque les utilisateurs ferment leur fenêtre de chat, ils sont retirés de la liste des "Utilisateurs en ligne".

Pour obtenir le code source de l'application de chat, veuillez consulter ce [dépôt GitHub] (https://github.com/intersystems/InterSystems-WebSockets).

Le Client

Le côté client de notre application de chat est mis en œuvre par une page CSP contenant le style de la fenêtre de chat, la déclaration de la connexion WebSocket, les événements et méthodes WebSocket qui gèrent la communication vers et depuis le serveur, et les fonctions d'aide qui regroupent les messages envoyés au serveur et traitent les messages entrants.

Tout d'abord, nous verrons comment l'application initie la connexion WebSocket à l'aide d'une bibliothèque Javascript WebSocket.

    ws = new WebSocket(((window.location.protocol === "https:")? "wss:":"ws:")
                    + "//"+ window.location.host + "/csp/user/Chat.Server.cls");

new crée une nouvelle instance de la classe WebSocket. Celle-ci ouvre une connexion WebSocket au serveur en utilisant le protocole "wss" (indique l'utilisation de TLS pour le canal de communication WebSocket) ou "ws". Le serveur est spécifié par le numéro de port du serveur web et le nom de l'hôte de l'instance qui définit la classe Chat.Server (cette information est contenue dans la variable window.location.host). Le nom de notre classe de serveur (Chat.Server.cls) est inclus dans l'URI d'ouverture de la WebSocket en tant que requête GET pour la ressource sur le serveur.

L'événement ws.onopen se déclenche lorsque la connexion WebSocket est établie avec succès, passant d'un état connecting à un état open.

    ws.onopen = function(event){
        document.getElementById("headline").innerHTML = "CHAT - CONNECTED";
    };

Cet événement met à jour l'en-tête de la fenêtre de chat pour indiquer que le client et le serveur sont connectés.

Envoi de messages

L'action d'un utilisateur qui envoie un message déclenche la fonction send. Cette fonction sert d'enveloppe à la méthode ws.send, qui contient les mécanismes d'envoi du message du client au serveur via la connexion WebSocket.

function send() {
    var line=$("#inputline").val();
    if (line.substr(0,5)=="alias"){
        alias=line.split(" ")[1];
        if (alias==""){
            alias="default";
        }
        var data = {}
        data.User = alias
        ws.send(JSON.stringify(data));
        } else {
        var msg=btoa(line);
        var data={};
        data.Message=msg;
        data.Author=alias;
        if (ws && msg!="") {
            ws.send(JSON.stringify(data));
        }
    }
    $("#inputline").val("");
}

send compile les informations à envoyer au serveur dans un objet JSON, en définissant des paires clé/valeur en fonction du type d'information envoyée (mise à jour d'alias ou message général). btoa traduit le contenu d'un message général en une chaîne ASCII encodée en base 64.

Réception des messages

Lorsque le client reçoit un message du serveur, l'événement ws.onmessage est déclenché.

ws.onmessage = function(event) {
    var d=JSON.parse(event.data);
    if (d.Type=="Chat") {
        $("#chat").append(wrapmessage(d));
            $("#chatdiv").animate({ scrollTop: $('#chatdiv').prop("scrollHeight")}, 1000);
    } else if(d.Type=="userlist") {
        var ul = document.getElementById("userlist");
        while(ul.firstChild){ul.removeChild(ul.firstChild)};
        $("#userlist").append(wrapuser(d.Users));
    } else if(d.Type=="Status"){
        document.getElementById("headline").innerHTML = "CHAT - connected - "+d.WSID;
    }
};

Selon le type de message reçu par le client ("Chat", "userlist" ou "status"), l'événement onmessage appelle wrapmessage ou wrapuser pour remplir les sections appropriées de la fenêtre de chat avec les données entrantes. Si le message entrant est une mise à jour d'état, l'en-tête d'état de la fenêtre de discussion est mis à jour avec l'ID WebSocket, qui identifie la connexion WebSocket bidirectionnelle associée à la fenêtre de discussion.

Composants supplémentaires du client

Une erreur dans la communication entre le client et le serveur déclenche la méthode WebSocket onerror, qui émet une alerte nous informant de l'erreur et mettant à jour l'en-tête d'état de la page.

ws.onerror = function(event) {
    document.GetElementById("headline").innerHTML = "CHAT - error";
    alert("Received error"); 
};

The onclose method is triggered when the WebSocket connection between the client and server is closed and updates the status header.

ws.onclose = function(event) {
    ws = null;
    document.getElementById("headline").innerHTML = "CHAT - disconnected";
}

Le Serveur

Le côté serveur de l'application de chat est implémenté par la classe Chat.Server, qui étend %CSP.WebSocket. Notre classe serveur hérite de diverses propriétés et méthodes de %CSP.WebSocket, dont certaines seront discutées ci-dessous. Chat.Server implémente également des méthodes personnalisées pour traiter les messages provenant du ou des client(s) et pour les diffuser.

Avant de démarrer le serveur

OnPreServer() est exécutée avant la création du serveur WebSocket et est héritée de la classe %CSP.WebSocket.

Method OnPreServer() As %Status
{
    set ..SharedConnection=1
    if (..WebSocketID '= ""){ 
        set ^Chat.WebSocketConnections(..WebSocketID)=""
    } else {
        set ^Chat.Errors($INCREMENT(^Chat.Errors),"no websocketid defined")=$HOROLOG 
    }
    Quit $$$OK
}

Cette méthode définit le paramètre de classe SharedConnection à 1, indiquant que notre connexion WebSocket sera asynchrone et supportée par plusieurs processus qui définissent les connexions entre l'instance InterSystems IRIS et la Web Gateway. Le paramètre SharedConnection ne peut être modifié que dans OnPreServer(). OnPreServer() stocke également l'identifiant WebSocket associé au client dans le global ^Chat.WebSocketConnections.

La méthode Server

L'essentiel de la logique exécutée par le serveur est contenu dans la méthode Server().```
Method Server() As %Status
{
do ..StatusUpdate(..WebSocketID)
for {
set data=..Read(.size,.sc,1)
if ($$$ISERR(sc)){
if ($$$GETERRORCODE(sc)=$$$CSPWebSocketTimeout) {
//$$$DEBUG("no data")
}
if ($$$GETERRORCODE(sc)=$$$CSPWebSocketClosed){
kill ^Chat.WebSocketConnections(..WebSocketID)
do ..RemoveUser($g(^Chat.Users(..WebSocketID)))
kill ^Chat.Users(..WebSocketID)
quit // Client closed WebSocket
}
} else{
if data["User"{
do ..AddUser(data,..WebSocketID)
} else {
set mid=$INCREMENT(^Chat.Messages)
set ^Chat.Messages(mid)=data
do ..ProcessMessage(mid)
}
}
}
Quit $$$OK
}

Cette méthode lit les messages entrants du client (en utilisant la méthode `Read` de la classe `%CSP.WebSockets`), ajoute les objets JSON reçus au global `^Chat.Messages`, et appelle `ProcessMessage()` pour transmettre le message à tous les autres clients de chat connectés. Lorsqu'un utilisateur ferme sa fenêtre de chat (mettant ainsi fin à la connexion WebSocket avec le serveur), l'appel à `Read` de la méthode `Server()` renvoie un code d'erreur qui évalue la macro `$$CSPWebSocketClosed` et la méthode procède à la gestion de la fermeture en conséquence.

### *Traitement et distribution des messages*

`ProcessMessage()` ajoute des métadonnées au message de chat entrant et appelle `SendData()`, en passant le message en paramètre.  

ClassMethod ProcessMessage(mid As %String)
{
set msg = ##class(%DynamicObject).%FromJSON($GET(^Chat.Messages(mid)))
set msg.Type="Chat"
set msg.Sent=$ZDATETIME($HOROLOG,3)
do ..SendData(msg)
}


`ProcessMessage()` récupère le message au format JSON dans le global `^Chat.Messages` et le convertit en un objet IRIS d'InterSystems en utilisant la méthode `%FromJSON` de la classe `%DynamicObject`. Cela nous permet d'éditer facilement les données avant de transmettre le message à tous les clients de chat connectés. Nous ajoutons un attribut `Type` avec la valeur "Chat", que le client utilise pour déterminer comment traiter le message entrant. `SendData()` envoie le message à tous les autres clients de chat connectés.``` ClassMethod SendData(data As %DynamicObject) { set c = "" for { set c = $order(^Chat.WebSocketConnections(c)) if c="" Quit set ws = ..%New() set sc = ws.OpenServer(c) if $$$ISERR(sc) { do ..HandleError(c,"open") } set sc = ws.Write(data.%ToJSON()) if $$$ISERR(sc) { do ..HandleError(c,"write") } } }

SendData() convertit l'objet InterSystems IRIS en une chaîne JSON (data.%ToJSON()) et envoie le message à tous les clients du chat. SendData() récupère l'ID WebSocket associé à chaque connexion client-serveur dans le global ^Chat.WebSocketConnections et utilise l'ID pour ouvrir une connexion WebSocket via la méthode OpenServer de la classe %CSP.WebSocket. Nous pouvons utiliser la méthode OpenServer car nos connexions WebSocket sont asynchrones - nous puisons dans le pool existant de processus IRIS-Web Gateway et attribuons à l'un d'entre eux l'ID WebSocket qui identifie la connexion du serveur à un client de chat spécifique. Enfin, la méthode Write()%CSP.WebSocket` envoie la représentation JSON du message au client.

Conclusion

Cette application de chat montre comment établir des connexions WebSocket entre un client et un serveur hébergés par InterSystems IRIS. Pour en savoir plus sur le protocole et son implémentation dans InterSystems IRIS, consultez les liens dans l'introduction.

Discussion (0)1
Connectez-vous ou inscrivez-vous pour continuer