WebSocketStream: integra transmisiones con la API de WebSocket

Aplica la contrapresión para evitar que tu app se ahogue en los mensajes de WebSocket o que sature un servidor de WebSocket de mensajes.

Información general

La API de WebSocket

La API de WebSocket Proporciona una interfaz de JavaScript para el protocolo de WebSocket. lo que permite iniciar una sesión de comunicación bidireccional interactiva. entre el navegador del usuario y un servidor. Con esta API, puedes enviar mensajes a un servidor y recibir respuestas controladas por eventos sin sondear el servidor para obtener una respuesta.

La API de Streams

La API de Streams Permite que JavaScript acceda de manera programática a flujos de fragmentos de datos recibidos a través de la red y procesarlos como desees. Un concepto importante en el contexto de las transmisiones es contrapresión. Este es el proceso mediante el cual una sola transmisión o cadena de tuberías regula la velocidad de lectura o escritura. Cuando la transmisión en sí o una transmisión posterior en la cadena de canalización sigue ocupada. y aún no está listo para aceptar más fragmentos, envía una señal hacia atrás a través de la cadena para ralentizar la entrega según corresponda.

El problema con la API de WebSocket actual

Es imposible aplicar la contrapresión a los mensajes recibidos.

Con la API de WebSocket actual, la reacción a un mensaje se produce WebSocket.onmessage: Se llama a EventHandler cuando se recibe un mensaje del servidor.

Supongamos que tienes una aplicación que necesita realizar operaciones pesadas de procesamiento de datos cada vez que se recibe un mensaje nuevo. Es probable que configurarías el flujo de manera similar al siguiente código: y, como await el resultado de la llamada a process(), deberías estar bien, ¿no?

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

Incorrecto. El problema con la API de WebSocket actual es que no hay forma de aplicar la contrapresión. Cuando los mensajes llegan más rápido de lo que el método process() puede manejarlos, el proceso de renderización llenará la memoria almacenando en búfer esos mensajes no responden debido al 100% del uso de la CPU, o a ambos.

La contrapresión en los mensajes enviados no es ergonómico

Es posible aplicar la contrapresión a los mensajes enviados, pero implica sondear WebSocket.bufferedAmount que es ineficiente y no ergonómico. Esta propiedad de solo lectura muestra la cantidad de bytes de datos que se colocaron en cola usar llamadas para WebSocket.send(): pero aún no se transmiten a la red. Este valor se restablece a cero luego de que se envían todos los datos en cola pero si sigues llamando a WebSocket.send(), seguirá subiendo.

¿Qué es la API de WebSocketStream?

La API de WebSocketStream se encarga del problema de la contrapresión inexistente o no ergonómica. a través de la integración de transmisiones con la API de WebSocket. Esto significa que la contrapresión se puede aplicar "de forma gratuita" sin ningún costo adicional.

Casos de uso sugeridos para la API de WebSocketStream

Estos son algunos ejemplos de sitios que pueden usar esta API:

  • Aplicaciones de WebSocket de alto ancho de banda que necesitan conservar la interactividad en particular el video y la pantalla compartida.
  • Del mismo modo, la captura de video y otras aplicaciones que generan una gran cantidad de datos en el navegador que debe subirse al servidor. Con la contrapresión, el cliente puede dejar de producir datos en lugar de acumularlos en la memoria.

Estado actual

Paso Estado
1. Crear explicación Completo
2. Crea el borrador inicial de la especificación En curso
3. Recopila comentarios y iterar en el diseño En curso
4. Prueba de origen Completo
5. Lanzamiento Sin iniciar

Cómo usar la API de WebSocketStream

Ejemplo introductorio

La API de WebSocketStream se basa en promesas, lo que hace que lidiar con ella se sienta natural. en un mundo moderno de JavaScript. Para comenzar, debes construir un WebSocketStream nuevo y pasarle la URL del servidor de WebSocket. Luego, espera a que la conexión sea opened, lo que da como resultado un ReadableStream y/o WritableStream

Al llamar al ReadableStream.getReader() Por último, obtienes un ReadableStreamDefaultReader, que luego podrás read() desde el momento en que se completa la transmisión, es decir, hasta que se muestra un objeto con el formato {value: undefined, done: true}

Por lo tanto, al llamar al WritableStream.getWriter() Por último, obtienes un WritableStreamDefaultWriter, que luego podrás write() a la que se envían los datos.

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

Contrapresión

¿Qué pasa con la función de contrapresión prometida? Como mencioné antes, lo obtienes "gratis", sin necesidad de realizar pasos adicionales. Si process() lleva más tiempo, el siguiente mensaje solo se consumirá una vez que la canalización esté lista. Del mismo modo, el paso WritableStreamDefaultWriter.write() solo procederá si es seguro hacerlo.

Ejemplos avanzados

El segundo argumento de WebSocketStream es una bolsa de opciones para permitir extensiones futuras. Actualmente, la única opción es protocols, que se comporta igual que segundo argumento para el constructor de WebSocket:

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

El protocol seleccionado y los posibles extensions forman parte del diccionario disponible a través de la promesa WebSocketStream.opened. Esta promesa proporciona toda la información sobre la conexión en vivo ya que no es relevante si la conexión falla.

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

Información sobre la conexión cerrada de WebSocketStream

La información que estaba disponible en el WebSocket.onclose y WebSocket.onerror eventos en la API de WebSocket ahora está disponible a través de la promesa WebSocketStream.closed. La promesa se rechaza en caso de un cierre sucio de lo contrario, se resuelve en el código y el motivo que envió el servidor.

Todos los códigos de estado posibles y su significado se explican en el lista de CloseEvent códigos de estado.

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

Cómo cerrar una conexión de WebSocketStream

Un WebSocketStream se puede cerrar con un AbortController Por lo tanto, pasa un objeto AbortSignal. al constructor WebSocketStream.

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

Como alternativa, también puedes usar el método WebSocketStream.close(), pero su propósito principal es permitir que se especifiquen los código y el motivo que se envía al servidor.

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

Interoperabilidad y mejora progresiva

En este momento, Chrome es el único navegador que implementa la API de WebSocketStream. Para la interoperabilidad con la API clásica de WebSocket, no es posible aplicar la contrapresión a los mensajes recibidos. Es posible aplicar la contrapresión a los mensajes enviados, pero implica sondear WebSocket.bufferedAmount que es ineficiente y no ergonómico.

Detección de funciones

Para verificar si la API de WebSocketStream es compatible, usa lo siguiente:

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

Demostración

En los navegadores compatibles, puedes ver la API de WebSocketStream en acción en el iframe incorporado, o directamente en Glitch.

Comentarios

El equipo de Chrome quiere conocer tus experiencias con la API de WebSocketStream.

Cuéntanos sobre el diseño de la API

¿Algo en la API no funciona como esperabas? ¿O faltan métodos o propiedades que necesites para implementar tu idea? ¿Tienes alguna pregunta o comentario sobre el modelo de seguridad? Informa un problema de especificaciones en el repositorio de GitHub correspondiente. o agrega lo que piensas a un problema existente.

Informar un problema con la implementación

¿Encontraste un error en la implementación de Chrome? ¿O la implementación es diferente de la especificación? Informa un error en new.crbug.com. Asegúrate de incluir tantos detalles como puedas, instrucciones sencillas para la reproducción, y, luego, ingresa Blink>Network>WebSockets en el cuadro Componentes. Glitch funciona muy bien para compartir casos de reproducción de forma rápida y sencilla.

Demuestra compatibilidad con la API

¿Planeas usar la API de WebSocketStream? Tu asistencia pública ayuda al equipo de Chrome a priorizar funciones y muestra a otros proveedores de navegadores la importancia de brindar asistencia.

Envía un tweet a @ChromiumDev con el hashtag #WebSocketStream y cuéntanos dónde y cómo la utilizas.

Vínculos útiles

Agradecimientos

Adam Rice implementó la API de WebSocketStream y Yutaka Hirano. Hero image de Daan Mooij en Retiro: