Como usar o WebTransport

Publicado em: 8 de junho de 2020

O WebTransport é uma API da Web que usa o protocolo HTTP/3 como um transporte bidirecional. Ele é destinado a comunicações bidirecionais entre um cliente da Web e um servidor HTTP/3. Ela oferece suporte ao envio de dados de forma não confiável com as APIs de datagrama e de forma confiável com as APIs de streams.

Os datagramas são ideais para enviar e receber dados que não precisam de garantias de entrega fortes. O tamanho dos pacotes individuais de dados é limitado pela unidade máxima de transmissão (MTU) da conexão subjacente. Eles podem ou não ser transmitidos com sucesso e, se forem transferidos, podem chegar em uma ordem arbitrária. Essas características tornam as APIs de datagrama ideais para transmissão de dados com baixa latência e melhor esforço. Pense nos datagramas como mensagens do protocolo de datagramas do usuário (UDP), mas criptografadas e com controle de congestionamento.

Já as APIs de streams oferecem transferência de dados confiável e ordenada. Eles são adequados para cenários em que você precisa enviar ou receber um ou mais fluxos de dados ordenados. Usar vários fluxos WebTransport é análogo a estabelecer várias conexões TCP, mas como o HTTP/3 usa o protocolo QUIC mais leve por baixo dos panos, eles podem ser abertos e fechados sem tanta sobrecarga.

Casos de uso

Esta é uma pequena lista de possíveis maneiras de os desenvolvedores usarem o WebTransport.

  • Enviar o estado do jogo em um intervalo regular com latência mínima para um servidor em uma pequena quantidade de mensagens não confiáveis e fora de ordem.
  • Receber fluxos de mídia enviados por push de um servidor com latência mínima, independente de outros fluxos de dados.
  • Receber notificações enviadas de um servidor enquanto uma página da Web está aberta.

Queremos saber mais sobre como você planeja usar o WebTransport.

Suporte ao navegador

Browser Support

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

Source

Como acontece com todos os recursos que não têm suporte universal do navegador, recomendamos adicionar detecção de recursos.

Relação com outras tecnologias

O WebTransport é um substituto para WebSockets?

Talvez. Há casos de uso em que WebSockets ou WebTransport podem ser protocolos de comunicação válidos.

As comunicações do WebSockets são modeladas em torno de um único fluxo confiável e ordenado de mensagens, o que é adequado para alguns tipos de necessidades de comunicação. Se você precisar dessas características, as APIs de streams do WebTransport também podem fornecê-las. Em comparação, as APIs de datagrama do WebTransport oferecem entrega de baixa latência, sem garantias de confiabilidade ou ordenação. Portanto, elas não são uma substituição direta para WebSockets.

Ao usar o WebTransport com as APIs de datagrama ou várias instâncias simultâneas da API Streams, você não precisa se preocupar com o bloqueio de início de linha, que pode ser um problema com WebSockets. Além disso, há benefícios de desempenho ao estabelecer novas conexões, já que o handshake QUIC subjacente é mais rápido do que iniciar o TCP sobre TLS.

O WebTransport faz parte de uma nova especificação de rascunho. Por isso, o ecossistema WebSocket em torno das bibliotecas de cliente e servidor é muito mais robusto. Se você precisa de algo que funcione "pronto para uso" com configurações comuns de servidor e com amplo suporte a clientes da Web, o WebSockets é uma opção melhor hoje.

O WebTransport é igual a uma API de soquete UDP?

Não. O WebTransport não é uma API de soquete UDP. Embora o WebTransport use HTTP/3, que por sua vez usa UDP "por baixo dos panos", ele tem requisitos de criptografia e controle de congestionamento que o tornam mais do que uma API de soquete UDP básica.

O WebTransport é uma alternativa aos canais de dados do WebRTC?

Sim, para conexões cliente-servidor. O WebTransport compartilha muitas das mesmas propriedades dos canais de dados do WebRTC, embora os protocolos subjacentes sejam diferentes.

Em geral, executar um servidor compatível com HTTP/3 exige menos configuração do que manter um servidor WebRTC, que envolve entender vários protocolos (ICE, DTLS e SCTP) para ter um transporte funcional. O WebRTC envolve muito mais partes móveis que podem levar a negociações com falha entre cliente e servidor.

A API WebTransport foi projetada pensando nos casos de uso de desenvolvedores da Web e deve parecer mais uma escrita de código moderno da plataforma da Web do que o uso das interfaces de canal de dados do WebRTC. Ao contrário do WebRTC, o WebTransport é compatível com Web Workers, o que permite realizar comunicações cliente-servidor independentemente de uma determinada página HTML. Como o WebTransport expõe uma interface compatível com Streams, ele oferece suporte a otimizações relacionadas à contrapressão.

No entanto, se você já tiver uma configuração de cliente/servidor WebRTC funcional e estiver satisfeito com ela, a mudança para o WebTransport talvez não ofereça muitas vantagens.

Experimento

A melhor maneira de testar o WebTransport é iniciar um servidor HTTP/3 compatível. Use esta página com um cliente JavaScript básico para testar as comunicações entre cliente e servidor.

Além disso, um servidor de eco mantido pela comunidade está disponível em webtransport.day.

Usar a API

A WebTransport foi projetada com base em primitivos modernos da plataforma Web, como a API Streams. Ele depende muito de promessas e funciona bem com async e await.

A implementação atual do WebTransport no Chromium oferece suporte a três tipos distintos de tráfego: datagramas e fluxos unidirecionais e bidirecionais.

Conectar-se a um servidor

É possível se conectar a um servidor HTTP/3 criando uma instância WebTransport. O esquema do URL precisa ser https. É necessário especificar explicitamente o número da porta.

Use a promessa ready para aguardar o estabelecimento da conexão. Essa promessa permanece não atendida até que a configuração seja concluída e é rejeitada se a conexão falhar na etapa QUIC/TLS.

A promessa closed é cumprida quando a conexão é fechada normalmente e rejeitada se o fechamento for inesperado.

Se o servidor rejeitar a conexão devido a um erro de indicação do cliente (como o caminho do URL é inválido), isso fará com que closed seja rejeitado, enquanto ready permanecerá sem resolução.

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 datagrama

Depois de ter uma instância do WebTransport conectada a um servidor, você pode usá-la para enviar e receber bits discretos de dados, conhecidos como datagramas.

O getter writeable retorna um WritableStream, que um cliente da Web pode usar para enviar dados ao servidor. O getter readable retorna um ReadableStream, permitindo que você detecte dados do servidor. Como os dois fluxos são inerentemente não confiáveis, é possível que os dados gravados não sejam recebidos pelo servidor e vice-versa.

Os dois tipos de fluxos usam instâncias Uint8Array para transferência de dados.

// 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 Streams

Depois de se conectar ao servidor, você também pode usar o WebTransport para enviar e receber dados pelas APIs Streams.

Cada parte de todos os streams é um Uint8Array. Ao contrário das APIs de datagrama, esses fluxos são confiáveis. Mas cada stream é independente, então a ordem dos dados entre elas não é garantida.

WebTransportSendStream

Um WebTransportSendStream é criado pelo cliente da Web usando o método createUnidirectionalStream() de uma instância WebTransport, que retorna uma promessa para o WebTransportSendStream.

Use o método close() do WritableStreamDefaultWriter para fechar o fluxo HTTP/3 associado. O navegador tenta enviar todos os dados pendentes antes de fechar o stream associado.

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

Da mesma forma, use o método abort() da WritableStreamDefaultWriter para enviar um RESET_STREAM ao servidor. Ao usar abort(), o navegador pode descartar dados pendentes que ainda não foram enviados.

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

O servidor inicia WebTransportReceiveStream. A obtenção de um WebTransportReceiveStream é um processo de duas etapas para um cliente da Web. Primeiro, o cliente chama o atributo incomingUnidirectionalStreams de uma instância WebTransport, que retorna um ReadableStream. Cada parte desse ReadableStream é, por sua vez, um WebTransportReceiveStream que pode ser usado para ler instâncias Uint8Array enviadas pelo 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);
}

É possível detectar o fechamento do stream usando a promessa closed do ReadableStreamDefaultReader. Quando o fluxo HTTP/3 subjacente é fechado com o bit FIN, a promessa closed é cumprida depois que todos os dados são lidos. Quando o fluxo HTTP/3 é fechado abruptamente (por exemplo, por RESET_STREAM), a promessa closed é rejeitada.

// 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

Um WebTransportBidirectionalStream pode ser criado pelo servidor ou pelo cliente.

Os clientes da Web podem criar um usando o método createBidirectionalStream() de uma instância WebTransport, que retorna uma promessa para um WebTransportBidirectionalStream.

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

É possível detectar um WebTransportBidirectionalStream criado pelo servidor com o atributo incomingBidirectionalStreams de uma instância WebTransport, que retorna um ReadableStream. Cada parte desse ReadableStream é, por sua vez, um 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
}

Um WebTransportBidirectionalStream é apenas uma combinação de um WebTransportSendStream e um WebTransportReceiveStream. Os exemplos das duas seções anteriores explicam como usar cada uma delas.

Polyfill

Um polyfill (ou melhor, um ponyfill que oferece funcionalidade como um módulo independente que você pode usar) chamado webtransport-ponyfill-websocket que implementa alguns dos recursos do WebTransport está disponível. Leia com atenção as restrições no README do projeto para determinar se essa solução pode funcionar no seu caso de uso.

Considerações sobre privacidade e segurança

Consulte a seção correspondente do rascunho da especificação para orientações oficiais.

Feedback

Há algo na API que é estranho ou não funciona como esperado? Ou há peças faltando que você precisa para implementar sua ideia?

Seu apoio público ajuda o Chrome a priorizar recursos e mostra a outros fornecedores de navegadores a importância de oferecer suporte a eles.

Discussão geral

Use o grupo do Google web-transport-dev (em inglês) para perguntas gerais ou problemas que não se encaixam em uma das outras categorias.

Agradecimentos

Incorporamos informações do WebTransport Explainer e do rascunho da especificação. Agradecemos aos respectivos autores por fornecerem essa base.