Impedisci che la tua app venga annegata nei messaggi WebSocket o inonda di messaggi un server WebSocket applicando la contropressione.
Contesto
L'API WebSocket
L'API WebSocket fornisce un'interfaccia JavaScript al protocollo WebSocket, che consente di aprire una sessione di comunicazione interattiva bidirezionale tra il browser dell'utente e un server. Con questa API, puoi inviare messaggi a un server e ricevere risposte basate su eventi senza chiedere al server una risposta.
API Streams
L'API Streams consente a JavaScript di accedere in modo programmatico ai flussi di blocchi di dati ricevuti sulla rete ed elaborarli come desiderato. Un concetto importante nel contesto dei flussi di dati è la contropressione. Si tratta del processo mediante il quale un singolo flusso o una pipeline regola la velocità di lettura o scrittura. Quando il flusso stesso o uno stream successivo nella catena di pipeline è ancora occupato e non è ancora pronto ad accettare altri blocchi, invia un segnale a ritroso attraverso la catena per rallentare la distribuzione, come opportuno.
Il problema con l'API WebSocket attuale
Non è possibile applicare la contropressione ai messaggi ricevuti
Con l'API WebSocket attuale, la reazione a un messaggio avviene in
WebSocket.onmessage
,
un EventHandler
richiamato alla ricezione di un messaggio dal server.
Supponiamo che tu abbia un'applicazione che deve eseguire pesanti operazioni di analisi dei dati ogni volta che viene ricevuto un nuovo messaggio.
Probabilmente configureresti il flusso in modo simile al codice seguente.
Dato che await
il risultato della chiamata process()
, dovresti essere a posto, giusto?
// A heavy data crunching operation.
const process = async (data) => {
return new Promise((resolve) => {
window.setTimeout(() => {
console.log('WebSocket message processed:', data);
return resolve('done');
}, 1000);
});
};
webSocket.onmessage = async (event) => {
const data = event.data;
// Await the result of the processing step in the message handler.
await process(data);
};
Sbagliato. Il problema con l'API WebSocket attuale è che non esiste un modo per applicare la contropressione.
Quando i messaggi arrivano più velocemente di quanto il metodo process()
sia in grado di gestirli, il processo di rendering riempirà la memoria eseguendo il buffering di questi messaggi, non risponderà più a causa dell'utilizzo del 100% della CPU o entrambi.
La contropressione ai messaggi inviati non è ergonomica
È possibile applicare la contropressione ai messaggi inviati, ma comporta il polling della proprietà WebSocket.bufferedAmount
, che è inefficiente e non ergonomica.
Questa proprietà di sola lettura restituisce il numero di byte di dati che sono stati messi in coda utilizzando le chiamate a WebSocket.send()
, ma non ancora trasmessi alla rete.
Questo valore viene reimpostato su zero una volta che tutti i dati in coda sono stati inviati, ma se continui a chiamare WebSocket.send()
, il numero continuerà a salire.
Che cos'è l'API WebSocketStream?
L'API WebSocketStream affronta il problema di contropressione inesistente o non ergonomica integrando i flussi con l'API WebSocket. Ciò significa che la contropressione può essere applicata "senza costi", senza alcun costo aggiuntivo.
Casi d'uso suggeriti per l'API WebSocketStream
Esempi di siti che possono utilizzare questa API includono:
- Applicazioni WebSocket a larghezza di banda elevata che devono mantenere l'interattività, in particolare la condivisione di video e schermo.
- Analogamente, all'acquisizione di video e ad altre applicazioni che generano nel browser molti dati da caricare sul server. Con la contropressione, il client può smettere di produrre dati anziché accumulare dati in memoria.
Stato attuale
Come utilizzare l'API WebSocketStream
Esempio introduttivo
L'API WebSocketStream si basa sulle promesse, il che fa sembrare naturale
in un mondo JavaScript moderno.
Per iniziare, crea un nuovo WebSocketStream
e trasmettilo l'URL del server WebSocket.
Successivamente, attendi che la connessione sia opened
,
che genererà
ReadableStream
e/o
WritableStream
.
Chiamando il metodo
ReadableStream.getReader()
,
ottieni finalmente un
ReadableStreamDefaultReader
,
da cui puoi read()
usare i dati fino al completamento del flusso, ovvero fino a quando non restituisce un oggetto nel formato
{value: undefined, done: true}
.
Di conseguenza, chiamando il metodo WritableStream.getWriter()
, si ottiene infine un WritableStreamDefaultWriter
, su cui è possibile write()
utilizzare i dati.
const wss = new WebSocketStream(WSS_URL);
const {readable, writable} = await wss.opened;
const reader = readable.getReader();
const writer = writable.getWriter();
while (true) {
const {value, done} = await reader.read();
if (done) {
break;
}
const result = await process(value);
await writer.write(result);
}
Contropressione
E la funzionalità di contropressione promessa?
Come ho scritto sopra, l'offerta è "senza costi" e non sono necessari passaggi aggiuntivi.
Se process()
richiede più tempo, il messaggio successivo verrà utilizzato solo quando la pipeline è pronta.
Analogamente, il passaggio WritableStreamDefaultWriter.write()
verrà eseguito solo se è sicuro farlo.
Esempi avanzati
Il secondo argomento per WebSocketStream è un riquadro di opzioni per consentire l'estensione futura.
Attualmente l'unica opzione è protocols
, che si comporta come il secondo argomento del costruttore WebSocket:
const chatWSS = new WebSocketStream(CHAT_URL, {protocols: ['chat', 'chatv2']});
const {protocol} = await chatWSS.opened;
Il protocol
selezionato e il potenziale extensions
fanno parte del dizionario
disponibile tramite la promessa WebSocketStream.opened
.
Tutte le informazioni sulla connessione in tempo reale sono fornite da questa promessa,
poiché non sono pertinenti in caso di interruzione della connessione.
const {readable, writable, protocol, extensions} = await chatWSS.opened;
Informazioni sulla connessione WebSocketStream chiusa
Le informazioni che erano disponibili negli eventi WebSocket.onclose
e WebSocket.onerror
nell'API WebSocket sono ora disponibili tramite la promessa WebSocketStream.closed
.
La promessa viene rifiutata in caso di chiusura imprevista, altrimenti viene risolta nel codice e nel motivo inviati dal server.
Tutti i possibili codici di stato e il loro significato sono spiegati nell'elenco dei codici di stato CloseEvent
.
const {code, reason} = await chatWSS.closed;
Chiusura di una connessione WebSocketStream
Un WebSocketStream può essere chiuso con un
AbortController
.
Di conseguenza, passa un AbortSignal
al costruttore WebSocketStream
.
const controller = new AbortController();
const wss = new WebSocketStream(URL, {signal: controller.signal});
setTimeout(() => controller.abort(), 1000);
In alternativa, puoi utilizzare anche il metodo WebSocketStream.close()
, ma il suo scopo principale è consentire di specificare il codice e il motivo che viene inviato al server.
wss.close({code: 4000, reason: 'Game over'});
Miglioramento progressivo e interoperabilità
Attualmente Chrome è l'unico browser a implementare l'API WebSocketStream.
Per l'interoperabilità con l'API WebSocket classica, non è possibile applicare la contropressione ai messaggi ricevuti.
È possibile applicare la contropressione ai messaggi inviati, ma comporta il polling della proprietà WebSocket.bufferedAmount
, che è inefficiente e non ergonomica.
Rilevamento funzionalità
Per verificare se l'API WebSocketStream è supportata, utilizza:
if ('WebSocketStream' in window) {
// `WebSocketStream` is supported!
}
Demo
Sui browser che supportano i browser, puoi vedere l'API WebSocketStream in azione nell'iframe incorporato o direttamente su Glitch.
Feedback
Il team di Chrome vuole conoscere le tue esperienze con l'API WebSocketStream.
Parlaci del design dell'API
C'è qualcosa nell'API che non funziona come previsto? Oppure mancano metodi o proprietà per realizzare la tua idea? Hai domande o commenti sul modello di sicurezza? Segnala un problema di specifiche nel repository GitHub corrispondente oppure aggiungi le tue riflessioni su un problema esistente.
Segnalare un problema di implementazione
Hai trovato un bug nell'implementazione di Chrome?
Oppure l'implementazione è diversa dalle specifiche?
Segnala un bug all'indirizzo new.crbug.com. Assicurati di includere il maggior numero possibile di dettagli, semplici istruzioni per la riproduzione e inserisci Blink>Network>WebSockets
nella casella Componenti.
Glitch è perfetto per condividere custodie di riproduzione facile e veloce.
Mostra il supporto per l'API
Intendi utilizzare l'API WebSocketStream? Il tuo supporto pubblico aiuta il team di Chrome a dare la priorità alle funzionalità e mostra ad altri fornitori di browser quanto è importante supportarle.
Invia un tweet a @ChromiumDev usando l'hashtag
#WebSocketStream
e facci sapere dove e come lo usi.
Link utili
- Spiegazione pubblica
- Demo API WebSocketStream | Origine demo API WebSocketStream
- Bug di monitoraggio
- Voce ChromeStatus.com
- Componente Blink:
Blink>Network>WebSockets
Ringraziamenti
L'API WebSocketStream è stata implementata da Adam Rice e Yutaka Hirano. Immagine hero di Daan Mooij su Unsplash.