コンテンツにスキップ

WebTransport のセッション

セッションは、Extended CONNECT リクエスト(WebTransport セッションの開始を伝える HTTP/3 のリクエスト)から始まります。 サーバーはリクエストのパス(:path)を見て拒否でき、サブプロトコル(セッション上で使うアプリケーション独自のプロトコル名)を選べ、セッションの終了も検知できます。

コード

main.c
#define WIRED_MAIN
#include "wired.h"
/* Decide per :path, before the session exists. Leaving `out` untouched
* accepts; a non-zero status is sent instead of 200. */
static void on_resource(
void* ctx,
wired_span authority,
wired_span path,
wired_wt_resource_decision* out) {
(void)ctx;
(void)authority;
if (wired_span_eq_cstr(path, "/forbidden")) out->status = 405;
}
static void on_session(
void* ctx, wired_wt_session* s, wired_span path, wired_span protocol) {
(void)ctx;
(void)s;
wired_dprintf(2, "session %.*s\n", WIRED_SPAN_ARG(path));
wired_dprintf(2, "protocol %.*s\n", WIRED_SPAN_ARG(protocol));
}
/* Fires once per session, however it ended. Release per-session state here. */
static void on_session_close(void* ctx, wired_wt_session* s) {
(void)ctx;
(void)s;
wired_log_str("session closed\n");
}
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_resource_check = on_resource;
opt.wt_protocols = "chat"; /* space-separated list we accept */
opt.wt_on_session = on_session;
opt.wt_on_session_close = on_session_close;
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"
"net/http"
"os"
"github.com/quic-go/quic-go"
"github.com/quic-go/webtransport-go"
)
func main() {
ctx := context.Background()
var conn *quic.Conn // the latest connection, kept so main can close it
d := webtransport.Transport{
TLSClientConfig: &tls.Config{InsecureSkipVerify: true}, // self-signed demo cert
QUICConfig: &quic.Config{EnableDatagrams: true, EnableStreamResetPartialDelivery: true},
ApplicationProtocols: []string{"chat"},
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, _, err := d.Dial(ctx, os.Args[1]+"/forbidden", http.Header{})
if err == nil {
panic("/forbidden was accepted")
}
fmt.Println("/forbidden:", rsp.StatusCode)
rsp, s, err := d.Dial(ctx, os.Args[1]+"/chat", http.Header{})
if err != nil {
panic(err)
}
fmt.Printf("/chat: %d protocol %q\n", rsp.StatusCode, s.SessionState().ApplicationProtocol)
conn.CloseWithError(0, "") // ending the connection ends its session too
fmt.Println("closed /chat")
}

実行結果

Terminal window
$ ./wt-session --port 4433
conn closed by peer (CONNECTION_CLOSE)
session /chat
protocol chat
conn closed by peer (CONNECTION_CLOSE)
session closed
Terminal window
$ go run . https://127.0.0.1:4433
/forbidden: 405
/chat: 200 protocol "chat"
closed /chat
  • wt_resource_check は、セッションができる前に呼ばれます。 受け入れるなら out に触れずに返します。 拒否するなら out->status(ここでは 405)を設定すると、セッションの代わりにそのステータスが送られます。 拒否したリクエストで wt_on_session が呼ばれることはありません。
  • opt.wt_protocols は、サーバーが対応するサブプロトコルを空白区切りで並べたものです。 そのうちクライアントも提示したものが、wt_on_session の protocol に渡されます。
  • wt_on_session_close は、セッションがどう終わっても、セッションごとにちょうど 1 回呼ばれます。 終わり方には、クライアントが閉じた場合、サーバーが wired_server_wt_close_session で閉じた場合、コネクションがなくなった場合があります。 セッションのポインターをキーにした状態は、ここで解放します。 セッションの領域は再利用されるからです。
  • 出力の最初の conn closed by peer は、拒否された /forbidden のコネクションが閉じたときのログです。
  • クライアントは QUIC コネクションを閉じて /chat のセッションを終えます。 そのため、サーバーはまずコネクションの切断をログに出し、そのあとに session closed を出します。