WebSocketStream: integrazione dei flussi con l'API WebSocket

Impedisci all'app di essere annegata nei messaggi WebSocket o di inondare un server WebSocket di messaggi applicando una contropressione.

Sfondo

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 interrogare il server per ricevere 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 elaborarle come desiderato. Un concetto importante nel contesto degli stream è rappresentato contropressione. Questo è il processo mediante il quale un singolo flusso o una catena di tubi regola la velocità di lettura o scrittura. Quando il flusso stesso o uno stream successivo nella catena delle pipeline è ancora occupato e non è ancora pronto ad accettare altri chunk, ma invia un segnale all'indietro attraverso la catena per rallentare la consegna, a seconda dei casi.

Il problema con l'API WebSocket attuale

Impossibile applicare la contropressione ai messaggi ricevuti

Con l'API WebSocket attuale, le reazioni a un messaggio avvengono in WebSocket.onmessage, EventHandler chiamato alla ricezione di un messaggio dal server.

Supponiamo che tu abbia un'applicazione che deve eseguire operazioni di analisi dei dati pesanti ogni volta che viene ricevuto un nuovo messaggio. Probabilmente configureresti il flusso in modo simile al codice seguente, e siccome hai 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'API WebSocket attuale è che non è possibile applicare una contropressione. Quando i messaggi arrivano più velocemente di quanto sia in grado di gestire il metodo process(), il processo di rendering riempie la memoria con il buffering di questi messaggi, non rispondono per il 100% di utilizzo della CPU o per entrambi.

L'applicazione della contropressione ai messaggi inviati non è ergonomica

È possibile applicare una contropressione ai messaggi inviati, ma occorre eseguire il polling della 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 inviati tutti i dati in coda ma se continui a chiamare WebSocket.send(), continuerà a salire.

Che cos'è l'API WebSocketStream?

L'API WebSocketStream risolve il problema della contropressione inesistente o non ergonomica. integrando i flussi con l'API WebSocket. Ciò significa che la contropressione può essere applicata "senza costi", senza costi aggiuntivi.

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 schermate.
  • Analogamente, l'acquisizione video e altre applicazioni che generano molti dati nel browser che deve essere caricato sul server. Con la contropressione, il client può smettere di produrre dati invece di accumulare dati in memoria.

Stato attuale

Passaggio Stato
1. Crea messaggio esplicativo Completato
2. Crea la bozza iniziale delle specifiche In corso
3. Raccogli feedback e esegui l'iterazione del design In corso
4. Prova dell'origine Completato
5. Lancio Non avviato

Come utilizzare l'API WebSocketStream

Esempio introduttivo

L'API WebSocketStream è basata sulla promessa, il che rende naturale la gestione in un mondo JavaScript moderno. Inizierai costruendo un nuovo WebSocketStream e passandogli l'URL del server WebSocket. Ora attendi che la connessione arrivi a opened, il che comporta ReadableStream e/o un WritableStream.

Chiamando il metodo ReadableStream.getReader() , ottieni infine un ReadableStreamDefaultReader, che potrai poi read() i dati da quando il flusso è terminato, cioè finché non restituisce un oggetto nel formato {value: undefined, done: true}.

Di conseguenza, richiamando il WritableStream.getWriter() , ottieni infine un WritableStreamDefaultWriter, che potrai poi write() a cui inviare 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 caratteristica di contropressione promessa? Come ho scritto sopra, l'operazione è "senza costi" e non richiede ulteriori passaggi. Se process() richiede più tempo, il messaggio successivo verrà utilizzato solo quando la pipeline è pronta. Allo stesso modo, il passaggio WritableStreamDefaultWriter.write() procederà solo se è sicuro farlo.

Esempi avanzati

Il secondo argomento per WebSocketStream è un pacchetto di opzioni per consentire estensioni future. Al momento l'unica opzione è protocols, che si comporta allo stesso modo secondo argomento al costruttore WebSocket:

const chatWSS = new WebSocketStream(CHAT_URL, {protocols: ['chat', 'chatv2']});
const {protocol} = await chatWSS.opened;

La protocol selezionata e le potenziali extensions fanno parte del dizionario disponibili tramite la promessa WebSocketStream.opened. Tutte le informazioni sulla connessione dal vivo sono fornite da questa promessa, poiché non è pertinente se la connessione non riesce.

const {readable, writable, protocol, extensions} = await chatWSS.opened;

Informazioni sulla connessione WebSocketStream chiusa

Le informazioni disponibili nel WebSocket.onclose e WebSocket.onerror eventi nell'API WebSocket è ora disponibile tramite la promessa WebSocketStream.closed. La promessa viene rifiutata in caso di chiusura sporca, altrimenti si risolve nel codice e nella motivazione inviati dal server.

Tutti i codici di stato possibili e il loro significato sono spiegati nel elenco di codici di stato CloseEvent.

const {code, reason} = await chatWSS.closed;

Chiusura di una connessione WebSocketStream

Un WebSocketStream può essere chiuso con AbortController Di conseguenza, trasmetti un valore 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 codice e il motivo che viene inviato al server.

wss.close({code: 4000, reason: 'Game over'});

Miglioramento progressivo e interoperabilità

Chrome è attualmente 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 una contropressione ai messaggi inviati, ma occorre eseguire il polling della WebSocket.bufferedAmount che è inefficiente e non ergonomica.

Rilevamento delle caratteristiche

Per verificare se l'API WebSocketStream è supportata, utilizza:

if ('WebSocketStream' in window) {
  // `WebSocketStream` is supported!
}

Demo

Sui browser che supportano, 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.

Parlaci della progettazione dell'API

C'è qualcosa nell'API che non funziona come previsto? Oppure mancano metodi o proprietà che ti servono per implementare la tua idea? Hai una domanda o un commento sul modello di sicurezza? Segnala 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. Includi il maggior numero di dettagli possibile, semplici istruzioni per la riproduzione, e inserisci Blink>Network>WebSockets nella casella Componenti. Glitch è perfetto per condividere cover di riproduzione semplici e veloci.

Mostra il supporto per l'API

Intendi utilizzare l'API WebSocketStream? Il tuo supporto pubblico aiuta il team di Chrome a dare priorità alle funzionalità e mostra agli altri fornitori di browser quanto sia fondamentale supportarli.

Invia un tweet a @ChromiumDev utilizzando l'hashtag #WebSocketStream: e facci sapere dove e come lo utilizzi.

Link utili

Ringraziamenti

L'API WebSocketStream è stata implementata da Adam Rice e Yutaka Hirano. Immagine hero di Daan Mooij su Rimuovi schermo.