Using WebTransport

WebTransport is an API offering low-latency, bidirectional, client-server messaging. Learn more about its use cases, and how to give feedback about the future of the implementation.

Background

What's WebTransport?

WebTransport is a web API that uses the HTTP/3 protocol as a bidirectional transport. It's intended for two-way communications between a web client and an HTTP/3 server. It supports sending data both unreliably via its datagram APIs, and reliably via its streams APIs.

Datagrams are ideal for sending and receiving data that do not need strong delivery guarantees. Individual packets of data are limited in size by the maximum transmission unit (MTU) of the underlying connection, and may or may not be transmitted successfully, and if they are transferred, they may arrive in an arbitrary order. These characteristics make the datagram APIs ideal for low-latency, best-effort data transmission. You can think of datagrams as user datagram protocol (UDP) messages, but encrypted and congestion-controlled.

The streams APIs, in contrast, provide reliable, ordered data transfer. They're well-suited to scenarios where you need to send or receive one or more streams of ordered data. Using multiple WebTransport streams is analogous to establishing multiple TCP connections, but since HTTP/3 uses the lighter-weight QUIC protocol under the hood, they can be opened and closed without as much overhead.

Use cases

This a small list of possible ways developers might use WebTransport.

  • Sending game state at a regular interval with minimal latency to a server via small, unreliable, out-of-order messages.
  • Receiving media streams pushed from a server with minimal latency, independent of other data streams.
  • Receiving notifications pushed from a server while a web page is open.

We're interested in hearing more about how you plan to use WebTransport.

Browser support

Browser Support

  • Chrome: 97.
  • Edge: 97.
  • Firefox: 114.
  • Safari: not supported.

Source

As with all features that do not have universal browser support, coding defensively via feature detection is a best practice.

Current status

Step Status
1. Create explainer Complete
2. Create initial draft of specification Complete
3. Gather feedback and iterate design Complete
4. Origin trial Complete
5. Launch Chromium 97

WebTransport's relationship to other technologies

Is WebTransport a replacement for WebSockets?

Maybe. There are use cases where either WebSockets or WebTransport might be valid communication protocols to use.

WebSockets communications are modeled around a single, reliable, ordered stream of messages, which is fine for some types of communication needs. If you need those characteristics, then WebTransport's streams APIs can provide them as well. In comparison, WebTransport's datagram APIs provide low-latency delivery, without guarantees about reliability or ordering, so they're not a direct replacement for WebSockets.

Using WebTransport, via the datagram APIs or via multiple concurrent Streams API instances, means that you don't have to worry about head-of-line blocking, which can be an issue with WebSockets. Additionally, there are performance benefits when establishing new connections, as the underlying QUIC handshake is faster than starting up TCP over TLS.

WebTransport is part of a new draft specification, and as such the WebSocket ecosystem around client and server libraries is currently much more robust. If you need something that works "out of the box" with common server setups, and with broad web client support, WebSockets is a better choice today.

Is WebTransport the same as a UDP Socket API?

No. WebTransport is not a UDP Socket API. While WebTransport uses HTTP/3, which in turn uses UDP "under the hood," WebTransport has requirements around encryption and congestion control that make it more than a basic UDP Socket API.

Is WebTransport an alternative to WebRTC data channels?

Yes, for client-server connections. WebTransport shares many of the same properties as WebRTC data channels, although the underlying protocols are different.

Generally, running a HTTP/3-compatible server requires less setup and configuration than maintaining a WebRTC server, which involves understanding multiple protocols (ICE, DTLS, and SCTP) in order to get a working transport. WebRTC entails many more moving pieces that could lead to failed client/server negotiations.

The WebTransport API was designed with the web developer use cases in mind, and should feel more like writing modern web platform code than using WebRTC's data channel interfaces. Unlike WebRTC, WebTransport is supported inside of Web Workers, which allows you to perform client-server communications independent of a given HTML page. Because WebTransport exposes a Streams-compliant interface, it supports optimizations around backpressure.

However, if you already have a working WebRTC client/server setup that you're happy with, switching to WebTransport may not offer many advantages.

Try it out

The best way to experiment with WebTransport is to start up a compatible HTTP/3 server. You can then use this page with a basic JavaScript client to try out client/server communications.

Additionally, a community-maintained echo server is available at webtransport.day.

Using the API

WebTransport was designed on top of modern web platform primitives, like the Streams API. It relies heavily on promises, and works well with async and await.

The current WebTransport implementation in Chromium supports three distinct types of traffic: datagrams, as well as both unidirectional and bidirectional streams.

Connecting to a server

You can connect to a HTTP/3 server by creating a WebTransport instance. The scheme of the URL should be https. You need to explicitly specify the port number.

You should use the ready promise to wait for the connection to be established. This promise will not be fulfilled until the setup is complete, and will reject if the connection fails at the QUIC/TLS stage.

The closed promise fulfills when the connection closes normally, and rejects if the closure was unexpected.

If the server rejects the connection due to a client indication error (e.g. the path of the URL is invalid), then that causes closed to reject, while ready remains unresolved.

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;

Datagram APIs

Once you have a WebTransport instance that's connected to a server, you can use it to send and receive discrete bits of data, known as datagrams.

The writeable getter returns a WritableStream, which a web client can use to send data to the server. The readable getter returns a ReadableStream, allowing you to listen for data from the server. Both streams are inherently unreliable, so it is possible that the data you write will not be received by the server, and vice versa.

Both types of streams use Uint8Array instances for data transfer.

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

Streams APIs

Once you've connected to the server, you could also use WebTransport to send and receive data via its Streams APIs.

Each chunk of all streams is a Uint8Array. Unlike with the Datagram APIs, these streams are reliable. But each stream is independent, so data order across streams is not guaranteed.

WebTransportSendStream

A WebTransportSendStream is created by the web client using the createUnidirectionalStream() method of a WebTransport instance, which returns a promise for the WebTransportSendStream.

Use the close() method of the WritableStreamDefaultWriter to close the associated HTTP/3 connection. The browser tries to send all pending data before actually closing the associated connection.

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

Similarly, use the abort() method of the WritableStreamDefaultWriter to send a RESET\_STREAM to the server. When using abort(), the browser may discard any pending data that hasn't yet been sent.

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

A WebTransportReceiveStream is initiated by the server. Obtaining a WebTransportReceiveStream is a two-step process for a web client. First, it calls the incomingUnidirectionalStreams attribute of a WebTransport instance, which returns a ReadableStream. Each chunk of that ReadableStream, is, in turn, a WebTransportReceiveStream that can be used to read Uint8Array instances sent by the server.

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

You can detect stream closure using the closed promise of the ReadableStreamDefaultReader. When the underlying HTTP/3 connection is closed with the FIN bit, the closed promise is fulfilled after all the data is read. When the HTTP/3 connection is closed abruptly (for example, by RESET\_STREAM), then the closed promise rejects.

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

A WebTransportBidirectionalStream might be created either by the server or the client.

Web clients can create one using the createBidirectionalStream() method of a WebTransport instance, which returns a promise for a WebTransportBidirectionalStream.

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

You can listen for a WebTransportBidirectionalStream created by the server with the incomingBidirectionalStreams attribute of a WebTransport instance, which returns a ReadableStream. Each chunk of that ReadableStream, is, in turn, a 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
}

A WebTransportBidirectionalStream is just a combination of a WebTransportSendStream and WebTransportReceiveStream. The examples from the previous two sections explain how to use each of them.

More examples

The WebTransport draft specification includes a number of additional inline examples, along with full documentation for all of the methods and properties.

WebTransport in Chrome's DevTools

Unfortunately, Chrome's DevTools do not currently support WebTransport. You can "star" this Chrome issue to be notified about updates on the DevTools interface.

Polyfill

A polyfill (or rather ponyfill that provides functionality as a standalone module you can use) called webtransport-ponyfill-websocket that implements some of the features of WebTransport is available. Carefully read the constraints in the project's README to determine if this solution can work for your use case.

Privacy and security considerations

See the corresponding section of the draft specification for authoritative guidance.

Feedback

The Chrome team wants to hear your thoughts and experiences using this API.

Feedback about the API design

Is there something about the API that's awkward or doesn't work as expected? Or are there missing pieces that you need to implement your idea?

File an issue on the Web Transport GitHub repo, or add your thoughts to an existing issue.

Problem with the implementation?

Did you find a bug with Chrome's implementation?

File a bug at https://new.crbug.com. Include as much detail as you can, along with simple instructions for reproducing.

Planning to use the API?

Your public support helps Chrome prioritize features, and shows other browser vendors how critical it is to support them.

General discussion

You can use the web-transport-dev Google Group for general questions or problems that don't fit into one of the other categories.

Acknowledgements

This article incorporates information from the WebTransport Explainer, draft specification, and related design docs. Thank you to the respective authors for providing that foundation.

The hero image on this post is by Robin Pierre on Unsplash.