WebSocketStream: integrazione degli stream con l'API WebSocket

Evita che la tua app venga sommersa da messaggi WebSocket o inondi un server WebSocket con messaggi applicando la backpressure.

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 della pipeline è ancora occupato e non è ancora pronto ad accettare altri chunk, invia un segnale a ritroso lungo la catena per rallentare l'invio, se necessario.

Il problema con l'attuale API WebSocket

L'applicazione della backpressure ai messaggi ricevuti è impossibile

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 configurerai un flusso simile al codice riportato di seguito e, poiché await il risultato della chiamata process(), non dovresti avere problemi, 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(), continuerà ad aumentare.

Che cos'è l'API WebSocketStream?

L'API WebSocketStream risolve il problema della backpressure inesistente o non ergonomica integrando gli stream 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

Ecco alcuni esempi di siti che possono utilizzare questa API:

  • Applicazioni WebSocket ad alta larghezza di banda che devono mantenere l'interattività, in particolare la condivisione di video e schermo.
  • Analogamente, le applicazioni di acquisizione video e altre che generano molti dati nel browser 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 video esplicativo Completato
2. Creare una bozza iniziale della specifica In corso
3. Raccogli feedback e esegui l'iterazione sul design 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 sia opened, ovvero che venga visualizzato un valore ReadableStream e/o WritableStream.

Se chiami 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() ottieni finalmente un WritableStreamDefaultWriter, a cui puoi poi write() trasferire 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);
  }

Pressione di ricircolo

Che ne è della funzionalità di contropressione promessa? Puoi ottenerlo "senza costi", senza passaggi aggiuntivi. Se process() richiede più tempo, il messaggio successivo viene utilizzato solo quando la pipeline è pronta. Analogamente, il passaggio WritableStreamDefaultWriter.write() procede solo se è sicuro farlo.

Esempi avanzati

Il secondo argomento di WebSocketStream è un bag di opzioni per consentire l'estensione futura. 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 vengono fornite da questa promessa, poiché non sono pertinenti se la connessione non va a buon fine.

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 utilizzare anche il metodo WebSocketStream.close(), ma il suo scopo principale è consentire di specificare il codice e il motivo inviati 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. È possibile applicare la backpressure ai messaggi inviati, ma questo comporta il polling della proprietà WebSocket.bufferedAmount, che è inefficiente e non ergonomico.

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 una segnalazione relativa alle specifiche nel repository GitHub corrispondente o aggiungi il tuo parere a una segnalazione esistente.

Segnalare un problema con l'implementazione

Hai trovato un bug nell'implementazione di Chrome? Oppure l'implementazione è diversa dalla specifica? Segnala un bug all'indirizzo new.crbug.com. Assicurati di includere il maggior numero di dettagli possibile, istruzioni semplici per la riproduzione e inserire Blink>Network>WebSockets nella casella Componenti. Glitch è ideale per condividere casi di riproduzione rapidi e semplici.

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 all'indirizzo @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.