コンテンツにスキップ

WebTransport のストリーム

ストリームで送ったデータは、欠けることなく、送った順序どおりに届きます。 このページのサーバーは、新しいセッションごとに自分で開いたストリームで挨拶を送り、クライアントが開いた双方向ストリームはエコーします。

コード

main.c
#define WIRED_MAIN
#include "wired.h"
/* Greet every new session on a server-opened uni stream. Its bytes must start
* with the WebTransport stream signal naming the session. */
static void on_session(
void* ctx, wired_wt_session* s, wired_span path, wired_span protocol) {
(void)ctx;
(void)protocol;
wired_dprintf(2, "session %.*s\n", WIRED_SPAN_ARG(path));
u8 buf[16];
usz n = wired_wtwire_signal_put(buf, sizeof buf, 0, s->connect_stream_id);
memcpy(buf + n, "welcome", 7);
wired_server_wt_open_uni(s, wired_span_of(buf, n + 7));
}
/* Echo a client-opened bidi stream once it has been fully received. */
static void on_stream_data(
void* ctx, wired_wt_session* s, u64 stream_id, wired_span data, int fin) {
(void)ctx;
if (!fin) return;
wired_dprintf(2, "stream %.*s\n", WIRED_SPAN_ARG(data));
wired_server_wt_stream_reply(s, stream_id, 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_stream_data = on_stream_data;
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.go
package main
import (
"context"
"crypto/tls"
"fmt"
"io"
"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)
uni, err := s.AcceptUniStream(ctx)
if err != nil {
panic(err)
}
welcome, _ := io.ReadAll(uni)
fmt.Printf("uni from server: %q\n", welcome)
str, err := s.OpenStreamSync(ctx)
if err != nil {
panic(err)
}
str.Write([]byte("hello"))
str.Close() // FIN: the server echoes once the stream is complete
echo, _ := io.ReadAll(str)
fmt.Printf("sent %q, echoed %q\n", "hello", echo)
}

実行結果

Terminal window
$ ./wt-streams --port 4433
session /echo
stream hello
conn closed by peer (CONNECTION_CLOSE)
Terminal window
$ go run . https://127.0.0.1:4433
status 200
uni from server: "welcome"
sent "hello", echoed "hello"
  • wired_server_wt_open_uni(s, payload) は、サーバーからクライアントへのストリームを開き、payload を送って閉じます。 ペイロードの先頭には、セッションを示す WebTransport のストリームシグナルが必要です。 wired_wtwire_signal_put が、s->connect_stream_id からこれを書きます。
  • wt_on_stream_data は、クライアントのストリームに届いたバイト列を渡します。 クライアントが書き込みを終えると、fin が 1 になります。 この例は fin を待ち、メッセージ全体が 1 つのチャンクで届いたと仮定します。 小さなメッセージなら、この仮定は成り立ちます。
  • wired_server_wt_stream_reply(s, stream_id, data) は、同じ双方向ストリームに書き戻して閉じます。 プレフィックスは付きません。
  • データグラムと同じく、WebTransport のストリームにも id.max_datagram_frame_size の設定が必要です。