{{series_nav(current_post=12)}}
HTTP/3 and WebTransport represent the future of web transport protocols, replacing TCP with QUIC (Quick UDP Internet Connections) to eliminate head-of-line blocking, reduce connection establishment latency, and enable new communication patterns. Built on UDP, these protocols offer the reliability of TCP with the flexibility and performance previously only available in custom solutions.
What are HTTP/3 and WebTransport?
HTTP/3 is the third major version of HTTP, using QUIC instead of TCP as its transport protocol. QUIC provides multiplexed streams, built-in encryption, and faster connection establishment.
WebTransport is a new browser API that provides low-level access to QUIC streams and datagrams, enabling bidirectional communication with both reliable streams and unreliable messages.
Core Concepts
QUIC: UDP-based transport protocol with TCP-like reliability plus modern features.
Streams: Independent data flows that don’t block each other (no head-of-line blocking).
Datagrams: Unreliable, unordered messages for low-latency use cases.
0-RTT: Resume connections instantly using previous session data.
Connection Migration: Maintain connections when switching networks (Wi-Fi to cellular).
Encryption by Default: TLS 1.3 integrated into QUIC, cannot be disabled.
Architecture Comparison
Multiplexing] T2[TLS 1.3] TCP2[TCP
Head-of-Line Blocking] IP2[IP] H2 --> T2 T2 --> TCP2 TCP2 --> IP2 end subgraph "HTTP/3 over QUIC" H3[HTTP/3
Multiplexing] Q[QUIC
Streams + TLS 1.3
No HOL Blocking] UDP[UDP] IP3[IP] H3 --> Q Q --> UDP UDP --> IP3 end style TCP2 fill:#ffcccc style Q fill:#ccffcc
Head-of-Line Blocking Elimination
waiting for retransmission S->>C: Stream 1: Retransmitted packet S->>C: Stream 2: Response (delayed) S->>C: Stream 3: Response (delayed) end rect rgb(230, 255, 230) Note over C,S: HTTP/3 over QUIC - No HOL Blocking C->>S: Stream 1: Request A C->>S: Stream 2: Request B C->>S: Stream 3: Request C Note over S: Stream 1 packet lost! S--xC: Stream 1: Packet lost Note over C,S: Only Stream 1 blocked
Streams 2 & 3 proceed independently S->>C: Stream 2: Response (not blocked!) S->>C: Stream 3: Response (not blocked!) S->>C: Stream 1: Retransmitted packet S->>C: Stream 1: Response end
QUIC Connection Establishment
(combined handshake) S->>C: ServerHello + Certificate
+ HTTP Response Note over C,S: Total: 1 Round-Trip Time end rect rgb(240, 240, 255) Note over C,S: QUIC 0-RTT (Resumption) C->>S: 0-RTT Data + HTTP Request
(using cached session) S->>C: HTTP Response Note over C,S: Total: 0 Round-Trip Times! end
WebTransport Communication Patterns
Server→Client or Client→Server] BIDI[Bidirectional Streams
Request-Response] end subgraph "Datagrams" DGRAM[Unreliable Datagrams
Fire-and-forget] end end subgraph "Server" S[WebTransport Handler] end C <-->|Reliable
Ordered| BIDI C -->|Reliable
Ordered| UNI S -->|Reliable
Ordered| UNI C <-.->|Unreliable
Unordered| DGRAM S <-.->|Unreliable
Unordered| DGRAM BIDI --> Q UNI --> Q DGRAM --> Q Q --> S
Real-World Use Cases
1. Cloud Gaming & Game Streaming
Ultra-low latency input and video streaming with independent streams.
Perfect for: Stadia, GeForce NOW, Xbox Cloud Gaming, multiplayer games
2. Video Conferencing
Multiple video/audio streams that don’t block each other on packet loss.
Perfect for: Zoom, Google Meet, Teams, WebRTC media servers
3. Live Streaming
Low-latency broadcasting with quick recovery from network issues.
Perfect for: Twitch, YouTube Live, sports broadcasts, live auctions
4. Financial Trading
Real-time market data with datagrams for non-critical updates.
Perfect for: Trading platforms, market data feeds, order books
5. Multiplayer Gaming
Player position updates via datagrams, game state via streams.
Perfect for: FPS games, racing games, MMORPGs, battle royale
6. IoT Sensor Networks
Efficient data transmission from mobile/unstable connections.
Perfect for: Vehicle telemetry, mobile sensors, drone communications
7. CDN & Asset Delivery
Faster page loads with 0-RTT resumption and better multiplexing.
Perfect for: CDNs, image optimization, video delivery, web performance
8. Collaborative Applications
Real-time sync with independent document streams.
Perfect for: Google Docs, Figma, code editors, whiteboarding tools
Project Structure
webtransport-service/
├── cmd/
│ ├── server/
│ │ └── main.go # WebTransport server
│ └── client/
│ └── main.go # Go client (testing)
├── internal/
│ ├── server/
│ │ ├── server.go # WebTransport server
│ │ ├── session.go # Session management
│ │ └── handler.go # Message handlers
│ ├── stream/
│ │ ├── stream.go # Stream handling
│ │ └── datagram.go # Datagram handling
│ └── protocol/
│ ├── message.go # Message protocol
│ └── codec.go # Encoding/decoding
├── pkg/
│ ├── gaming/
│ │ ├── player.go # Player state
│ │ └── world.go # Game world
│ └── metrics/
│ └── metrics.go # Performance metrics
├── web/
│ ├── index.html # Client UI
│ └── client.js # JavaScript client
├── certs/
│ ├── cert.pem # TLS certificate
│ └── key.pem # TLS private key
├── go.mod
└── go.sum
Implementation
1. Message Protocol
// internal/protocol/message.go
package protocol
import (
"encoding/binary"
"encoding/json"
"fmt"
)
// MessageType defines message types
type MessageType byte
const (
TypePlayerPosition MessageType = 0x01
TypeChatMessage MessageType = 0x02
TypeGameState MessageType = 0x03
TypePing MessageType = 0x04
TypePong MessageType = 0x05
TypePlayerJoin MessageType = 0x06
TypePlayerLeave MessageType = 0x07
)
// Message represents a protocol message
type Message struct {
Type MessageType
Payload []byte
Timestamp int64
}
// PlayerPosition for real-time updates (datagrams)
type PlayerPosition struct {
PlayerID string `json:"player_id"`
X float64 `json:"x"`
Y float64 `json:"y"`
Z float64 `json:"z"`
Rotation float64 `json:"rotation"`
Velocity float64 `json:"velocity"`
Timestamp int64 `json:"timestamp"`
}
// ChatMessage for reliable delivery (streams)
type ChatMessage struct {
PlayerID string `json:"player_id"`
Username string `json:"username"`
Message string `json:"message"`
Timestamp int64 `json:"timestamp"`
}
// GameState for periodic full state sync (streams)
type GameState struct {
Players []PlayerPosition `json:"players"`
Score map[string]int `json:"score"`
Timestamp int64 `json:"timestamp"`
}
// Encode encodes a message to bytes
func (m *Message) Encode() ([]byte, error) {
// Format: [Type:1][PayloadLen:4][Payload:N]
payloadLen := len(m.Payload)
buf := make([]byte, 1+4+payloadLen)
buf[0] = byte(m.Type)
binary.BigEndian.PutUint32(buf[1:5], uint32(payloadLen))
copy(buf[5:], m.Payload)
return buf, nil
}
// Decode decodes bytes to a message
func Decode(data []byte) (*Message, error) {
if len(data) < 5 {
return nil, fmt.Errorf("message too short")
}
msgType := MessageType(data[0])
payloadLen := binary.BigEndian.Uint32(data[1:5])
if len(data) < int(5+payloadLen) {
return nil, fmt.Errorf("incomplete message")
}
return &Message{
Type: msgType,
Payload: data[5 : 5+payloadLen],
}, nil
}
// NewPlayerPositionMessage creates player position message
func NewPlayerPositionMessage(pos *PlayerPosition) (*Message, error) {
payload, err := json.Marshal(pos)
if err != nil {
return nil, err
}
return &Message{
Type: TypePlayerPosition,
Payload: payload,
Timestamp: pos.Timestamp,
}, nil
}
// ParsePlayerPosition parses player position from message
func ParsePlayerPosition(msg *Message) (*PlayerPosition, error) {
if msg.Type != TypePlayerPosition {
return nil, fmt.Errorf("wrong message type")
}
var pos PlayerPosition
if err := json.Unmarshal(msg.Payload, &pos); err != nil {
return nil, err
}
return &pos, nil
}
2. WebTransport Server
// internal/server/server.go
package server
import (
"context"
"crypto/tls"
"fmt"
"log"
"sync"
"time"
"github.com/quic-go/quic-go"
"github.com/quic-go/quic-go/http3"
"github.com/quic-go/webtransport-go"
)
// Server manages WebTransport connections
type Server struct {
addr string
server *webtransport.Server
sessions sync.Map // sessionID -> *Session
tlsConfig *tls.Config
// Handlers
onSession func(*Session)
onStreamData func(*Session, []byte)
onDatagramData func(*Session, []byte)
}
// Config holds server configuration
type Config struct {
Addr string
TLSConfig *tls.Config
}
// NewServer creates a new WebTransport server
func NewServer(cfg *Config) *Server {
return &Server{
addr: cfg.Addr,
tlsConfig: cfg.TLSConfig,
}
}
// Start starts the server
func (s *Server) Start(ctx context.Context) error {
server := &webtransport.Server{
H3: http3.Server{
Addr: s.addr,
TLSConfig: s.tlsConfig,
QUICConfig: &quic.Config{
EnableDatagrams: true,
MaxIncomingStreams: 1000,
MaxIncomingUniStreams: 1000,
KeepAlivePeriod: 30 * time.Second,
},
},
}
s.server = server
// Handle WebTransport sessions
http.HandleFunc("/webtransport", func(w http.ResponseWriter, r *http.Request) {
session, err := server.Upgrade(w, r)
if err != nil {
log.Printf("Upgrade error: %v", err)
return
}
log.Printf("New session: %s", session.RemoteAddr())
s.handleSession(ctx, session)
})
log.Printf("WebTransport server starting on %s", s.addr)
return server.ListenAndServe()
}
// handleSession handles a WebTransport session
func (s *Server) handleSession(ctx context.Context, wtSession *webtransport.Session) {
session := NewSession(wtSession)
s.sessions.Store(session.ID, session)
defer s.sessions.Delete(session.ID)
if s.onSession != nil {
s.onSession(session)
}
ctx, cancel := context.WithCancel(ctx)
defer cancel()
var wg sync.WaitGroup
// Handle bidirectional streams
wg.Add(1)
go func() {
defer wg.Done()
s.handleStreams(ctx, session)
}()
// Handle datagrams
wg.Add(1)
go func() {
defer wg.Done()
s.handleDatagrams(ctx, session)
}()
// Wait for context cancellation
<-ctx.Done()
wg.Wait()
log.Printf("Session closed: %s", session.ID)
}
// handleStreams handles bidirectional streams
func (s *Server) handleStreams(ctx context.Context, session *Session) {
for {
stream, err := session.wt.AcceptStream(ctx)
if err != nil {
log.Printf("Accept stream error: %v", err)
return
}
go func(stream webtransport.Stream) {
defer stream.Close()
buf := make([]byte, 4096)
for {
n, err := stream.Read(buf)
if err != nil {
return
}
data := make([]byte, n)
copy(data, buf[:n])
if s.onStreamData != nil {
s.onStreamData(session, data)
}
// Echo back (or process)
stream.Write(data)
}
}(stream)
}
}
// handleDatagrams handles unreliable datagrams
func (s *Server) handleDatagrams(ctx context.Context, session *Session) {
for {
data, err := session.wt.ReceiveMessage(ctx)
if err != nil {
log.Printf("Receive datagram error: %v", err)
return
}
if s.onDatagramData != nil {
s.onDatagramData(session, data)
}
}
}
// Broadcast sends data to all sessions
func (s *Server) Broadcast(data []byte, useDatagram bool) {
s.sessions.Range(func(key, value interface{}) bool {
session := value.(*Session)
if useDatagram {
session.SendDatagram(data)
} else {
session.SendStream(data)
}
return true
})
}
// BroadcastExcept sends data to all sessions except one
func (s *Server) BroadcastExcept(excludeID string, data []byte, useDatagram bool) {
s.sessions.Range(func(key, value interface{}) bool {
session := value.(*Session)
if session.ID != excludeID {
if useDatagram {
session.SendDatagram(data)
} else {
session.SendStream(data)
}
}
return true
})
}
// OnSession sets session handler
func (s *Server) OnSession(handler func(*Session)) {
s.onSession = handler
}
// OnStreamData sets stream data handler
func (s *Server) OnStreamData(handler func(*Session, []byte)) {
s.onStreamData = handler
}
// OnDatagramData sets datagram data handler
func (s *Server) OnDatagramData(handler func(*Session, []byte)) {
s.onDatagramData = handler
}
3. Session Management
// internal/server/session.go
package server
import (
"context"
"fmt"
"log"
"sync"
"time"
"github.com/google/uuid"
"github.com/quic-go/webtransport-go"
)
// Session represents a WebTransport session
type Session struct {
ID string
wt *webtransport.Session
mu sync.RWMutex
metadata map[string]interface{}
createdAt time.Time
lastSeen time.Time
}
// NewSession creates a new session
func NewSession(wt *webtransport.Session) *Session {
return &Session{
ID: uuid.New().String(),
wt: wt,
metadata: make(map[string]interface{}),
createdAt: time.Now(),
lastSeen: time.Now(),
}
}
// SendDatagram sends unreliable datagram
func (s *Session) SendDatagram(data []byte) error {
s.mu.Lock()
defer s.mu.Unlock()
if err := s.wt.SendMessage(data); err != nil {
return fmt.Errorf("send datagram: %w", err)
}
s.lastSeen = time.Now()
return nil
}
// SendStream sends reliable stream data
func (s *Session) SendStream(data []byte) error {
s.mu.Lock()
defer s.mu.Unlock()
stream, err := s.wt.OpenStreamSync(context.Background())
if err != nil {
return fmt.Errorf("open stream: %w", err)
}
defer stream.Close()
if _, err := stream.Write(data); err != nil {
return fmt.Errorf("write stream: %w", err)
}
s.lastSeen = time.Now()
return nil
}
// OpenUniStream opens a unidirectional stream
func (s *Session) OpenUniStream(ctx context.Context) (webtransport.SendStream, error) {
return s.wt.OpenUniStreamSync(ctx)
}
// SetMetadata sets session metadata
func (s *Session) SetMetadata(key string, value interface{}) {
s.mu.Lock()
defer s.mu.Unlock()
s.metadata[key] = value
}
// GetMetadata gets session metadata
func (s *Session) GetMetadata(key string) (interface{}, bool) {
s.mu.RLock()
defer s.mu.RUnlock()
val, ok := s.metadata[key]
return val, ok
}
// Close closes the session
func (s *Session) Close() error {
return s.wt.CloseWithError(0, "session closed")
}
// RemoteAddr returns remote address
func (s *Session) RemoteAddr() string {
return s.wt.RemoteAddr().String()
}
4. Game Server Example
// cmd/server/main.go
package main
import (
"context"
"crypto/tls"
"encoding/json"
"log"
"os"
"os/signal"
"sync"
"syscall"
"time"
"webtransport-service/internal/protocol"
"webtransport-service/internal/server"
)
type GameServer struct {
wt *server.Server
players sync.Map // playerID -> *Player
mu sync.RWMutex
}
type Player struct {
ID string
Username string
Position protocol.PlayerPosition
Session *server.Session
LastSeen time.Time
}
func main() {
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
// Load TLS certificates
cert, err := tls.LoadX509KeyPair("certs/cert.pem", "certs/key.pem")
if err != nil {
log.Fatal(err)
}
tlsConfig := &tls.Config{
Certificates: []tls.Certificate{cert},
NextProtos: []string{"h3"},
}
// Create game server
game := &GameServer{}
// Create WebTransport server
wtServer := server.NewServer(&server.Config{
Addr: ":4433",
TLSConfig: tlsConfig,
})
game.wt = wtServer
// Set handlers
wtServer.OnSession(func(session *server.Session) {
log.Printf("Player connected: %s", session.ID)
game.handlePlayerJoin(session)
})
wtServer.OnDatagramData(func(session *server.Session, data []byte) {
// Handle player position updates (unreliable, fast)
game.handleDatagram(session, data)
})
wtServer.OnStreamData(func(session *server.Session, data []byte) {
// Handle chat messages (reliable)
game.handleStream(session, data)
})
// Start game loop
go game.gameLoop(ctx)
// Start server
go func() {
if err := wtServer.Start(ctx); err != nil {
log.Fatal(err)
}
}()
log.Println("Game server running on https://localhost:4433")
log.Println("Connect via: https://localhost:4433/webtransport")
// Wait for shutdown
sigChan := make(chan os.Signal, 1)
signal.Notify(sigChan, syscall.SIGINT, syscall.SIGTERM)
<-sigChan
log.Println("Shutting down...")
cancel()
}
func (g *GameServer) handlePlayerJoin(session *server.Session) {
player := &Player{
ID: session.ID,
Username: "Player-" + session.ID[:8],
Session: session,
LastSeen: time.Now(),
Position: protocol.PlayerPosition{
PlayerID: session.ID,
X: 0,
Y: 0,
Z: 0,
Timestamp: time.Now().UnixMilli(),
},
}
g.players.Store(session.ID, player)
// Send join message to all players
msg := &protocol.Message{
Type: protocol.TypePlayerJoin,
Payload: []byte(player.Username + " joined"),
}
data, _ := msg.Encode()
g.wt.BroadcastExcept(session.ID, data, false)
}
func (g *GameServer) handleDatagram(session *server.Session, data []byte) {
msg, err := protocol.Decode(data)
if err != nil {
log.Printf("Decode error: %v", err)
return
}
if msg.Type == protocol.TypePlayerPosition {
pos, err := protocol.ParsePlayerPosition(msg)
if err != nil {
return
}
// Update player position
if p, ok := g.players.Load(session.ID); ok {
player := p.(*Player)
player.Position = *pos
player.LastSeen = time.Now()
// Broadcast position to other players (via datagram for low latency)
g.wt.BroadcastExcept(session.ID, data, true)
}
}
}
func (g *GameServer) handleStream(session *server.Session, data []byte) {
msg, err := protocol.Decode(data)
if err != nil {
log.Printf("Decode error: %v", err)
return
}
if msg.Type == protocol.TypeChatMessage {
// Broadcast chat to all players (via stream for reliability)
g.wt.Broadcast(data, false)
}
}
func (g *GameServer) gameLoop(ctx context.Context) {
ticker := time.NewTicker(100 * time.Millisecond)
defer ticker.Stop()
for {
select {
case <-ctx.Done():
return
case <-ticker.C:
// Periodic game state sync
g.syncGameState()
// Clean up inactive players
g.cleanupPlayers()
}
}
}
func (g *GameServer) syncGameState() {
positions := []protocol.PlayerPosition{}
g.players.Range(func(key, value interface{}) bool {
player := value.(*Player)
positions = append(positions, player.Position)
return true
})
state := protocol.GameState{
Players: positions,
Timestamp: time.Now().UnixMilli(),
}
payload, _ := json.Marshal(state)
msg := &protocol.Message{
Type: protocol.TypeGameState,
Payload: payload,
}
data, _ := msg.Encode()
// Send via stream (reliable)
g.wt.Broadcast(data, false)
}
func (g *GameServer) cleanupPlayers() {
timeout := 30 * time.Second
g.players.Range(func(key, value interface{}) bool {
player := value.(*Player)
if time.Since(player.LastSeen) > timeout {
log.Printf("Removing inactive player: %s", player.ID)
g.players.Delete(key)
// Notify others
msg := &protocol.Message{
Type: protocol.TypePlayerLeave,
Payload: []byte(player.Username + " left"),
}
data, _ := msg.Encode()
g.wt.Broadcast(data, false)
}
return true
})
}
5. Web Client (JavaScript)
// web/client.js
class GameClient {
constructor(url) {
this.url = url;
this.transport = null;
this.playerID = null;
this.position = { x: 0, y: 0, z: 0 };
this.otherPlayers = new Map();
}
async connect() {
try {
// Create WebTransport connection
this.transport = new WebTransport(this.url);
await this.transport.ready;
console.log('WebTransport connected');
// Start receiving datagrams
this.receiveDatagrams();
// Start receiving streams
this.receiveStreams();
// Start sending position updates
this.sendPositionUpdates();
return true;
} catch (error) {
console.error('Connection failed:', error);
return false;
}
}
async receiveDatagrams() {
const reader = this.transport.datagrams.readable.getReader();
while (true) {
try {
const { value, done } = await reader.read();
if (done) break;
// Decode and handle datagram
this.handleDatagram(value);
} catch (error) {
console.error('Datagram receive error:', error);
break;
}
}
}
async receiveStreams() {
const reader = this.transport.incomingBidirectionalStreams.getReader();
while (true) {
try {
const { value: stream, done } = await reader.read();
if (done) break;
// Handle stream data
this.handleStream(stream);
} catch (error) {
console.error('Stream receive error:', error);
break;
}
}
}
handleDatagram(data) {
const message = this.decodeMessage(data);
if (message.type === 0x01) {
// Player position update
const position = JSON.parse(new TextDecoder().decode(message.payload));
if (position.player_id !== this.playerID) {
this.otherPlayers.set(position.player_id, position);
this.updatePlayerPosition(position);
}
}
}
async handleStream(stream) {
const reader = stream.readable.getReader();
while (true) {
const { value, done } = await reader.read();
if (done) break;
const message = this.decodeMessage(value);
if (message.type === 0x02) {
// Chat message
const chat = JSON.parse(new TextDecoder().decode(message.payload));
this.displayChatMessage(chat);
} else if (message.type === 0x03) {
// Game state sync
const state = JSON.parse(new TextDecoder().decode(message.payload));
this.syncGameState(state);
}
}
}
sendPositionUpdates() {
// Send position updates via datagrams (unreliable, low-latency)
setInterval(() => {
const position = {
player_id: this.playerID,
x: this.position.x,
y: this.position.y,
z: this.position.z,
rotation: 0,
velocity: 0,
timestamp: Date.now(),
};
const message = this.encodeMessage(0x01, JSON.stringify(position));
const writer = this.transport.datagrams.writable.getWriter();
writer.write(message);
writer.releaseLock();
}, 50); // 20 updates per second
}
async sendChatMessage(text) {
const chat = {
player_id: this.playerID,
username: 'Player-' + this.playerID,
message: text,
timestamp: Date.now(),
};
const message = this.encodeMessage(0x02, JSON.stringify(chat));
// Send via stream (reliable)
const stream = await this.transport.createBidirectionalStream();
const writer = stream.writable.getWriter();
await writer.write(message);
await writer.close();
}
encodeMessage(type, payload) {
const payloadBytes = new TextEncoder().encode(payload);
const buffer = new ArrayBuffer(5 + payloadBytes.length);
const view = new DataView(buffer);
view.setUint8(0, type);
view.setUint32(1, payloadBytes.length);
new Uint8Array(buffer, 5).set(payloadBytes);
return new Uint8Array(buffer);
}
decodeMessage(data) {
const view = new DataView(data.buffer);
const type = view.getUint8(0);
const payloadLen = view.getUint32(1);
const payload = new Uint8Array(data.buffer, 5, payloadLen);
return { type, payload };
}
updatePlayerPosition(position) {
// Update player in 3D scene/canvas
console.log('Player position:', position);
}
displayChatMessage(chat) {
console.log(`${chat.username}: ${chat.message}`);
}
syncGameState(state) {
console.log('Game state synced:', state.players.length, 'players');
}
disconnect() {
this.transport.close();
}
}
// Usage
const client = new GameClient('https://localhost:4433/webtransport');
await client.connect();
// Send chat
client.sendChatMessage('Hello, world!');
// Update position (continuously via input)
setInterval(() => {
client.position.x += Math.random() - 0.5;
client.position.z += Math.random() - 0.5;
}, 100);
Advanced Features
1. Connection Migration
// QUIC automatically handles network changes
// No code needed - works out of the box!
// Client switches from Wi-Fi to cellular -> connection continues
// Server sees same connection ID, no interruption
2. 0-RTT Resumption
// Client configuration for 0-RTT
quicConfig := &quic.Config{
Allow0RTT: true,
}
// Server configuration
server := &webtransport.Server{
H3: http3.Server{
QUICConfig: &quic.Config{
Allow0RTT: true,
},
},
}
// On reconnection, client can send data immediately
// without waiting for handshake
3. Flow Control
// Configure stream-level flow control
quicConfig := &quic.Config{
InitialStreamReceiveWindow: 512 * 1024, // 512 KB
MaxStreamReceiveWindow: 6 * 1024 * 1024, // 6 MB
InitialConnectionReceiveWindow: 1024 * 1024, // 1 MB
MaxConnectionReceiveWindow: 15 * 1024 * 1024, // 15 MB
}
4. Priority Streams
// Open high-priority stream
stream, err := session.wt.OpenStreamSync(ctx)
if err != nil {
return err
}
// Set priority (lower number = higher priority)
// Note: Priority support varies by implementation
Dependencies
// go.mod
module webtransport-service
go 1.21
require (
github.com/quic-go/quic-go v0.40.1
github.com/quic-go/webtransport-go v0.6.0
github.com/google/uuid v1.6.0
)
TLS Certificate Generation
# Generate self-signed certificate for development
openssl req -x509 -newkey rsa:4096 \
-keyout certs/key.pem \
-out certs/cert.pem \
-days 365 -nodes \
-subj "/CN=localhost"
# For production, use Let's Encrypt or proper CA
Best Practices
1. Choose the Right Transport
Use Datagrams for:
- Player positions in games
- Mouse/cursor updates
- Sensor data
- Real-time metrics (lossy acceptable)
Use Streams for:
- Chat messages
- Game state sync
- File transfers
- API requests/responses
2. Error Handling
// Handle connection errors gracefully
func (s *Server) handleSession(ctx context.Context, session *webtransport.Session) {
defer func() {
if r := recover(); r != nil {
log.Printf("Session panic: %v", r)
}
}()
// Monitor connection
go func() {
<-session.Context().Done()
log.Printf("Connection closed: %v", session.Context().Err())
}()
// Handle streams/datagrams
}
3. Performance Optimization
// Reuse buffers
var bufferPool = sync.Pool{
New: func() interface{} {
return make([]byte, 4096)
},
}
func handleDatagram(data []byte) {
buf := bufferPool.Get().([]byte)
defer bufferPool.Put(buf)
// Process data
}
// Batch datagram sends
func batchSend(session *Session, messages [][]byte) {
for _, msg := range messages {
session.SendDatagram(msg)
}
}
4. Monitoring
import "github.com/prometheus/client_golang/prometheus"
var (
datagramsSent = prometheus.NewCounter(prometheus.CounterOpts{
Name: "webtransport_datagrams_sent_total",
Help: "Total datagrams sent",
})
streamsActive = prometheus.NewGauge(prometheus.GaugeOpts{
Name: "webtransport_streams_active",
Help: "Number of active streams",
})
latency = prometheus.NewHistogram(prometheus.HistogramOpts{
Name: "webtransport_latency_seconds",
Help: "WebTransport latency",
Buckets: prometheus.DefBuckets,
})
)
When to Use HTTP/3 & WebTransport
✅ Perfect for:
- Cloud gaming and game streaming
- Multiplayer games requiring low latency
- Video conferencing with multiple streams
- Financial trading platforms
- Real-time collaborative tools
- Live streaming with low latency
- Mobile applications (connection migration)
- Applications needing both reliable and unreliable delivery
❌ Not ideal for:
- Legacy browser support required (HTTP/3 support still growing)
- Simple request-response APIs (use HTTP/2)
- When UDP is blocked (corporate firewalls)
- Static file serving (HTTP/2 sufficient)
- When TLS overhead is concern (QUIC always encrypted)
Conclusion
HTTP/3 and WebTransport represent the cutting edge of web transport technology, solving fundamental limitations of TCP through QUIC’s UDP-based design. By eliminating head-of-line blocking, reducing connection latency, and supporting both reliable streams and unreliable datagrams, these protocols enable new classes of real-time applications that were previously impossible or impractical on the web.
Key takeaways:
- QUIC eliminates TCP head-of-line blocking via independent streams
- 0-RTT enables instant reconnection with cached session data
- Connection migration maintains connections across network changes
- Use datagrams for low-latency, loss-tolerant data
- Use streams for reliable, ordered delivery
- Built-in encryption (TLS 1.3) with no opt-out
HTTP/3 and WebTransport shine when you need ultra-low latency, efficient multiplexing, and mobile-friendly connection handling, making them ideal for the next generation of real-time web applications.
{{series_nav(current_post=12)}}