WebSocketStream: streams integreren met de WebSocket API

Voorkom dat uw app overspoeld raakt met WebSocket-berichten of een WebSocket-server overspoelt met berichten door backpressure toe te passen.

Achtergrond

De WebSocket API biedt een JavaScript-interface voor het WebSocket-protocol , waarmee een interactieve communicatiesessie in twee richtingen tussen de browser van de gebruiker en een server kan worden opgezet. Met deze API kunt u berichten naar een server sturen en op basis van gebeurtenissen reacties ontvangen zonder de server continu te hoeven pollen.

De Streams API

De Streams API stelt JavaScript in staat om programmatisch toegang te krijgen tot datastromen die via het netwerk worden ontvangen en deze naar wens te verwerken. Een belangrijk concept in de context van streams is backpressure . Dit is het proces waarbij een enkele stream of een pijplijnketen de lees- of schrijfsnelheid reguleert. Wanneer de stream zelf of een stream verderop in de pijplijnketen nog bezig is en nog niet klaar is om meer data te ontvangen, stuurt deze een signaal terug door de keten om de levering indien nodig te vertragen.

Het probleem met de huidige WebSocket API

Het is onmogelijk om tegendruk uit te oefenen op ontvangen berichten.

Met de huidige WebSocket API vindt de reactie op een bericht plaats in WebSocket.onmessage , een EventHandler die wordt aangeroepen wanneer een bericht van de server wordt ontvangen.

Stel dat je een applicatie hebt die zware dataverwerkingsbewerkingen moet uitvoeren telkens wanneer een nieuw bericht binnenkomt. Je zou de workflow waarschijnlijk opzetten zoals in de onderstaande code, en aangezien je await het resultaat van de process() -aanroep, zou dat geen probleem moeten zijn, toch?

// 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);
};

Fout! Het probleem met de huidige WebSocket API is dat er geen manier is om backpressure toe te passen. Wanneer berichten sneller binnenkomen dan de process() `-methode ze kan verwerken, zal het renderproces ofwel het geheugen vol laten lopen door het bufferen van die berichten, ofwel niet meer reageren door 100% CPU-gebruik, of beide.

Het uitoefenen van tegendruk op verzonden berichten is niet ergonomisch.

Het is mogelijk om backpressure toe te passen op verzonden berichten, maar dit vereist het continu controleren van de eigenschap WebSocket.bufferedAmount , wat inefficiënt en niet ergonomisch is. Deze alleen-lezen eigenschap geeft het aantal bytes aan gegevens weer dat in de wachtrij is geplaatst met behulp van aanroepen naar WebSocket.send() , maar nog niet naar het netwerk is verzonden. Deze waarde wordt gereset naar nul zodra alle gegevens in de wachtrij zijn verzonden, maar als u WebSocket.send() blijft aanroepen, zal de waarde blijven oplopen.

Wat is de WebSocketStream API?

De WebSocketStream API lost het probleem van ontbrekende of niet-ergonomische backpressure op door streams te integreren met de WebSocket API. Dit betekent dat backpressure "gratis" kan worden toegepast, zonder extra kosten.

Voorgestelde gebruiksscenario's voor de WebSocketStream API

Voorbeelden van sites die deze API kunnen gebruiken zijn:

  • WebSocket-applicaties met hoge bandbreedte die interactiviteit moeten behouden, met name video- en schermdeling.
  • Op dezelfde manier genereren video-opnames en andere applicaties in de browser veel data die naar de server moet worden geüpload. Met backpressure kan de client stoppen met het produceren van data in plaats van data in het geheugen te laten accumuleren.

Huidige status

Stap Status
1. Maak een uitleg Compleet
2. Stel een eerste concept van de specificatie op. In behandeling
3. Verzamel feedback en pas het ontwerp aan In behandeling
4. Oorsprongsproef Compleet
5. Lancering Nog niet begonnen

Hoe gebruik je de WebSocketStream API?

De WebSocketStream API is gebaseerd op promises, waardoor het gebruik ervan in de moderne JavaScript-wereld heel natuurlijk aanvoelt. Je begint met het aanmaken van een nieuwe WebSocketStream en geeft deze de URL van de WebSocket-server mee. Vervolgens wacht je tot de verbinding is opened , wat resulteert in een ReadableStream en/of een WritableStream .

Door de methode ReadableStream.getReader() aan te roepen, verkrijgt u uiteindelijk een ReadableStreamDefaultReader , waaruit u vervolgens gegevens kunt read() totdat de stream klaar is, dat wil zeggen totdat deze een object retourneert in de vorm {value: undefined, done: true} .

Door de methode WritableStream.getWriter() aan te roepen, verkrijgt u uiteindelijk een WritableStreamDefaultWriter , waarnaar u vervolgens gegevens kunt write() .

  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);
  }

Tegendruk

En hoe zit het met de beloofde backpressure-functie? Die krijg je "gratis", zonder extra stappen. Als process() extra tijd in beslag neemt, wordt het volgende bericht pas verwerkt zodra de pipeline gereed is. Evenzo wordt de stap WritableStreamDefaultWriter.write() alleen uitgevoerd als dat veilig is.

Geavanceerde voorbeelden

Het tweede argument van WebSocketStream is een verzameling opties om toekomstige uitbreidingen mogelijk te maken. De enige optie is protocols , die zich op dezelfde manier gedraagt ​​als het tweede argument van de WebSocket-constructor :

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

Het geselecteerde protocol en eventuele extensions maken deel uit van het woordenboek dat beschikbaar is via de WebSocketStream.opened -promise. Alle informatie over de actieve verbinding wordt door deze belofte verstrekt, aangezien deze niet relevant is als de verbinding mislukt.

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

Informatie over een gesloten WebSocketStream-verbinding

De informatie die beschikbaar was via de gebeurtenissen WebSocket.onclose en WebSocket.onerror in de WebSocket API is nu beschikbaar via de promise WebSocketStream.closed . Deze promise wordt afgewezen bij een onjuiste sluiting; anders wordt de code en reden die door de server zijn verzonden, geretourneerd.

Alle mogelijke statuscodes en hun betekenis worden uitgelegd in de lijst met CloseEvent -statuscodes .

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

Een WebSocketStream-verbinding sluiten

Een WebSocketStream kan worden gesloten met een AbortController . Geef daarom een AbortSignal door aan de constructor WebSocketStream . AbortController.abort() werkt alleen vóór de handshake, niet erna.

const controller = new AbortController();
const wss = new WebSocketStream(URL, {signal: controller.signal});
setTimeout(() => controller.abort(), 1000);

Als alternatief kunt u ook de WebSocketStream.close() -methode gebruiken, maar het belangrijkste doel daarvan is om de code en de reden die naar de server wordt verzonden, te specificeren.

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

Progressieve verbetering en interoperabiliteit

Chrome is momenteel de enige browser die de WebSocketStream API implementeert. Voor interoperabiliteit met de klassieke WebSocket API is het niet mogelijk om backpressure toe te passen op ontvangen berichten. Het toepassen van backpressure op verzonden berichten is wel mogelijk, maar vereist het continu controleren van de WebSocket.bufferedAmount -eigenschap, wat inefficiënt en niet ergonomisch is.

Kenmerkdetectie

Om te controleren of de WebSocketStream API wordt ondersteund, gebruikt u:

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

Demo

In browsers die dit ondersteunen, kunt u de WebSocketStream API in actie zien of de ingesloten iframe bekijken.

Feedback

Het Chrome-team wil graag meer horen over uw ervaringen met de WebSocketStream API.

Vertel ons iets over het API-ontwerp.

Werkt er iets aan de API niet zoals je had verwacht? Of ontbreken er methoden of eigenschappen die je nodig hebt om je idee te implementeren? Heb je een vraag of opmerking over het beveiligingsmodel? Dien een specificatie-issue in op de bijbehorende GitHub-repository , of voeg je gedachten toe aan een bestaand issue.

Meld een probleem met de implementatie.

Heb je een bug gevonden in de implementatie van Chrome? Of wijkt de implementatie af van de specificatie? Meld een bug op new.crbug.com . Vermeld zoveel mogelijk details, eenvoudige instructies voor het reproduceren van het probleem en voer Blink>Network>WebSockets in bij het veld Components .

Toon je steun voor de API

Ben je van plan de WebSocketStream API te gebruiken? Jouw publieke steun helpt het Chrome-team bij het prioriteren van functies en laat andere browserleveranciers zien hoe belangrijk het is om deze te ondersteunen.

Stuur een tweet naar @ChromiumDev met de hashtag #WebSocketStream en laat ons weten waar en hoe je het gebruikt.

Handige links

Dankbetuigingen

De WebSocketStream API is geïmplementeerd door Adam Rice en Yutaka Hirano .