WebTransport のセッション
セッションは、Extended CONNECT リクエスト(WebTransport セッションの開始を伝える HTTP/3 のリクエスト)から始まります。
サーバーはリクエストのパス(:path)を見て拒否でき、サブプロトコル(セッション上で使うアプリケーション独自のプロトコル名)を選べ、セッションの終了も検知できます。
コード
#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;}クライアント
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")}実行結果
$ ./wt-session --port 4433conn closed by peer (CONNECTION_CLOSE)session /chatprotocol chatconn closed by peer (CONNECTION_CLOSE)session closed$ go run . https://127.0.0.1:4433/forbidden: 405/chat: 200 protocol "chat"closed /chatwt_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を出します。