WebSocketStream: streams integreren met de WebSocket API

Voorkom dat uw app verdrinkt in WebSocket-berichten of een WebSocket-server overspoelt met berichten door tegendruk toe te passen.

Achtergrond

De WebSocket-API

De WebSocket API biedt een JavaScript-interface voor het WebSocket-protocol , waardoor het mogelijk wordt een interactieve tweerichtingscommunicatiesessie te openen tussen de browser van de gebruiker en een server. Met deze API kunt u berichten naar een server sturen en gebeurtenisgestuurde reacties ontvangen zonder de server om een ​​antwoord te vragen.

De Streams-API

Met de Streams API kan JavaScript programmatisch toegang krijgen tot gegevensstromen die via het netwerk worden ontvangen en deze naar wens verwerken. Een belangrijk concept in de context van stromen is tegendruk . Dit is het proces waarbij een enkele stroom of een pijpketen de snelheid van lezen of schrijven regelt. Wanneer de stroom zelf of een stroom later in de pijpketen nog steeds bezig is en nog niet klaar is om meer stukken te accepteren, stuurt hij een signaal achteruit door de keten om de levering indien nodig te vertragen.

Het probleem met de huidige WebSocket API

Het toepassen van tegendruk op ontvangen berichten is onmogelijk

Met de huidige WebSocket API gebeurt het reageren op een bericht in WebSocket.onmessage , een EventHandler die wordt aangeroepen wanneer een bericht wordt ontvangen van de server.

Laten we aannemen dat u een toepassing heeft die zware gegevensverwerkingsbewerkingen moet uitvoeren wanneer er een nieuw bericht wordt ontvangen. Je zou de stroom waarschijnlijk instellen zoals in de onderstaande code, en aangezien je await het resultaat van de process() aanroep, zou het goed 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 tegendruk toe te passen. Wanneer berichten sneller aankomen dan de methode process() ze aankan, zal het weergaveproces het geheugen opvullen door die berichten te bufferen, niet meer reageren vanwege 100% CPU-gebruik, of beide.

Het toepassen van tegendruk op verzonden berichten is niet ergonomisch

Het toepassen van tegendruk op verzonden berichten is mogelijk, maar hiervoor moet de eigenschap WebSocket.bufferedAmount worden gecontroleerd, wat inefficiënt en niet-ergonomisch is. Deze alleen-lezen eigenschap retourneert het aantal bytes aan gegevens die in de wachtrij zijn geplaatst met behulp van aanroepen van WebSocket.send() , maar nog niet naar het netwerk zijn verzonden. Deze waarde wordt opnieuw ingesteld op nul zodra alle gegevens in de wachtrij zijn verzonden, maar als u WebSocket.send() blijft aanroepen, blijft deze stijgen.

Wat is de WebSocketStream-API?

De WebSocketStream API pakt het probleem van niet-bestaande of niet-ergonomische tegendruk aan door streams te integreren met de WebSocket API. Dit betekent dat tegendruk “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 scherm delen.
  • Op dezelfde manier kunnen video-opname en andere toepassingen die veel gegevens in de browser genereren die naar de server moeten worden geüpload. Met tegendruk kan de client stoppen met het produceren van gegevens in plaats van gegevens in het geheugen te verzamelen.

Huidige status

Stap Toestand
1. Maak een uitleg Compleet
2. Maak een eerste ontwerp van specificatie Bezig
3. Verzamel feedback en herhaal het ontwerp Bezig
4. Oorsprongsproces Compleet
5. Lancering Niet begonnen

Hoe u de WebSocketStream-API gebruikt

Inleidend voorbeeld

De WebSocketStream API is gebaseerd op beloftes, waardoor het omgaan ermee natuurlijk aanvoelt in een moderne JavaScript-wereld. U begint met het construeren van een nieuwe WebSocketStream en geeft deze de URL van de WebSocket-server door. Vervolgens wacht je tot de verbinding wordt 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 is voltooid, 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 dus 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

Hoe zit het met de beloofde tegendrukfunctie? Zoals ik hierboven schreef, krijg je het "gratis", er zijn geen extra stappen nodig. Als process() extra tijd in beslag neemt, wordt het volgende bericht pas verbruikt zodra de pijplijn gereed is. Op dezelfde manier zal de stap WritableStreamDefaultWriter.write() alleen doorgaan als het veilig is om dit te doen.

Geavanceerde voorbeelden

Het tweede argument voor WebSocketStream is een optiepakket om toekomstige uitbreiding mogelijk te maken. Momenteel is de enige optie protocols , die zich hetzelfde gedraagt ​​als het tweede argument voor de WebSocket-constructor :

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

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

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

Informatie over 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 belofte WebSocketStream.closed . De belofte wordt afgewezen in het geval van een onreine afsluiting, anders wordt besloten tot de code en de reden die door de server zijn verzonden.

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 afgesloten met een AbortController . Geef daarom een AbortSignal door aan de WebSocketStream constructor.

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

Als alternatief kunt u ook de methode WebSocketStream.close() gebruiken, maar het voornaamste doel ervan is het mogelijk te maken de code en de reden te specificeren die naar de server wordt verzonden.

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

Geleidelijke 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 tegendruk toe te passen op ontvangen berichten. Het toepassen van tegendruk op verzonden berichten is mogelijk, maar hiervoor moet de eigenschap WebSocket.bufferedAmount worden gecontroleerd, wat inefficiënt en niet-ergonomisch is.

Functiedetectie

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

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

Demo

In ondersteunende browsers kunt u de WebSocketStream API in actie zien in het ingebedde iframe, of rechtstreeks op Glitch .

Feedback

Het Chrome-team wil graag horen wat uw ervaringen zijn met de WebSocketStream API.

Vertel ons over het API-ontwerp

Is er iets aan de API dat niet werkt zoals je had verwacht? Of ontbreken er methoden of eigenschappen die je nodig hebt om je idee te implementeren? Heeft u een vraag of opmerking over het beveiligingsmodel? Dien een spec issue in op de corresponderende GitHub repository , of voeg uw gedachten toe aan een bestaand issue.

Meld een probleem met de implementatie

Heeft u een bug gevonden in de implementatie van Chrome? Of wijkt de uitvoering af van de specificaties? Dien een bug in op new.crbug.com . Zorg ervoor dat u zoveel mogelijk details en eenvoudige instructies voor het reproduceren opneemt, en typ Blink>Network>WebSockets in het vak Componenten . Glitch werkt prima voor het delen van snelle en gemakkelijke reproductiecasussen.

Toon ondersteuning voor de API

Bent u van plan de WebSocketStream API te gebruiken? Uw publieke steun helpt het Chrome-team prioriteiten te stellen voor 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 u deze gebruikt.

Handige Links

Dankbetuigingen

De WebSocketStream API is geïmplementeerd door Adam Rice en Yutaka Hirano . Hero-afbeelding door Daan Mooij op Unsplash .