Cómo usar WebTransport

Fecha de publicación: 8 de junio de 2020

WebTransport es una API web que usa el protocolo HTTP/3 como transporte bidireccional. Está diseñada para comunicaciones bidireccionales entre un cliente web y un servidor HTTP/3. Admite el envío de datos de forma no confiable con sus APIs de datagramas y de forma confiable con sus APIs de transmisión.

Los datagramas son ideales para enviar y recibir datos que no necesitan garantías de entrega sólidas. Los paquetes de datos individuales están limitados en tamaño por la unidad de transmisión máxima (MTU) de la conexión subyacente y pueden transmitirse o no de forma correcta. Si se transfieren, pueden llegar en un orden arbitrario. Estas características hacen que las APIs de datagramas sean ideales para la transmisión de datos de baja latencia y de mejor esfuerzo. Puedes pensar en los datagramas como mensajes del protocolo de datagramas de usuario (UDP), pero encriptados y con control de congestión.

Por el contrario, las APIs de transmisión proporcionan una transferencia de datos confiable y ordenada. Son adecuadas para situaciones en las que necesitas enviar o recibir una o más transmisiones de datos ordenados. El uso de varias transmisiones de WebTransport es análogo al establecimiento de varias conexiones TCP, pero, como HTTP/3 usa el protocolo QUIC más liviano en segundo plano, se pueden abrir y cerrar sin tanta sobrecarga.

Casos de uso

Esta es una pequeña lista de las posibles formas en que los desarrolladores podrían usar WebTransport.

  • Enviar el estado del juego a un intervalo regular con una latencia mínima a un servidor en mensajes pequeños, no confiables y desordenados
  • Recibir transmisiones de contenido multimedia enviadas desde un servidor con una latencia mínima, independientemente de otras transmisiones de datos
  • Recibir notificaciones enviadas desde un servidor mientras una página web está abierta

Nos interesa saber más sobre cómo planeas usar WebTransport.

Navegadores compatibles

Browser Support

  • Chrome: 97.
  • Edge: 97.
  • Firefox: 114.
  • Safari: 26.4.

Source

Al igual que con todas las funciones que no tienen compatibilidad universal con el navegador, te recomendamos que agregues la detección de funciones.

Relación con otras tecnologías

¿WebTransport reemplaza a WebSockets?

Tal vez. Existen casos de uso en los que WebSockets o WebTransport podrían ser protocolos de comunicación válidos para usar.

Las comunicaciones de WebSockets se modelan en torno a una sola transmisión de mensajes confiable y ordenada, lo que es adecuado para algunos tipos de necesidades de comunicación. Si necesitas esas características, las APIs de transmisión de WebTransport también pueden proporcionarlas. En comparación, las APIs de datagramas de WebTransport proporcionan una entrega de baja latencia, sin garantías sobre la confiabilidad o el orden, por lo que no son un reemplazo directo de WebSockets.

Cuando usas WebTransport, con las APIs de datagramas o varias instancias simultáneas de la API de Streams, no tienes que preocuparte por el bloqueo de encabezado de línea, que puede ser un problema con WebSockets. Además, hay beneficios de rendimiento cuando se establecen conexiones nuevas, ya que el protocolo de enlace QUIC subyacente es más rápido que iniciar TCP a través de TLS.

WebTransport forma parte de una nueva especificación de borrador y, como tal, el ecosistema de WebSocket en torno a las bibliotecas de cliente y servidor es mucho más sólido. Si necesitas algo que funcione "listo para usar" con configuraciones de servidor comunes y con una amplia compatibilidad con clientes web, WebSockets es una mejor opción hoy en día.

¿WebTransport es lo mismo que una API de socket UDP?

No. WebTransport no es una API de socket UDP. Si bien WebTransport usa HTTP/3, que a su vez usa UDP "en segundo plano", WebTransport tiene requisitos en torno a la encriptación y el control de congestión que lo hacen más que una API de socket UDP básica.

¿WebTransport es una alternativa a los canales de datos de WebRTC?

Sí, para las conexiones cliente-servidor. WebTransport comparte muchas de las mismas propiedades que los canales de datos de WebRTC, aunque los protocolos subyacentes son diferentes.

En general, ejecutar un servidor compatible con HTTP/3 requiere menos configuración que mantener un servidor WebRTC, lo que implica comprender varios protocolos (ICE, DTLS, y SCTP) para obtener un transporte en funcionamiento. WebRTC implica muchas más partes móviles que podrían generar negociaciones fallidas entre el cliente y el servidor.

La API de WebTransport se diseñó teniendo en cuenta los casos de uso de los desarrolladores web y debería parecerse más a escribir código moderno de plataforma web que a usar las interfaces de canal de datos de WebRTC. A diferencia de WebRTC, WebTransport es compatible con Web Workers, lo que te permite realizar comunicaciones cliente-servidor independientes de una página HTML determinada. Como WebTransport expone una interfaz compatible con Streams, admite optimizaciones en torno a la contrapresión.

Sin embargo, si ya tienes una configuración cliente-servidor de WebRTC en funcionamiento con la que estás satisfecho, cambiar a WebTransport podría no ofrecer muchas ventajas.

Experimento

La mejor manera de experimentar con WebTransport es iniciar un servidor HTTP/3 compatible. Usa esta página con un cliente básico de JavaScript para probar las comunicaciones cliente-servidor.

Además, hay un servidor de eco mantenido por la comunidad disponible en webtransport.day.

Cómo usar la API

WebTransport se diseñó sobre primitivas modernas de la plataforma web, como la API de Streams. Se basa en gran medida en las promesas y funciona bien con async y await.

La implementación actual de WebTransport en Chromium admite tres tipos distintos de tráfico: datagramas, así como transmisiones unidireccionales y bidireccionales.

Conéctate a un servidor

Puedes conectarte a un servidor HTTP/3 creando una instancia de WebTransport. El esquema de la URL debe ser https. Debes especificar explícitamente el número de puerto.

Debes usar la promesa ready para esperar a que se establezca la conexión. Esta promesa permanece sin cumplir hasta que se completa la configuración y se rechaza si la conexión falla en la etapa QUIC/TLS.

La promesa closed se cumple cuando la conexión se cierra normalmente y se rechaza si el cierre fue inesperado.

Si el servidor rechaza la conexión debido a un error de indicación del cliente (por ejemplo, si la ruta de acceso de la URL no es válida), se rechaza closed mientras que ready permanece sin resolver.

const url = 'https://example.com:4999/foo/bar';
const transport = new WebTransport(url);

// Optionally, set up functions to respond to
// the connection closing:
transport.closed.then(() => {
  console.log(`The HTTP/3 connection to ${url} closed gracefully.`);
}).catch((error) => {
  console.error(`The HTTP/3 connection to ${url} closed due to ${error}.`);
});

// Once .ready fulfills, the connection can be used.
await transport.ready;

APIs de datagramas

Una vez que tengas una instancia de WebTransport conectada a un servidor, podrás usarla para enviar y recibir bits de datos discretos, conocidos como datagramas.

El método get writeable muestra un WritableStream, que un cliente web puede usar para enviar datos al servidor. El método get readable muestra un ReadableStream, lo que te permite escuchar datos del servidor. Ambas transmisiones son inherentemente no confiables, por lo que es posible que el servidor no reciba los datos que escribes y viceversa.

Ambos tipos de transmisiones usan Uint8Array instancias para la transferencia de datos.

// Send two datagrams to the server.
const writer = transport.datagrams.writable.getWriter();
const data1 = new Uint8Array([65, 66, 67]);
const data2 = new Uint8Array([68, 69, 70]);
writer.write(data1);
writer.write(data2);

// Read datagrams from the server.
const reader = transport.datagrams.readable.getReader();
while (true) {
  const {value, done} = await reader.read();
  if (done) {
    break;
  }
  // value is a Uint8Array.
  console.log(value);
}

APIs de transmisión

Una vez que te conectes al servidor, también podrás usar WebTransport para enviar y recibir datos a través de sus APIs de Streams.

Cada fragmento de todas las transmisiones es un Uint8Array. A diferencia de las APIs de datagramas, estas transmisiones son confiables. Sin embargo, cada transmisión es independiente, por lo que no se garantiza el orden de los datos en las transmisiones.

WebTransportSendStream

El cliente web crea un WebTransportSendStream con el método createUnidirectionalStream() de una instancia de WebTransport, que muestra una promesa para el WebTransportSendStream.

Usa el close() método de la WritableStreamDefaultWriter para cerrar la transmisión HTTP/3 asociada. El navegador intenta enviar todos los datos pendientes antes de cerrar la transmisión asociada.

// Send two Uint8Arrays to the server.
const stream = await transport.createUnidirectionalStream();
const writer = stream.writable.getWriter();
const data1 = new Uint8Array([65, 66, 67]);
const data2 = new Uint8Array([68, 69, 70]);
writer.write(data1);
writer.write(data2);
try {
  await writer.close();
  console.log('All data has been sent.');
} catch (error) {
  console.error(`An error occurred: ${error}`);
}

Del mismo modo, usa el abort() método de WritableStreamDefaultWriter para enviar un RESET_STREAM al servidor. Cuando se usa abort(), el navegador puede descartar cualquier dato pendiente que aún no se haya enviado.

const ws = await transport.createUnidirectionalStream();
const writer = ws.getWriter();
writer.write(...);
writer.write(...);
await writer.abort();
// Not all the data may have been written.

WebTransportReceiveStream

El servidor inicia WebTransportReceiveStream. Obtener un WebTransportReceiveStream es un proceso de dos pasos para un cliente web. Primero, el cliente llama al atributo incomingUnidirectionalStreams de una instancia de WebTransport, que muestra un ReadableStream. Cada fragmento de ese ReadableStream es, a su vez, un WebTransportReceiveStream que se puede usar para leer instancias de Uint8Array enviadas por el servidor.

async function readFrom(receiveStream) {
  const reader = receiveStream.readable.getReader();
  while (true) {
    const {done, value} = await reader.read();
    if (done) {
      break;
    }
    // value is a Uint8Array
    console.log(value);
  }
}

const rs = transport.incomingUnidirectionalStreams;
const reader = rs.getReader();
while (true) {
  const {done, value} = await reader.read();
  if (done) {
    break;
  }
  // value is an instance of WebTransportReceiveStream
  await readFrom(value);
}

Puedes detectar el cierre de la transmisión con la closed promesa de la ReadableStreamDefaultReader. Cuando la transmisión HTTP/3 subyacente se cierra con el bit FIN, la closed promesa se cumple después de que se leen todos los datos. Cuando la transmisión HTTP/3 se cierra de forma abrupta (por ejemplo, con RESET_STREAM), se rechaza la promesa closed.

// Assume an active receiveStream
const reader = receiveStream.readable.getReader();
reader.closed.then(() => {
  console.log('The receiveStream closed gracefully.');
}).catch(() => {
  console.error('The receiveStream closed abruptly.');
});

WebTransportBidirectionalStream

El servidor o el cliente pueden crear un WebTransportBidirectionalStream.

Los clientes web pueden crear uno con el createBidirectionalStream() método de una WebTransport instancia, que muestra una promesa para una WebTransportBidirectionalStream.

const stream = await transport.createBidirectionalStream();
// stream is a WebTransportBidirectionalStream
// stream.readable is a ReadableStream
// stream.writable is a WritableStream

Puedes escuchar un WebTransportBidirectionalStream creado por el servidor con el atributo incomingBidirectionalStreams de una instancia de WebTransport, que muestra un ReadableStream. Cada fragmento de ese ReadableStream es, a su vez, un WebTransportBidirectionalStream.

const rs = transport.incomingBidirectionalStreams;
const reader = rs.getReader();
while (true) {
  const {done, value} = await reader.read();
  if (done) {
    break;
  }
  // value is a WebTransportBidirectionalStream
  // value.readable is a ReadableStream
  // value.writable is a WritableStream
}

Un WebTransportBidirectionalStream es solo una combinación de un WebTransportSendStream y un WebTransportReceiveStream. En los ejemplos de las dos secciones anteriores, se explica cómo usar cada uno de ellos.

Polyfill

Hay un polyfill (o más bien ponyfill que proporciona funcionalidad como un módulo independiente que puedes usar) llamado webtransport-ponyfill-websocket que implementa algunas de las funciones de WebTransport. Lee atentamente las restricciones en el README del proyecto para determinar si esta solución puede funcionar para tu caso de uso.

Consideraciones de privacidad y seguridad

Consulta la sección correspondiente de la especificación de borrador para obtener instrucciones autorizadas.

Comentarios

¿Hay algo en la API que sea incómodo o que no funcione como se espera? ¿O faltan partes que necesitas para implementar tu idea?

Tu apoyo público ayuda a Chrome a priorizar funciones y muestra a otros proveedores de navegadores lo fundamental que es admitirlas.

Análisis general

Puedes usar el Grupo de Google web-transport-dev para preguntas o problemas generales que no se ajusten a ninguna de las otras categorías.

Agradecimientos

Incorporamos información del WebTransport Explainer y la especificación de borrador. Gracias a los respectivos autores por proporcionar esa base.