Skip to content

WebTransport datagrams

A WebTransport session can exchange datagrams: small, unordered messages with no retransmission. This server echoes each one back to the client that sent it.

Code

main.c
#define WIRED_MAIN
#include "wired.h"
/* A browser (or any WebTransport client) opened a session on `path`. */
static void on_session(
void* ctx, wired_wt_session* s, wired_span path, wired_span protocol) {
(void)ctx;
(void)s;
(void)protocol;
wired_dprintf(2, "session %.*s\n", WIRED_SPAN_ARG(path));
}
/* Echo every datagram back to the session it came from. */
static void on_datagram(void* ctx, wired_wt_session* s, wired_span data) {
(void)ctx;
wired_server_wt_send_datagram_to(s, data);
}
int wired_main(int argc, char** argv) {
/* The same fixed demo identity as the HTTP/3 hello page. */
static wired_srvboot_demo_keys keys;
wired_srvboot_id id;
wired_srvboot_demo(&id, &keys, 0x50, "guide-wt");
id.max_datagram_frame_size = 65535; /* WebTransport requires DATAGRAM */
wired_srvrun_opt opt = {0};
opt.incoming_cpu = -1;
opt.wt_on_session = on_session;
opt.wt_on_datagram = on_datagram;
u16 port = (u16)wired_cliargs_int(argc, argv, "--port", 4433);
wired_srvrun_handler h = {0};
wired_srvrun_obs obs = {0};
return wired_server_run_opt(port, &id, h, obs, &opt) ? 0 : 1;
}

Client

client.go
package main
import (
"context"
"crypto/tls"
"fmt"
"net/http"
"os"
"github.com/quic-go/quic-go"
"github.com/quic-go/webtransport-go"
)
func main() {
ctx := context.Background()
var conn *quic.Conn // kept so main can close it before exiting
d := webtransport.Transport{
TLSClientConfig: &tls.Config{InsecureSkipVerify: true}, // self-signed demo cert
QUICConfig: &quic.Config{EnableDatagrams: true, EnableStreamResetPartialDelivery: true},
DialAddr: func(ctx context.Context, addr string, t *tls.Config, q *quic.Config) (*quic.Conn, error) {
c, err := quic.DialAddrEarly(ctx, addr, t, q)
conn = c
return c, err
},
}
rsp, s, err := d.Dial(ctx, os.Args[1]+"/echo", http.Header{})
if err != nil {
panic(err)
}
defer conn.CloseWithError(0, "")
fmt.Println("status", rsp.StatusCode)
s.SendDatagram([]byte("ping"))
fmt.Printf("sent %q\n", "ping")
b, err := s.ReceiveDatagram(ctx)
if err != nil {
panic(err)
}
fmt.Printf("recv %q\n", b)
}

Output

Terminal window
$ ./wt-datagram --port 4433
session /echo
conn closed by peer (CONNECTION_CLOSE)
Terminal window
$ go run . https://127.0.0.1:4433
status 200
sent "ping"
recv "ping"
  • wired_server_run_opt is wired_server_run plus callbacks. Set opt.incoming_cpu = -1 (the default for “off”), then register the WebTransport hooks you need.
  • id.max_datagram_frame_size = 65535 advertises QUIC DATAGRAM support. WebTransport clients refuse a server without it, even if you never send one.
  • wt_on_session runs once per accepted session, with its :path.
  • wt_on_datagram hands you each received datagram; data is only valid during the call.
  • wired_server_wt_send_datagram_to(s, data) queues a reply to one session (the payload is copied). To send one datagram to every connected client, as a chat does, use wired_server_broadcast_datagram (one pending payload, a later call replaces it) or wired_server_broadcast_datagram_ring (queued, single process only).
  • The client keeps the QUIC connection and closes it before exiting, so the server logs conn closed by peer right away.