Aplica la contrapresión para evitar que tu app se ahogue en mensajes de WebSocket o inunde un servidor de WebSocket con mensajes.
Información general
La API de WebSocket proporciona una interfaz de JavaScript al protocolo WebSocket, lo que permite abrir una sesión de comunicación interactiva de dos vías entre el navegador del usuario y un servidor. Con esta API, puedes enviar mensajes a un servidor y recibir respuestas orientadas a eventos sin sondear el servidor para obtener una respuesta.
La API de Streams
La API de Streams permite que JavaScript acceda de forma programática a flujos de fragmentos de datos recibidos a través de la red y los procese como desee. Un concepto importante en el contexto de las transmisiones es la presión de contraflujo. Este es el proceso mediante el cual una sola transmisión o una cadena de canal regula la velocidad de lectura o escritura. Cuando la transmisión en sí o una transmisión posterior en la cadena de canalizaciones sigue ocupada y aún no está lista 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 en WebSocket.onmessage
, un EventHandler
al que se llama cuando se recibe un mensaje del servidor.
Supongamos que tienes una aplicación que necesita realizar operaciones de procesamiento de datos pesadas cada vez que se recibe un mensaje nuevo.
Es probable que configures 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 con el almacenamiento en búfer de esos mensajes, no responderá debido al uso del 100% de la CPU o ambas.
Aplicar la contrapresión a los mensajes enviados no es ergonómico.
Es posible aplicar la contrapresión a los mensajes enviados, pero implica sondear la propiedad WebSocket.bufferedAmount
, lo que es ineficiente y no ergonómico.
Esta propiedad de solo lectura muestra la cantidad de bytes de datos que se pusieron en cola con llamadas a WebSocket.send()
, pero que aún no se transmitieron a la red.
Este valor se restablece a cero una vez que se envían todos los datos en fila, pero si sigues llamando a WebSocket.send()
, seguirá aumentando.
¿Qué es la API de WebSocketStream?
La API de WebSocketStream aborda el problema de la contrapresión inexistente o no ergonómica mediante la integración de transmisiones con la API de WebSocket. Esto significa que la contrapresión se puede aplicar “sin costo”, 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 WebSocket de gran ancho de banda que necesitan retener la interactividad, en particular, el video y el uso compartido de pantalla.
- Del mismo modo, la captura de video y otras aplicaciones que generan muchos datos en el navegador que se deben subir 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. Crea un video explicativo | Completar |
2. Crea un borrador inicial de la especificación | En curso |
3. Recopila comentarios y itera en el diseño | En curso |
4. Prueba de origen | Completar |
5. Lanzamiento | Sin iniciar |
Cómo usar la API de WebSocketStream
La API de WebSocketStream se basa en promesas, lo que hace que trabajar con ella sea algo natural en un mundo moderno de JavaScript.
Para comenzar, debes crear un WebSocketStream
nuevo y pasarle la URL del servidor WebSocket.
A continuación, esperas a que la conexión sea opened
, lo que genera un ReadableStream
o un WritableStream
.
Cuando llamas al método ReadableStream.getReader()
, finalmente obtienes un ReadableStreamDefaultReader
, del que puedes read()
datos hasta que se complete la transmisión, es decir, hasta que muestre un objeto del formulario {value: undefined, done: true}
.
Por lo tanto, cuando llamas al método WritableStream.getWriter()
, finalmente obtienes un WritableStreamDefaultWriter
, al que puedes write()
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é sucede con la función de contrapresión prometida?
La obtienes “gratis”, sin pasos adicionales.
Si process()
tarda más tiempo, el siguiente mensaje solo se consume una vez que la canalización está lista.
Del mismo modo, el paso WritableStreamDefaultWriter.write()
solo se realiza si es seguro hacerlo.
Ejemplos avanzados
El segundo argumento de WebSocketStream es un paquete de opciones para permitir una extensión futura.
La única opción es protocols
, que se comporta de la misma manera que el segundo argumento del constructor de WebSocket:
const chatWSS = new WebSocketStream(CHAT_URL, {protocols: ['chat', 'chatv2']});
const {protocol} = await chatWSS.opened;
El protocol
seleccionado, así como el extensions
potencial, forman parte del diccionario disponible a través de la promesa WebSocketStream.opened
.
Esta promesa proporciona toda la información sobre la conexión activa, 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 los eventos WebSocket.onclose
y WebSocket.onerror
de la API de WebSocket ahora está disponible a través de la promesa WebSocketStream.closed
.
La promesa se rechaza en caso de un cierre incorrecto. 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 la
lista de códigos de estado de CloseEvent
.
const {code, reason} = await chatWSS.closed;
Cómo cerrar una conexión de WebSocketStream
Se puede cerrar un WebSocketStream con un AbortController
.
Por lo tanto, pasa un 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 especificar el
código
y el motivo que se envía al servidor.
wss.close({code: 4000, reason: 'Game over'});
Mejora progresiva e interoperabilidad
Actualmente, Chrome es el único navegador que implementa la API de WebSocketStream.
Para la interoperabilidad con la API de WebSocket clásica,
no es posible aplicar la contrapresión a los mensajes recibidos.
Es posible aplicar la contrapresión a los mensajes enviados, pero implica sondear la propiedad WebSocket.bufferedAmount
, lo que es ineficiente y no ergonómico.
Detección de atributos
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
¿Hay algo en la API que no funciona como esperabas? ¿O faltan métodos o propiedades que necesitas para implementar tu idea? ¿Tienes alguna pregunta o comentario sobre el modelo de seguridad? Informa un problema de especificación en el repositorio de GitHub correspondiente o agrega tus comentarios a un problema existente.
Denuncia 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 sea posible, instrucciones simples para reproducirlo
y, luego, ingresa Blink>Network>WebSockets
en el cuadro Componentes.
Glitch es ideal para compartir casos de reproducción rápidos y fáciles.
Cómo mostrar compatibilidad con la API
¿Piensas usar la API de WebSocketStream? Tu apoyo público ayuda al equipo de Chrome a priorizar las funciones y les muestra a otros proveedores de navegadores lo importante que es admitirlas.
Envía un tweet a @ChromiumDev con el hashtag #WebSocketStream
y cuéntanos dónde y cómo lo usas.
Vínculos útiles
- Explicación pública
- Demostración de la API de WebSocketStream | Fuente de la demostración de la API de WebSocketStream
- Error de seguimiento
- Entrada de ChromeStatus.com
- Componente Blink:
Blink>Network>WebSockets
Agradecimientos
Adam Rice y Yutaka Hirano implementaron la API de WebSocketStream.