Partager via


Créer une application de diffusion en continu de code en temps réel avec Socket.IO et l’héberger sur Azure

La création d’une expérience en temps réel comme la fonctionnalité de cocréation dans Microsoft Word peut être difficile.

Grâce à ses API faciles à utiliser, Socket.IO a fait ses preuves en tant que bibliothèque de communication en temps réel entre des clients et un serveur. Toutefois, les utilisateurs de Socket.IO signalent souvent des difficultés concernant la mise à l’échelle des connexions Socket.IO. Avec Web PubSub pour Socket.IO, les développeurs n’ont plus besoin de s’inquiéter de la gestion des connexions persistantes.

Important

Des chaînes de connexion brutes sont utilisées dans cet article uniquement à des fins de démonstration.

Une chaîne de connexion contient les informations d’autorisation requises pour que votre application accède au service Azure Web PubSub. La clé d’accès à l’intérieur dans la chaîne de connexion est semblable à un mot de passe racine pour votre service. Dans les environnements de production, protégez toujours vos clés d’accès. Utilisez Azure Key Vault pour gérer et permuter vos clés de façon sécurisée, et sécurisez votre connexion avec WebPubSubServiceClient.

Évitez de distribuer des clés d’accès à d’autres utilisateurs, de les coder en dur ou de les enregistrer en texte brut dans un emplacement accessible à d’autres personnes. Effectuez une rotation de vos clés si vous pensez qu’elles ont pu être compromises.

Vue d’ensemble

Cet article explique comment créer une application qui permet à un codeur de diffuser en continu des activités de codage à un public. Vous générez cette application à l’aide de :

  • Éditeur Monaco, l’éditeur de code qui alimente Visual Studio Code.
  • Express, une infrastructure web Node.js.
  • Les API que la bibliothèque Socket.IO fournit pour la communication en temps réel.
  • Héberger des connexions Socket.IO qui utilisent Web PubSub pour Socket.IO.

L’application terminée

L’application terminée permet à l’utilisateur d’un éditeur de code de partager un lien web via lequel les utilisateurs peuvent regarder le codage.

Capture d’écran de l’application de streaming de code terminée.

Pour que les procédures restent ciblées et puissent être digérées en une quinzaine de minutes, cet article définit deux rôles d’utilisateur et ce qu’ils peuvent faire dans l’éditeur :

  • Un codeur, qui peut écrire dans l’éditeur en ligne et dont le contenu est diffusé en continu
  • Un spectateur, qui reçoit du contenu en temps réel écrit par le codeur et qui ne peut pas modifier le contenu

Architecture

Article Objectif Avantages
Bibliothèque Socket.IO Fournit un mécanisme d’échange de données bidirectionnel à faible latence entre l’application principale et les clients Des API faciles à utiliser qui couvrent la plupart des scénarios de communication en temps réel
Web PubSub pour Socket.IO Héberge WebSocket ou des connexions persistantes basées sur des sondages avec des clients Socket.IO Prise en charge de 100 000 connexions simultanées ; architecture d’application simplifiée

Diagramme montrant comment le service Web PubSub pour Socket.IO connecte les clients à un serveur.

Prérequis

Pour suivre les étapes décrites dans cet article, vous avez besoin :

Créer une ressource Web PubSub pour Socket.IO

Utilisez Azure CLI pour créer la ressource :

az webpubsub create -n <resource-name> \
                    -l <resource-location> \
                    -g <resource-group> \
                    --kind SocketIO \
                    --sku Free_F1

Obtenir une chaîne de connexion

Une chaîne de connexion vous permet de vous connecter à Web PubSub pour Socket.IO.

Exécutez les commandes suivantes : Conservez la chaîne de connexion retournée quelque part, car vous en aurez besoin lorsque vous exécuterez l’application plus loin dans cet article.

az webpubsub key show -n <resource-name> \ 
                      -g <resource-group> \ 
                      --query primaryKey \
                      -o tsv

Écrire le code côté serveur de l’application

Commencez à écrire le code de votre application en travaillant côté serveur.

Générer un serveur HTTP

  1. Créez un projet Node.js :

    mkdir codestream
    cd codestream
    npm init
    
  2. Installez le kit de développement logiciel (SDK) serveur et Express :

    npm install @azure/web-pubsub-socket.io
    npm install express
    
  3. Importez les packages requis et créez un serveur HTTP pour traiter les fichiers statiques :

    /*server.js*/
    
    // Import required packages
    const express = require('express');
    const path = require('path');
    
    // Create an HTTP server based on Express
    const app = express();
    const server = require('http').createServer(app);
    
    app.use(express.static(path.join(__dirname, 'public')));
    
  4. Définissez un point de terminaison appelé /negotiate. Le client du codeur atteint d’abord ce point de terminaison. Ce point de terminaison retourne une réponse HTTP. La réponse contient un point de terminaison que le client doit utiliser pour établir une connexion persistante. Elle retourne également une valeur room à laquelle le client est affecté.

    /*server.js*/
    app.get('/negotiate', async (req, res) => {
        res.json({
            url: endpoint
            room_id: Math.random().toString(36).slice(2, 7),
        });
    });
    
    // Make the Socket.IO server listen on port 3000
    io.httpServer.listen(3000, () => {
        console.log('Visit http://localhost:%d', 3000);
    });
    

Créer le serveur Web PubSub pour Socket.IO

  1. Importez le kit de développement logiciel (SDK) Web PubSub pour Socket.IO et définissez les options suivantes :

    Des chaînes de connexion brutes sont utilisées dans cet article uniquement à des fins de démonstration. Dans les environnements de production, protégez toujours vos clés d’accès. Utilisez Azure Key Vault pour gérer et permuter vos clés de façon sécurisée, et sécurisez votre connexion avec WebPubSubServiceClient.

    /*server.js*/
    const { useAzureSocketIO } = require("@azure/web-pubsub-socket.io");
    
    const wpsOptions = {
        hub: "codestream",
        connectionString: process.argv[2]
    }
    
  2. Créer un serveur Web PubSub pour Socket.IO :

    /*server.js*/
    
    const io = require("socket.io")();
    useAzureSocketIO(io, wpsOptions);
    

Les deux étapes sont légèrement différentes de la façon dont vous créez normalement un serveur Socket.IO, comme décrit dans cette documentation Socket.IO. Avec ces deux étapes, votre code côté serveur peut décharger la gestion des connexions persistantes vers un service Azure. Avec l’aide d’un service Azure, votre serveur d’applications agit uniquement comme un serveur HTTP léger.

Implémenter une logique métier

Maintenant que vous avez créé un serveur Socket.IO hébergé par Web PubSub, vous pouvez définir la façon dont les clients et le serveur communiquent à l’aide des API de Socket.IO. Ce processus est appelé l’implémentation de la logique métier.

  1. Une fois qu’un client est connecté, le serveur d’applications indique au client qu’il est connecté en envoyant un événement personnalisé nommé login.

    /*server.js*/
    io.on('connection', socket => {
        socket.emit("login");
    });
    
  2. Chaque client émet deux événements auxquels le serveur peut répondre : joinRoom et sendToRoom. Une fois que le serveur reçoit la valeur room_id qu’un client souhaite rejoindre, vous utilisez socket.join de l’API Socket.IO pour rejoindre le client cible à la salle spécifiée.

    /*server.js*/
    socket.on('joinRoom', async (message) => {
        const room_id = message["room_id"];
        await socket.join(room_id);
    });
    
  3. Une fois qu’un client est joint, le serveur informe le client du résultat réussi en envoyant un événement message. Lorsque le client reçoit un événement message avec un type de ackJoinRoom, le client peut demander au serveur d’envoyer le dernier état de l’éditeur.

    /*server.js*/
    socket.on('joinRoom', async (message) => {
        // ...
        socket.emit("message", {
            type: "ackJoinRoom", 
            success: true 
        })
    });
    
    /*client.js*/
    socket.on("message", (message) => {
        let data = message;
        if (data.type === 'ackJoinRoom' && data.success) {
            sendToRoom(socket, `${room_id}-control`, { data: 'sync'});
        }
        // ... 
    });
    
  4. Lorsqu’un client envoie un événement sendToRoom au serveur, le serveur diffuse les modifications apportées à l’état de l’éditeur de code dans la salle spécifiée. Tous les clients de la salle peuvent désormais recevoir la dernière mise à jour.

    socket.on('sendToRoom', (message) => {
        const room_id = message["room_id"]
        const data = message["data"]
    
        socket.broadcast.to(room_id).emit("message", {
            type: "editorMessage",
            data: data
        });
    });
    

Écrire le code côté client de l’application

Maintenant que les procédures côté serveur sont terminées, vous pouvez travailler côté client.

Configuration initiale

Vous devez créer un client Socket.IO pour communiquer avec le serveur. La question est de savoir avec quel serveur le client doit établir une connexion persistante. Étant donné que vous utilisez Web PubSub pour Socket.IO, le serveur est un service Azure. Rappelez-vous que vous avez défini un itinéraire /negotiate pour servir aux clients un point de terminaison Web PubSub pour Socket.IO.

/*client.js*/

async function initialize(url) {
    let data = await fetch(url).json()

    updateStreamId(data.room_id);

    let editor = createEditor(...); // Create an editor component

    var socket = io(data.url, {
        path: "/clients/socketio/hubs/codestream",
    });

    return [socket, editor, data.room_id];
}

La fonction initialize(url) organise quelques opérations d’installation ensemble :

  • Récupère le point de terminaison à un service Azure à partir de votre serveur HTTP
  • Crée une instance de l’éditeur Monaco
  • Établit une connexion persistante avec Web PubSub pour Socket.IO

Client du codeur

Comme mentionné précédemment, vous avez deux rôles d’utilisateur côté client : le codeur et le spectateur. Tout ce que le codeur écrit est diffusé sur l’écran du spectateur.

  1. Obtenez le point de terminaison à Web PubSub pour Socket.IO et la valeur room_id :

    /*client.js*/
    
    let [socket, editor, room_id] = await initialize('/negotiate');
    
  2. Lorsque le client du codeur est connecté au serveur, le serveur envoie un événement login au codeur. Le codeur peut répondre en demandant au serveur de se joindre à une salle spécifiée. Toutes les 200 millisecondes, le client du codeur envoie le dernier état de l’éditeur à la salle. Une fonction nommée flush organise la logique d’envoi.

    /*client.js*/
    
    socket.on("login", () => {
        updateStatus('Connected');
        joinRoom(socket, `${room_id}`);
        setInterval(() => flush(), 200);
        // Update editor content
        // ...
    });
    
  3. Si un codeur n’apporte aucune modification, flush() ne fait rien et retourne simplement. Sinon, les modifications apportées à l’état de l’éditeur sont envoyées à la salle.

    /*client.js*/
    
    function flush() {
        // No changes from editor need to be flushed
        if (changes.length === 0) return;
    
        // Broadcast the changes made to editor content
        sendToRoom(socket, room_id, {
            type: 'delta',
            changes: changes
            version: version++,
        });
    
        changes = [];
        content = editor.getValue();
    }
    
  4. Lorsqu’un nouveau client de spectateur est connecté, le spectateur doit obtenir le dernier état complet de l’éditeur. Pour ce faire, un message qui contient les données sync est envoyé au client du codeur. Le message demande au client du codeur d’envoyer l’état complet de l’éditeur.

    /*client.js*/
    
    socket.on("message", (message) => {
        let data = message.data;
        if (data.data === 'sync') {
            // Broadcast the full content of the editor to the room
            sendToRoom(socket, room_id, {
                type: 'full',
                content: content
                version: version,
            });
        }
    });
    

Client du spectateur

  1. Comme le client du codeur, le client du spectateur crée son client Socket.IO via initialize(). Lorsque le client du spectateur est connecté et reçoit un événement login du serveur, il demande au serveur de se joindre à la salle spécifiée. La requête room_id spécifie la salle.

    /*client.js*/
    
    let [socket, editor] = await initialize(`/register?room_id=${room_id}`)
    socket.on("login", () => {
        updateStatus('Connected');
        joinRoom(socket, `${room_id}`);
    });
    
  2. Lorsqu’un client du spectateur reçoit un événement message du serveur et que le type de données est ackJoinRoom, le client du spectateur demande au client du codeur dans la salle d’envoyer l’état complet de l’éditeur.

    /*client.js*/
    
    socket.on("message", (message) => {
        let data = message;
        // Ensures the viewer client is connected
        if (data.type === 'ackJoinRoom' && data.success) { 
            sendToRoom(socket, `${id}`, { data: 'sync'});
        } 
        else //...
    });
    
  3. Si le type de données est editorMessage, le client du spectateur met à jour l’éditeur en fonction de son contenu actuel.

    /*client.js*/
    
    socket.on("message", (message) => {
        ...
        else 
            if (data.type === 'editorMessage') {
            switch (data.data.type) {
                case 'delta':
                    // ... Let editor component update its status
                    break;
                case 'full':
                    // ... Let editor component update its status
                    break;
            }
        }
    });
    
  4. Implémentez joinRoom() et sendToRoom() en utilisant les API Socket.IO :

    /*client.js*/
    
    function joinRoom(socket, room_id) {
        socket.emit("joinRoom", {
            room_id: room_id,
        });
    }
    
    function sendToRoom(socket, room_id, data) {
        socket.emit("sendToRoom", {
            room_id: room_id,
            data: data
        });
    }
    

Exécution de l'application

Localiser le référentiel

Les sections précédentes ont abordé la logique principale liée à la synchronisation de l’état de l’éditeur entre les spectateurs et le codeur. Vous trouverez le code complet dans le référentiel d’exemples.

Cloner le référentiel

Vous pouvez cloner le référentiel et exécuter npm install pour installer les dépendances du projet.

Démarrer le serveur

node server.js <web-pubsub-connection-string>

Il s’agit de la chaîne de connexion que vous avez reçue lors d’une étape précédente.

Lire avec l’éditeur de code en temps réel

Ouvrez http://localhost:3000 sur un onglet du navigateur. Ouvrez un autre onglet avec l’URL affichée sur la première page web.

Si vous écrivez du code sous le premier onglet, vous devez voir votre saisie reflétée en temps réel sur l’autre onglet. Web PubSub pour Socket.IO facilite la transmission de messages dans le cloud. Votre serveur express sert uniquement le fichier index.html statique et le point de terminaison /negotiate.