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.
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 |
Prérequis
Pour suivre les étapes décrites dans cet article, vous avez besoin :
- Un compte Azure. Si vous n’en avez pas, créez un compte gratuit Azure avant de commencer.
- D’Azure CLI (version 2.29.0 ou ultérieure) ou d’Azure Cloud Shell pour gérer les ressources Azure.
- De connaissance de base des API de Socket.IO.
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
Créez un projet Node.js :
mkdir codestream cd codestream npm init
Installez le kit de développement logiciel (SDK) serveur et Express :
npm install @azure/web-pubsub-socket.io npm install express
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')));
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 valeurroom
à 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
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] }
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.
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"); });
Chaque client émet deux événements auxquels le serveur peut répondre :
joinRoom
etsendToRoom
. Une fois que le serveur reçoit la valeurroom_id
qu’un client souhaite rejoindre, vous utilisezsocket.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); });
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énementmessage
avec un type deackJoinRoom
, 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'}); } // ... });
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.
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');
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éeflush
organise la logique d’envoi./*client.js*/ socket.on("login", () => { updateStatus('Connected'); joinRoom(socket, `${room_id}`); setInterval(() => flush(), 200); // Update editor content // ... });
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(); }
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
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énementlogin
du serveur, il demande au serveur de se joindre à la salle spécifiée. La requêteroom_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}`); });
Lorsqu’un client du spectateur reçoit un événement
message
du serveur et que le type de données estackJoinRoom
, 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 //... });
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; } } });
Implémentez
joinRoom()
etsendToRoom()
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
.