Photo of fast-moving traffic.

Experimenting with WebTransport

Experimenting with WebTransport

WebTransport is a new 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.

Updated

Caution: This proposal continues to change during the origin trial period. There may be a divergence between the browser implementation and the information in this article.

For the latest on this evolving proposal, please read refer to the editor's draft of WebTransport.

Once the proposal stabilizes, we will update this article and associated code samples with up to date information.

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.

As part of the origin trial process, we're interested in hearing more about how you plan to use WebTransport.

Many of the concepts in this proposal were previously experimented with as part of the earlier QuicTransport origin trial, which did not end up being released as part of Chrome.

WebTransport helps with similar use cases as QuicTransport, with the primary difference being that HTTP/3 instead of QUIC is the underlying transport protocol.

Current status #

StepStatus
1. Create explainerComplete
2. Create initial draft of specificationComplete
3. Gather feedback and iterate designIn Progress
4. Origin trialIn Progress
5. LaunchNot Started

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.

WebRTC data channels support peer-to-peer communications, but WebTransport only supports client-server connection. If you have multiple clients that need to talk directly to each other, then WebTransport isn't a viable alternative.

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 locally. (Unfortunately, a public reference server compatible with the latest specification is not currently available.) You can then use this page with a basic JavaScript client to try out client/server communications.

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 WebTransport origin trial 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);
}

Chrome does not currently expose an async iterator for a ReadableStream. For the time being, using the getReader() method combined with a while() loop is the best way to read from the stream.

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.

SendStream #

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

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.createSendStream();
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 QUIC 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.createSendStream();
const writer = ws.getWriter();
writer.write(...);
writer.write(...);
await writer.abort();
// Not all the data may have been written.

ReceiveStream #

A ReceiveStream is initiated by the server. Obtaining a ReceiveStream is a two-step process for a web client. First, it calls the receiveStreams() method of a WebTransport instance, which returns a ReadableStream. Each chunk of that ReadableStream, is, in turn, a ReceiveStream 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.receiveStreams();
const reader = rs.getReader();
while (true) {
const {done, value} = await reader.read();
if (done) {
break;
}
// value is an instance of ReceiveStream
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 STREAM_RESET), 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.');
});

BidirectionalStream #

A BidirectionalStream 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 BidirectionalStream.

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

You can listen for a BidirectionalStream created by the server with the receiveBidirectionalStreams() method of a WebTransport instance, which returns a ReadableStream. Each chunk of that ReadableStream, is, in turn, a BidirectionalStream.

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

A BidirectionalStream is just a combination of a SendStream and ReceiveStream. 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.

Enabling support during the origin trial #

  1. Request a token for your origin.
  2. Add the token to your pages. There are two ways to do that:
    • Add an origin-trial <meta> tag to the head of each page. For example, this may look something like:
      <meta http-equiv="origin-trial" content="TOKEN_GOES_HERE">
    • If you can configure your server, you can also add the token using an Origin-Trial HTTP header. The resulting response header should look something like:
      Origin-Trial: TOKEN_GOES_HERE

WebTransport in Chrome's DevTools #

Unfortunately, Chrome's DevTools support for WebTransport is not ready for the start of the origin trial. You can "star" this Chrome issue to be notified about updates on the DevTools interface.

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 throughout the origin trial process.

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.

  • Be sure you have signed up for the origin trial to show your interest and provide your domain and contact info.
  • Send a tweet to @ChromiumDev using the hashtag #WebTransport and details on where and how you're using it.

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.

Last updated: Improve article