# WebTransport API

> WebTransport() 构造函数打开一条承载可靠流与不可靠数据报的 HTTP/3 会话。本页列出构造选项、规范定义的每个异常、WebTransportError，以及 WebSocket 回退。

`WebTransport` 在客户端与服务器之间打开一条基于 HTTP/3（QUIC）的会话，并在其上复用两类流量：有序、可靠的字节流，页面通过 WHATWG Streams 读写；以及无序、不可靠的数据报，适合「最新一条覆盖上一条」的数据。当队头阻塞或 TCP 重传成为问题时，它是 WebSocket 的标准化替代方案，带 `[SecureContext]`，并可在 Worker 中使用。

Chrome 97 在桌面、Android 和 WebView 上发布，Edge 97 与 Samsung Internet 18 跟进，Firefox 114 加入，Safari 26.4 把它带到 macOS 和 iOS（BCD `api.WebTransport`）。`congestionControl` 选项由 Firefox 114 和 Safari 26.4 实现，Chrome 未实现，接受该成员但忽略它。

## 语法

```js
new WebTransport(url)
new WebTransport(url, options)

await transport.ready
transport.closed

transport.createBidirectionalStream()
transport.createBidirectionalStream(options)
transport.createUnidirectionalStream()
transport.createUnidirectionalStream(options)
transport.incomingBidirectionalStreams
transport.incomingUnidirectionalStreams
transport.datagrams.readable
transport.datagrams.writable

transport.close()
transport.close(closeInfo)
```

构造函数同步返回并开始建连；`ready` 是会话建立后兑现的 Promise，`closed` 在正常关闭时以 `WebTransportCloseInfo` 兑现、会话失败时以 `WebTransportError` 拒绝。`createBidirectionalStream()` 以 `WebTransportBidirectionalStream` 兑现，其 `readable` 为 `WebTransportReceiveStream`、`writable` 为 `WebTransportSendStream`；`createUnidirectionalStream()` 以 `WebTransportSendStream` 兑现。两个 `incoming*` 属性是由服务器发起的流组成的 `ReadableStream`。

## 参数

构造函数接受服务器 URL 和可选的 `WebTransportOptions` 字典；`close()` 接受可选的 `WebTransportCloseInfo`。每个选项都有默认值。

| 成员 | 类型 | 必填 | 说明 |
|---|---|---|---|
| `url` | `USVString` | 是 | HTTP/3 端点的绝对或相对 URL，按文档的 API base URL 解析。协议必须是 `https`，且不能带 fragment。 |
| `options.allowPooling` | `boolean` | 否 | 默认 `false`。为 `true` 时允许会话复用到同一源已有的 HTTP/3 连接上，而不是另开一条专用连接。 |
| `options.requireUnreliable` | `boolean` | 否 | 默认 `false`。为 `true` 时禁止在 HTTP/3 建连失败后回退到 HTTP/2，从而保证 `datagrams` 是真正的 QUIC 数据报。 |
| `options.headers` | `HeadersInit` | 否 | 默认 `{}`。建立会话的 CONNECT 请求所附带的额外请求头。 |
| `options.serverCertificateHashes` | `sequence<WebTransportHash>` | 否 | 默认 `[]`。`{ algorithm: "sha-256", value: BufferSource }` 条目；非空时浏览器只在某个哈希匹配时信任服务器，这让专用连接可以使用自签名证书。算法未知的哈希被忽略。 |
| `options.congestionControl` | `WebTransportCongestionControl` | 否 | `"default"`、`"throughput"` 或 `"low-latency"`；仅为提示，浏览器没有对应算法时降为 `"default"`。 |
| `options.anticipatedConcurrentIncomingUnidirectionalStreams` | `unsigned short?` | 否 | 默认 `null`。告知浏览器预计有多少条服务器发起的单向流，以便设置流控上限。 |
| `options.anticipatedConcurrentIncomingBidirectionalStreams` | `unsigned short?` | 否 | 默认 `null`。同上，针对服务器发起的双向流。 |
| `options.protocols` | `sequence<DOMString>` | 否 | 默认 `[]`。向服务器提供的应用协议名；协商结果从 `transport.protocol` 读回。 |
| `options.datagramsReadableType` | `ReadableStreamType` | 否 | 设为 `"bytes"` 时 `datagrams.readable` 成为支持 BYOB 读取器的字节流。 |
| `closeInfo.closeCode` | `unsigned long` | 否 | 默认 `0`。发给对端的应用错误码。 |
| `closeInfo.reason` | `USVString` | 否 | 默认 `""`。发给对端的可读关闭原因。 |

## 异常

构造函数同步抛出；创建流的方法返回被拒绝的 Promise。会话与流的失败以 `WebTransportError` 呈现，它是 `DOMException` 的子类，带 `source`（`"session"` 或 `"stream"`）和 `streamErrorCode` 属性（见 [WebTransportError 接口](https://w3c.github.io/webtransport/#web-transport-error-interface)（w3.org））。

| 异常 | 条件 |
|---|---|
| `SyntaxError` | 构造函数在 `url` 解析失败、协议不是 `https`、带有 fragment，或 `protocols` 含重复项、空名称或超过 512 字节的名称时抛出。 |
| `NotSupportedError` | 构造函数在 `allowPooling` 为 `true` 且 `serverCertificateHashes` 非空时抛出；证书固定需要专用连接。 |
| `InvalidStateError` | 会话进入 `"closed"` 或 `"failed"` 后，`createBidirectionalStream()`、`createUnidirectionalStream()`、`createSendGroup()`、`getStats()` 和 `exportKeyingMaterial()` 拒绝或抛出，包括调用进行中会话失败的情况。把某条流的 `sendGroup` 设为另一个 transport 的分组同样抛出它。 |
| `TypeError` | `createBidirectionalStream()` 或 `createUnidirectionalStream()` 收到的 `sendGroup` 选项属于另一个 `WebTransport`。 |
| `QuotaExceededError` | 流 ID 耗尽且调用传了 `waitUntilAvailable: false`；默认的 `true` 下 Promise 会等待。 |
| `RangeError` | `exportKeyingMaterial()` 的 label 或 context 超过 255 字节，或输出长度为 0 或超过实现上限（至少 4096）。 |
| `WebTransportError` | CONNECT 请求失败或握手被拒时，`ready` 与 `closed` 以 `source: "session"` 拒绝；被重置的流，其 `readable` 和 `writable` 以 `source: "stream"` 加对端的 `streamErrorCode` 出错。 |

:::observed
在 Chrome 中执行 `new WebTransport("http://localhost:4433/wt")` 抛出 `SyntaxError: Failed to construct 'WebTransport': The URL's scheme must be 'https'. 'http' is not allowed.`；执行 `new WebTransport("https://example.com/wt#live")` 抛出 `SyntaxError: Failed to construct 'WebTransport': The URL contains a fragment identifier ('#live'). Fragment identifiers are not allowed in WebTransport URLs.`。传入 `protocols: ["a", "a"]` 抛出 `SyntaxError: Failed to construct 'WebTransport': Duplicate protocols are not allowed.`。三条消息都在 Chromium 的 [`web_transport.cc`](https://chromium.googlesource.com/chromium/src/+/main/third_party/blink/renderer/modules/webtransport/web_transport.cc)（chromium.googlesource.com）中。
:::

## 示例

每个示例都先检测构造函数，并给出浏览器缺少它或服务器不可达时代码的实际行为。

### 带 WebSocket 回退的连接

构造前先检查全局对象，再在 `try` 中 `await ready`：服务器没有 HTTP/3、UDP 端口被拦、证书有问题，都会让 `ready` 以 `WebTransportError` 拒绝，而不是在构造函数中抛出。

```js
async function connect(url) {
  if (typeof WebTransport === "undefined") {
    return { kind: "websocket", socket: new WebSocket(url.replace(/^https/, "wss")) };
  }
  const transport = new WebTransport(url);
  try {
    await transport.ready;
  } catch (err) {
    console.warn(`WebTransport 失败（${err.name}，source=${err.source}），改用 WebSocket`);
    return { kind: "websocket", socket: new WebSocket(url.replace(/^https/, "wss")) };
  }
  transport.closed
    .then((info) => console.log(`已关闭：code ${info.closeCode} "${info.reason}"`))
    .catch((err) => console.error(`会话失败：${err.message}`));
  return { kind: "webtransport", transport };
}
```

WebSocket 路径没有数据报，也没有流之间的独立性，调用方应把 `kind` 当作能力标志，而不是假设两者行为一致。

### 用数据报发送游戏状态

数据报的上限是 `transport.datagrams.maxDatagramSize` 字节，由 QUIC 路径 MTU 决定，且可能丢失或乱序，所以每条消息必须自足。用数据报 writer 写，用循环读 `datagrams.readable`。

```js
async function runDatagrams(transport, encodeState, applyState) {
  const writer = transport.datagrams.writable.getWriter();
  const reader = transport.datagrams.readable.getReader();
  const max = transport.datagrams.maxDatagramSize;

  const timer = setInterval(async () => {
    const bytes = encodeState(); // 当前状态的类型化数组
    if (bytes.byteLength <= max) await writer.write(bytes);
  }, 50);

  try {
    while (true) {
      const { value, done } = await reader.read();
      if (done) break;
      applyState(value);
    }
  } finally {
    clearInterval(timer);
    writer.releaseLock();
  }
}
```

`requireUnreliable` 保持 `false` 而会话被池化到 HTTP/2 时，数据报会经回退通道可靠送达，延迟特性随之不同；不可靠语义比连通性更重要时，传 `requireUnreliable: true`。

### 在双向流上做回显并接收服务器发起的流

双向流是一对字节流；用 `TextEncoderStream` 和 `TextDecoderStream` 管道化，应用层看到的就是字符串。服务器发起的流到达 `incomingBidirectionalStreams`，必须循环读取，否则服务器的流预算会卡住。

```js
async function echoOnce(transport, message) {
  let stream;
  try {
    stream = await transport.createBidirectionalStream();
  } catch (err) {
    if (err.name === "InvalidStateError") return null; // 会话已关闭
    throw err;
  }
  const writer = stream.writable.getWriter();
  await writer.write(new TextEncoder().encode(message));
  await writer.close();

  const chunks = [];
  for await (const chunk of stream.readable.pipeThrough(new TextDecoderStream())) {
    chunks.push(chunk);
  }
  return chunks.join("");
}

async function acceptServerStreams(transport, handle) {
  const reader = transport.incomingBidirectionalStreams.getReader();
  while (true) {
    const { value: stream, done } = await reader.read();
    if (done) break;
    handle(stream.readable, stream.writable);
  }
}
```

关闭 writer 会发送 FIN，回显服务器由此得知请求已完整；从不关闭的流会一直占用其 ID 直到会话结束。

## 另请参阅

- [WebRTC API](/zh/reference/capabilities/webrtc/)，其 `RTCDataChannel` 是 WebTransport 流的点对点对应物
- [WebCodecs](/zh/reference/capabilities/webcodecs/)，在通过 WebTransport 流发送媒体帧前先编码
- [WebTransport: constructor](https://w3c.github.io/webtransport/#webtransport-constructor)（w3.org）
- [WebTransport: WebTransportError interface](https://w3c.github.io/webtransport/#web-transport-error-interface)（w3.org）
- [WebTransport over HTTP/3](https://datatracker.ietf.org/doc/html/draft-ietf-webtrans-http3)（datatracker.ietf.org）
- [Using WebTransport](https://developer.chrome.com/docs/capabilities/web-apis/webtransport)（developer.chrome.com）