Impedisci all'app di essere annegata nei messaggi WebSocket o di inondare un server WebSocket di messaggi applicando una contropressione.
Sfondo
L'API WebSocket fornisce un'interfaccia JavaScript per il 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 eseguire polling del server per una risposta.
L'API Streams
L'API Streams consente a JavaScript di accedere in modo programmatico ai flussi di blocchi di dati ricevuti sulla rete e di elaborarli come desiderato. Un concetto importante nel contesto degli stream è la backpressure. Si tratta del processo mediante il quale un singolo stream o una catena di pipe regola la velocità di lettura o scrittura. Quando lo stream stesso o uno stream più avanti nella catena pipe è ancora occupato e non è ancora pronto ad accettare altri blocchi, invia un segnale all'indietro attraverso la catena per rallentare la distribuzione a seconda dei casi.
Il problema con l'API WebSocket attuale
Impossibile applicare la contropressione ai messaggi ricevuti
Con l'attuale API WebSocket, la reazione a un messaggio avviene in
WebSocket.onmessage
,
un EventHandler
chiamato quando viene ricevuto un messaggio dal server.
Supponiamo che tu abbia un'applicazione che deve eseguire operazioni di elaborazione di dati pesanti ogni volta che viene ricevuto un nuovo messaggio.
Probabilmente configureresti il flusso in modo simile al codice riportato
di seguito e dato che await
il risultato della chiamata process()
dovrebbe andare bene, 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'attuale API WebSocket è che non è possibile applicare la backpressure.
Quando i messaggi arrivano più velocemente di quanto il metodo process()
possa gestirli,
la procedura di rendering riempie la memoria mettendo in buffer i messaggi,
non risponde a causa dell'utilizzo della CPU al 100% o entrambe le cose.
L'applicazione della backpressure ai messaggi inviati non è ergonomica
È possibile applicare la backpressure ai messaggi inviati, ma questo comporta il polling della proprietà
WebSocket.bufferedAmount
, che è inefficiente e non ergonomico.
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 inviati tutti i dati in coda,
ma se continui a chiamare WebSocket.send()
,
l'elemento continuerà a salire.
Che cos'è l'API WebSocketStream?
L'API WebSocketStream risolve il problema della contropressione inesistente o non ergonomica mediante l'integrazione dei flussi con l'API WebSocket. Ciò significa che la contropressione può essere applicata "gratuitamente", senza costi aggiuntivi.
Casi d'uso suggeriti per l'API WebSocketStream
Ecco alcuni esempi di siti che possono utilizzare questa API:
- Applicazioni WebSocket a elevata larghezza di banda che devono mantenere l'interattività, in particolare la condivisione di video e schermo.
- Analogamente, l'acquisizione di video e altre applicazioni che generano nel browser molti dati che devono essere caricati sul server. Con la backpressure, il client può interrompere la produzione di dati anziché accumularli in memoria.
Stato attuale
Passaggio | Stato |
---|---|
1. Creare un'animazione esplicativa | Completato |
2. Crea la bozza iniziale delle specifiche | In corso |
3. Raccogli feedback e ottimizza la progettazione | In corso |
4. Prova dell'origine | Completato |
5. Lancio | Non avviato |
Come utilizzare l'API WebSocketStream
L'API WebSocketStream è basata su promesse, il che rende naturale il suo utilizzo
in un mondo JavaScript moderno.
Per iniziare, crea un nuovo WebSocketStream
e passagli l'URL del server WebSocket.
Successivamente, attendi che la connessione venga opened
,
che genera un
ReadableStream
e/o una
WritableStream
.
Chiamando il metodo
ReadableStream.getReader()
ottieni finalmente un
ReadableStreamDefaultReader
,
da cui puoi read()
recuperare i dati fino al termine dello stream, ovvero fino a quando non restituisce un oggetto del tipo
{value: undefined, done: true}
.
Di conseguenza, chiamando il metodo
WritableStream.getWriter()
, potrai ottenere un valore
WritableStreamDefaultWriter
,
in cui potrai successivamente write()
effettuare 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
Che ne è della funzionalità di contropressione promessa?
L'iscrizione è "senza costi", senza bisogno di passaggi aggiuntivi.
Se process()
richiede più tempo, il messaggio successivo viene utilizzato solo quando la pipeline è pronta.
Allo stesso modo, il passaggio WritableStreamDefaultWriter.write()
procede solo se è sicuro farlo.
Esempi avanzati
Il secondo argomento per WebSocketStream è un pacchetto di opzioni per consentire estensioni future.
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 i potenziali extensions
fanno parte del dizionario
disponibile tramite la promessa WebSocketStream.opened
.
Tutte le informazioni sulla connessione in tempo reale sono fornite da questa promessa, in quanto non sono rilevanti se la connessione non riesce.
const {readable, writable, protocol, extensions} = await chatWSS.opened;
Informazioni sulla connessione WebSocketStream chiusa
Le informazioni disponibili dagli 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 non corretta,
altrimenti si risolve nel codice e nel motivo inviati dal server.
Tutti i possibili codici di stato e il relativo 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
.
Pertanto, 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 anche utilizzare 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'});
Potenziamento progressivo e interoperabilità
Chrome è attualmente l'unico browser che implementa l'API WebSocketStream.
Per l'interoperabilità con l'API WebSocket classica,
non è possibile applicare la backpressure ai messaggi ricevuti.
L'applicazione della contropressione ai messaggi inviati è possibile, ma prevede il polling della proprietà WebSocket.bufferedAmount
, che è inefficiente e non ergonomica.
Rilevamento di funzionalità
Per verificare se l'API WebSocketStream è supportata, utilizza:
if ('WebSocketStream' in window) {
// `WebSocketStream` is supported!
}
Demo
Nei browser supportati, puoi vedere l'API WebSocketStream in azione nell'iframe incorporato o direttamente su Glitch.
Feedback
Il team di Chrome vuole conoscere la tua esperienza con l'API WebSocketStream.
Fornisci informazioni sul design dell'API
C'è qualcosa nell'API che non funziona come previsto? Oppure mancano metodi o proprietà necessari per implementare la tua idea? Hai una domanda o un commento sul modello di sicurezza? Invia un problema relativo alle specifiche sul repository GitHub corrispondente o aggiungi le tue opinioni a un problema esistente.
Segnalare un problema con l'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 di dettagli possibile, di semplici istruzioni per la riproduzione e di inserire Blink>Network>WebSockets
nella casella Componenti.
Glitch è ideale per condividere casi di riproduzione facili e veloci.
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 sia fondamentale supportarle.
Invia un tweet a @ChromiumDev usando l'hashtag
#WebSocketStream
e facci sapere dove e come lo utilizzi.
Link utili
- Spiegazione pubblica
- Demo dell'API WebSocketStream | Origine della demo dell'API WebSocketStream
- Monitoraggio del bug
- Voce ChromeStatus.com
- Componente lampeggiante:
Blink>Network>WebSockets
Ringraziamenti
L'API WebSocketStream è stata implementata da Adam Rice e Yutaka Hirano.