Creare un'app di streaming di codice in tempo reale usando Socket.IO e ospitarla in Azure
La creazione di un'esperienza in tempo reale come la funzionalità di creazione condivisa in Microsoft Word può risultare complessa.
Grazie alle API facili da usare, Socket.IO si è dimostrata come una libreria per la comunicazione in tempo reale tra client e server. Tuttavia, Socket.IO gli utenti spesso segnalano difficoltà nel ridimensionare le connessioni di Socket.IO. Con Web PubSub per Socket.IO, gli sviluppatori non devono più preoccuparsi della gestione delle connessioni persistenti.
Importante
Le stringa di connessione non elaborate vengono visualizzate in questo articolo solo a scopo dimostrativo.
Una stringa di connessione include le informazioni sull'autorizzazione necessarie all'applicazione per l'accesso al servizio Azure Web PubSub. La chiave di accesso all'interno della stringa di connessione è simile a una password radice per il servizio. Negli ambienti di produzione proteggere sempre le chiavi di accesso. Usare Azure Key Vault per gestire e ruotare le chiavi in modo sicuro e proteggere la connessione con WebPubSubServiceClient
.
Evitare di distribuire le chiavi di accesso ad altri utenti, impostarle come hardcoded o salvarle in un file di testo normale accessibile ad altri. Ruotare le chiavi se si ritiene che siano state compromesse.
Panoramica
Questo articolo illustra come creare un'app che consente a un coder di trasmettere attività di codifica a un gruppo di destinatari. Per compilare questa applicazione, usare:
- Editor Monaco, l'editor di codice che supporta Visual Studio Code.
- Express, un framework Web Node.js.
- API fornite dalla libreria di Socket.IO per la comunicazione in tempo reale.
- Ospitare Socket.IO connessioni che usano Web PubSub per Socket.IO.
L'app completata
L'app completata consente all'utente di un editor di codice di condividere un collegamento Web tramite il quale gli utenti possono osservare la digitazione.
Per mantenere incentrate e digeribili le procedure in circa 15 minuti, questo articolo definisce due ruoli utente e le operazioni che possono eseguire nell'editor:
- Writer, che può digitare nell'editor online e il contenuto viene trasmesso in streaming
- Visualizzatori, che ricevono contenuti in tempo reale digitati dal writer e non possono modificare il contenuto
Architettura
Articolo | Scopo | Vantaggi |
---|---|---|
libreria Socket.IO | Fornisce un meccanismo di scambio di dati bidirezionali a bassa latenza tra l'applicazione back-end e i client | API facili da usare che coprono la maggior parte degli scenari di comunicazione in tempo reale |
Web PubSub per Socket.IO | Ospita connessioni persistenti basate su WebSocket o su polling con client Socket.IO | Supporto per 100.000 connessioni simultanee; architettura dell'applicazione semplificata |
Prerequisiti
Per seguire tutti i passaggi descritti in questo articolo, è necessario:
- Un account Azure. Se non si ha una sottoscrizione di Azure, creare un account Azure gratuito prima di iniziare.
- Interfaccia della riga di comando di Azure (versione 2.29.0 o successiva) o Azure Cloud Shell per gestire le risorse di Azure.
- Conoscenza di base delle API di Socket.IO.
Creare un sito Web PubSub per Socket.IO risorsa
Usare l'interfaccia della riga di comando di Azure per creare la risorsa:
az webpubsub create -n <resource-name> \
-l <resource-location> \
-g <resource-group> \
--kind SocketIO \
--sku Free_F1
Ottenere una stringa di connessione
Un stringa di connessione consente di connettersi con Web PubSub per Socket.IO.
Eseguire i comandi seguenti. Mantenere il stringa di connessione restituito da qualche parte, perché sarà necessario quando si esegue l'applicazione più avanti in questo articolo.
az webpubsub key show -n <resource-name> \
-g <resource-group> \
--query primaryKey \
-o tsv
Scrivere il codice lato server dell'applicazione
Iniziare a scrivere il codice dell'applicazione lavorando sul lato server.
Creare un server HTTP
Creare un progetto Node.js:
mkdir codestream cd codestream npm init
Installare l'SDK del server ed Express:
npm install @azure/web-pubsub-socket.io npm install express
Importare i pacchetti necessari e creare un server HTTP per gestire i file statici:
/*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')));
Definire un endpoint denominato
/negotiate
. Un client writer raggiunge prima questo endpoint. Questo endpoint restituisce una risposta HTTP. La risposta contiene un endpoint che il client deve usare per stabilire una connessione permanente. Restituisce anche unroom
valore a cui è assegnato il client./*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); });
Creare web PubSub per Socket.IO server
Importare Web PubSub per Socket.IO SDK e definire le opzioni:
Le stringa di connessione non elaborate vengono visualizzate in questo articolo solo a scopo dimostrativo. Negli ambienti di produzione proteggere sempre le chiavi di accesso. Usare Azure Key Vault per gestire e ruotare le chiavi in modo sicuro e proteggere la connessione con
WebPubSubServiceClient
./*server.js*/ const { useAzureSocketIO } = require("@azure/web-pubsub-socket.io"); const wpsOptions = { hub: "codestream", connectionString: process.argv[2] }
Creare un Web PubSub per Socket.IO server:
/*server.js*/ const io = require("socket.io")(); useAzureSocketIO(io, wpsOptions);
I due passaggi sono leggermente diversi da come si crea normalmente un server Socket.IO, come descritto in questa documentazione Socket.IO. Con questi due passaggi, il codice lato server può eseguire l'offload della gestione delle connessioni permanenti a un servizio di Azure. Con l'aiuto di un servizio di Azure, il server applicazioni funge solo da server HTTP leggero.
Implementare la logica di business
Ora che è stato creato un server Socket.IO ospitato da Web PubSub, è possibile definire il modo in cui i client e il server comunicano usando le API di Socket.IO. Questo processo viene chiamato implementazione della logica di business.
Dopo la connessione di un client, il server applicazioni indica al client che è connesso inviando un evento personalizzato denominato
login
./*server.js*/ io.on('connection', socket => { socket.emit("login"); });
Ogni client genera due eventi a cui il server può rispondere:
joinRoom
esendToRoom
. Dopo che il server ottiene ilroom_id
valore che un client vuole aggiungere, si usa dall'APIsocket.join
di Socket.IO per aggiungere il client di destinazione alla sala specificata./*server.js*/ socket.on('joinRoom', async (message) => { const room_id = message["room_id"]; await socket.join(room_id); });
Dopo che un client è stato aggiunto, il server informa il client del risultato riuscito inviando un
message
evento. Quando il client riceve unmessage
evento con un tipo diackJoinRoom
, il client può chiedere al server di inviare lo stato dell'editor più recente./*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'}); } // ... });
Quando un client invia un
sendToRoom
evento al server, il server trasmette le modifiche allo stato dell'editor di codice nella sala specificata. Tutti i client nella sala possono ora ricevere l'aggiornamento più recente.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 }); });
Scrivere il codice lato client dell'applicazione
Ora che le procedure sul lato server sono state completate, è possibile lavorare sul lato client.
Configurazione iniziale
È necessario creare un client Socket.IO per comunicare con il server. La domanda è il server con cui il client deve stabilire una connessione permanente. Poiché si usa Web PubSub per Socket.IO, il server è un servizio di Azure. Tenere presente che è stata definita una route /negotiate per servire i client a Web PubSub per 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 initialize(url)
funzione organizza alcune operazioni di configurazione insieme:
- Recupera l'endpoint in un servizio di Azure dal server HTTP
- Crea un'istanza dell'editor monaco
- Stabilisce una connessione permanente con Web PubSub per Socket.IO
Client writer
Come accennato in precedenza, si hanno due ruoli utente sul lato client: writer e visualizzatore. Tutto ciò che i tipi di writer vengono trasmessi alla schermata del visualizzatore.
Ottenere l'endpoint su Web PubSub per Socket.IO e il
room_id
valore:/*client.js*/ let [socket, editor, room_id] = await initialize('/negotiate');
Quando il client writer è connesso al server, il server invia un
login
evento al writer. Il writer può rispondere chiedendo al server di aggiungersi a una sala specificata. Ogni 200 millisecondi, il client writer invia lo stato dell'editor più recente alla sala. Una funzione denominataflush
organizza la logica di invio./*client.js*/ socket.on("login", () => { updateStatus('Connected'); joinRoom(socket, `${room_id}`); setInterval(() => flush(), 200); // Update editor content // ... });
Se un writer non apporta modifiche,
flush()
non esegue alcuna operazione e restituisce semplicemente. In caso contrario, le modifiche apportate allo stato dell'editor vengono inviate alla sala./*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(); }
Quando un nuovo client visualizzatore è connesso, il visualizzatore deve ottenere lo stato completo più recente dell'editor. A tale scopo, viene inviato un messaggio che contiene
sync
dati al client writer. Il messaggio chiede al client writer di inviare lo stato completo dell'editor./*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 visualizzatore
Analogamente al client writer, il client del visualizzatore crea il client Socket.IO tramite
initialize()
. Quando il client del visualizzatore è connesso e riceve unlogin
evento dal server, chiede al server di aggiungersi alla sala specificata. La queryroom_id
specifica la stanza./*client.js*/ let [socket, editor] = await initialize(`/register?room_id=${room_id}`) socket.on("login", () => { updateStatus('Connected'); joinRoom(socket, `${room_id}`); });
Quando un client visualizzatore riceve un
message
evento dal server e il tipo di dati èackJoinRoom
, il client del visualizzatore chiede al client writer nella sala di inviare lo stato dell'editor completo./*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 //... });
Se il tipo di dati è
editorMessage
, il client del visualizzatore aggiorna l'editor in base al contenuto effettivo./*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; } } });
Implementare
joinRoom()
esendToRoom()
usando le API di 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 }); }
Eseguire l'applicazione
Individuare il repository
Le sezioni precedenti hanno illustrato la logica di base correlata alla sincronizzazione dello stato dell'editor tra visualizzatori e writer. È possibile trovare il codice completo nel repository di esempi.
Clonare il repository
È possibile clonare il repository ed eseguire npm install
per installare le dipendenze del progetto.
Avviare il server
node server.js <web-pubsub-connection-string>
Questo è il stringa di connessione ricevuto in un passaggio precedente.
Eseguire la riproduzione con l'editor di codice in tempo reale
Aprire in una scheda del browser. Aprire http://localhost:3000
un'altra scheda con l'URL visualizzato nella prima pagina Web.
Se si scrive codice nella prima scheda, la digitazione dovrebbe essere riflessa in tempo reale nell'altra scheda. Web PubSub per Socket.IO facilita il passaggio del messaggio nel cloud. Il express
server serve solo il file statico index.html
e l'endpoint /negotiate
.