HTTP/3 has been in progress since 2018. It is based on Google’s QUIC protocol (which itself is based on UDP), and fixes many issues around the classic TCP protocol, on which HTTP and WebSockets are based.
These include:
- Head-of-Line Blocking
HTTP/2 allows multiplexing, so a single connection can stream multiple resources simultaneously. However, if even one resource fails, all other resources on that connection are put on hold until any missing packets are retransmitted. With QUIC, only the failed resource is affected.
- fast performance
QUIC is more performant than TCP in many ways. Instead of delegating responsibility to other protocols like TLS, QUIC can handle security features itself – meaning fewer round trips. And streams provide better transport efficiency than older packet systems. This can make a significant difference, especially on high-latency networks.
- Better Network Changes
QUIC uses a unique connection ID to handle the source and destination of each request – ensuring that packets are delivered correctly. This ID can persist between different networks, meaning that, for example, if you switch from Wi-Fi to a mobile network the download can continue without interruption. HTTP/2, on the other hand, uses IP addresses as identifiers, so network transmission can be problematic.
- incredible transportation
HTTP/3 supports unreliable data transmission via datagrams.
The WebTransport API provides low-level access to two-way communication via HTTP/3, taking advantage of the above benefits, and supports both reliable and untrusted data transmission.
initial connection
To open a connection to an HTTP/3 server, you pass its URL WebTransport() Producer. Note that the scheme must be HTTPS, and the port number must be explicitly specified. Once WebTransport.ready Promise fulfilled, you can start using the connection.
Also note that you can respond by waiting when the connection is closed WebTransport.closed Promise to fulfill. Errors returned by WebTransport operations are as follows WebTransportErrorand includes additional data on top of the standard DOMException set.
const url = "https://example.com:4999/wt";
async function initTransport(url) {
// Initialize transport connection
const transport = new WebTransport(url);
// The connection can be used once ready fulfills
await transport.ready;
// …
}
// …
async function closeTransport(transport) {
// Respond to connection closing
try {
await transport.closed;
console.log(`The HTTP/3 connection to ${url} closed gracefully.`);
} catch (error) {
console.error(`The HTTP/3 connection to ${url} closed due to ${error}.`);
}
}
Unreliable transmission via datagram
“Unreliable” means that the transmission of data is not guaranteed, nor is it guaranteed to arrive in any specific order. This is fine in some situations and provides very fast delivery. For example, you might want to broadcast regular game status updates, where each message replaces the last message that arrived, and the order is not important.
Unreliable data transmission is handled through WebTransport.datagrams Asset – it gives a return WebTransportDatagramDuplexStream The object contains everything you need to send datagrams to the server and receive them back.
WebTransportDatagramDuplexStream.writable asset return a WritableStream Object to which you can write data using Writer for transmission 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);
WebTransportDatagramDuplexStream.readable asset return a ReadableStream Objects that you can use to get data from the server:
async function readData() {
const reader = transport.datagrams.readable.getReader();
while (true) {
const { value, done } = await reader.read();
if (done) {
break;
}
// value is a Uint8Array.
console.log(value);
}
}
Reliable transmission via streams
“Reliable” means that the transmission and order of the data is guaranteed. This provides slow delivery (albeit faster than WebSockets), and is needed in situations where reliability and ordering are important (e.g. chat applications).
You can also set the relative priority of different streams on the same transport when using reliable transmission via streams.
unidirectional transmission
To open a unidirectional stream from a user agent, you use WebTransport.createUnidirectionalStream() Method to get reference of A WritableStreamThis allows you to get a writer to allow you to write data to a stream and send it to the server,
async function writeData() {
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}`);
}
}
Also pay attention to the use of WritableStreamDefaultWriter.close() A method of closing the associated HTTP/3 connection after all data has been sent.
If the server opens a unidirectional stream to transmit data to the client, it can be accessed on the client through WebTransport.incomingUnidirectionalStreams property, which returns a ReadableStream Of WebTransportReceiveStream objects. These can be used to read Uint8Array Examples sent by the server.
In this case, the first thing that needs to be done is to set up a function to read a WebTransportReceiveStreamThese things are inherited ReadableStream Class, so it can be used in the same way:
async function readData(receiveStream) {
const reader = receiveStream.getReader();
while (true) {
const { done, value } = await reader.read();
if (done) {
break;
}
// value is a Uint8Array
console.log(value);
}
}
Next, call WebTransport.incomingUnidirectionalStreams And get reader’s reference available at ReadableStream It comes back, and then uses the reader to read data from the server. each piece is one WebTransportReceiveStreamand we use it readFrom() Set them up in advance to read:
async function receiveUnidirectional() {
const uds = transport.incomingUnidirectionalStreams;
const reader = uds.getReader();
while (true) {
const { done, value } = await reader.read();
if (done) {
break;
}
// value is an instance of WebTransportReceiveStream
await readData(value);
}
}
two-way transmission
To open a bidirectional stream from a user agent, you use WebTransport.createBidirectionalStream() Method to get reference of A WebTransportBidirectionalStreamThis includes readable And writable Attributes whose references are returned WebTransportReceiveStream And WebTransportSendStream Instances that can be used to read and write from the server.
Comment:
WebTransportBidirectionalStream is similar to WebTransportDatagramDuplexStreamexcept that in that interface readable And writable properties are ReadableStream And WritableStream Respectively.
async function setUpBidirectional() {
const stream = await transport.createBidirectionalStream();
// stream is a WebTransportBidirectionalStream
// stream.readable is a WebTransportReceiveStream
const readable = stream.readable;
// stream.writable is a WebTransportSendStream
const writable = stream.writable;
// …
}
read from WebTransportReceiveStream Then it can be done as follows:
async function readData(readable) {
const reader = readable.getReader();
while (true) {
const { value, done } = await reader.read();
if (done) {
break;
}
// value is a Uint8Array.
console.log(value);
}
}
and am writing to WebTransportSendStream This can be done as follows:
async function writeData(writable) {
const writer = writable.getWriter();
const data1 = new Uint8Array([65, 66, 67]);
const data2 = new Uint8Array([68, 69, 70]);
writer.write(data1);
writer.write(data2);
}
If the server opens a bidirectional stream to transmit and receive data from the client, it can be accessed through WebTransport.incomingBidirectionalStreams property, which returns a ReadableStream Of WebTransportBidirectionalStream objects. Each can be used for reading and writing Uint8Array Example as shown above. However, as with the unidirectional example, you first need an initialization function to read the bidirectional stream:
async function receiveBidirectional() {
const bds = transport.incomingBidirectionalStreams;
const reader = bds.getReader();
while (true) {
const { done, value } = await reader.read();
if (done) {
break;
}
// value is an instance of WebTransportBidirectionalStream
await readData(value.readable);
await writeData(value.writable);
}
}
For complete examples, see:
