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 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

Ringraziamenti

L'API WebSocketStream è stata implementata da Adam Rice e Yutaka Hirano.