Реалізація WebTransport для low-latency комунікації на сайті
WebTransport — браузерний API поверх HTTP/3 (QUIC), доступний з Chrome 97+, Edge 97+, Firefox 114+. На відміну від WebSocket, працює поверх UDP через QUIC, підтримує кілька незалежних потоків і датаграми без head-of-line blocking. Для задач з вимогою задержки нижче 50 мс — це якісний крок вперед порівняно з WebSocket.
Де WebTransport виграє у WebSocket
WebSocket — це один TCP-потік. Потеря пакета блокує всю чергу (head-of-line blocking). При поганому з'єднанні задержки зростають непропорційно. QUIC розв'язує мультиплексуванням: кожен потік незалежний, потеря пакета в одному не впливає на інші.
| Характеристика | WebSocket | WebTransport |
|---|---|---|
| Протокол | TCP | QUIC (UDP) |
| Мультиплексування | Ні | Так (незалежні потоки) |
| Датаграми (ненадійні) | Ні | Так |
| Head-of-line blocking | Є | Ні (для різних потоків) |
| Поддержка браузерів | Всі | Chrome 97+, Firefox 114+, Edge 97+ |
| 0-RTT reconnect | Ні | Так (QUIC session resumption) |
Вимоги до сервера
WebTransport потребує HTTP/3. Варіанти:
-
Go:
quic-go+webtransport-go -
Node.js:
@fails-components/webtransport(експериментальний) -
Python:
aioquic - Cloudflare Workers: нативна підтримка WebTransport
- nginx/Caddy: поки немає нативної підтримки WebTransport
Мінімальний сервер на Go з webtransport-go:
package main
import (
"context"
"crypto/tls"
"log"
"net/http"
"github.com/quic-go/quic-go/http3"
"github.com/quic-go/webtransport-go"
)
func main() {
s := webtransport.Server{
H3: http3.Server{
Addr: ":4433",
TLSConfig: loadTLSConfig(), // TLS обов'язковий
},
}
http.HandleFunc("/wt", func(w http.ResponseWriter, r *http.Request) {
session, err := s.Upgrade(w, r)
if err != nil {
log.Printf("upgrade error: %v", err)
return
}
handleSession(session)
})
s.ListenAndServe()
}
func handleSession(session *webtransport.Session) {
ctx := context.Background()
for {
// Приймаємо вхідний двонаправлений потік
stream, err := session.AcceptStream(ctx)
if err != nil {
return
}
go handleStream(stream)
}
}
Клієнт: базове підключення
const transport = new WebTransport('https://your-server:4433/wt');
// Чекаємо готовності
await transport.ready;
console.log('WebTransport connected');
transport.closed.then(() => console.log('Transport closed'));
// Обробка помилок
transport.closed.catch(err => console.error('Transport error:', err));
Двонаправлені потоки
Потоки — надійні упорядковані канали, як кілька незалежних WebSocket в одному з'єднанні:
// Клієнт відкриває потік
const stream = await transport.createBidirectionalStream();
const writer = stream.writable.getWriter();
const reader = stream.readable.getReader();
// Відправка
const encoder = new TextEncoder();
await writer.write(encoder.encode(JSON.stringify({ type: 'subscribe', channel: 'prices' })));
// Читання відповідей
const decoder = new TextDecoder();
while (true) {
const { value, done } = await reader.read();
if (done) break;
const message = JSON.parse(decoder.decode(value));
handleMessage(message);
}
Сервер може ініціювати потоки в сторону клієнта:
// Клієнт приймає вхідні потоки від сервера
const streamReader = transport.incomingBidirectionalStreams.getReader();
while (true) {
const { value: stream, done } = await streamReader.read();
if (done) break;
processServerStream(stream);
}
Датаграми (ненадійні)
Ненадійні, неупорядковані UDP-like повідомлення. Для позицій гравців, метрик, курсорів — задержка важливіша за гарантію:
// Відправка датаграми
const datagramWriter = transport.datagrams.writable.getWriter();
const encoder = new TextEncoder();
function sendPosition(x, y) {
const data = encoder.encode(JSON.stringify({ x, y, ts: Date.now() }));
// Не очікуємо підтвердження, fire-and-forget
datagramWriter.write(data).catch(() => {}); // потеря пакета — норма
}
// Отримання датаграм
const datagramReader = transport.datagrams.readable.getReader();
const decoder = new TextDecoder();
(async () => {
while (true) {
const { value, done } = await datagramReader.read();
if (done) break;
const msg = JSON.parse(decoder.decode(value));
updateRemotePosition(msg);
}
})();
Однонаправлені потоки
Для потокової відправки даних (логування подій, бінарні дані):
// Клієнт → Сервер: однонаправлений потік
const sendStream = await transport.createUnidirectionalStream();
const writer = sendStream.getWriter();
await writer.write(encodeChunk(data));
await writer.close();
// Сервер → Клієнт: вхідні однонаправлені потоки
const incomingReader = transport.incomingUnidirectionalStreams.getReader();
while (true) {
const { value: stream, done } = await incomingReader.read();
if (done) break;
const reader = stream.getReader();
// читаємо дані з потоку
}
Реальний кейс: торговий терминал
Біржові котирування вимагають мінімальної задержки. Архітектура з WebTransport:
Біржевий фід (UDP) -> Go-сервер -> WebTransport -> Браузер
- Датаграми для тиків цін (fire-and-forget, втрата 1–2% допустима)
- Надійний потік для ордерів та підтверджень
- Окремий потік для підписок на інструменти
Проблема TLS-сертифікату в розробці
WebTransport вимагає валідний TLS. У dev-середовищі — два варіанти:
1. Chrome флаг для self-signed:
chrome://flags/#allow-insecure-localhost
2. Certificate pinning через serverCertificateHashes:
const transport = new WebTransport('https://localhost:4433/wt', {
serverCertificateHashes: [{
algorithm: 'sha-256',
value: hexToArrayBuffer('YOUR_CERT_SHA256_HASH'),
}],
});
Сертифікат максимум 14 днів при використанні цього методу — генерується при запуску dev-сервера.
Fallback стратегія
WebTransport не підтримується Safari (станом на початок 2026). Потрібен graceful fallback:
async function createTransport(url) {
if ('WebTransport' in window) {
try {
const wt = new WebTransport(url.replace('wss://', 'https://'));
await wt.ready;
return new WebTransportAdapter(wt);
} catch (e) {
console.warn('WebTransport failed, falling back to WebSocket');
}
}
return new WebSocketAdapter(url);
}
Адаптер приховує різницю за єдиним інтерфейсом send(data) / on('message', cb).
Терміни
- Прототип з датаграмами та одним потоком — 2–3 дні
- Production-сервер на Go з TLS + комплексною обробкою потоків — 1 тиждень
- Повноцінний клієнт з fallback на WebSocket, моніторингом задержок, reconnect — 2–3 тижні
- Інтеграція з існуючим real-time додатком (замінити WebSocket) — 3–5 днів при наявності адаптерного шару







