コンテンツにスキップ

セッションのライフサイクル

セッションの終わり方は、コネクションの切断だけではありません。 このサーバーは、セッションごとに小さな状態を持ちます。 最初のデータグラムを受け取るとクライアントに終了の準備を促し、2 つ目を受け取るとアプリケーションのコードと理由を付けてセッションを閉じます。

コード

main.c
#define WIRED_MAIN
#include "wired.h"
/* App state kept per session, keyed on the session pointer. */
typedef struct {
wired_wt_session* s; /* 0 = free slot */
int datagrams;
} session_state;
static session_state states[4];
static session_state* find(wired_wt_session* s) {
for (usz i = 0; i < sizeof states / sizeof states[0]; i++)
if (states[i].s == s) return &states[i];
return 0;
}
/* Claim a free slot for the new session, starting from zeroed state. */
static void on_session(
void* ctx, wired_wt_session* s, wired_span path, wired_span protocol) {
(void)ctx;
(void)path;
(void)protocol;
session_state* st = find(0);
if (!st) return;
*st = (session_state){.s = s};
wired_log_str("session open\n");
}
/* First datagram: ask the client to wind down. The session stays open, so
* the second datagram still arrives; then close with a code and a reason. */
static void on_datagram(void* ctx, wired_wt_session* s, wired_span data) {
(void)ctx;
(void)data;
session_state* st = find(s);
if (!st) return;
if (++st->datagrams == 1) {
wired_server_wt_drain_session(s);
wired_log_str("drain sent\n");
return;
}
wired_server_wt_close_session(s, 4001, wired_span_cstr("demo done"));
wired_log_str("close sent\n");
}
/* Fires once per session. Release the slot: the next session may get the
* same pointer, and it must not inherit this one's state. */
static void on_session_close(void* ctx, wired_wt_session* s) {
(void)ctx;
session_state* st = find(s);
if (st) st->s = 0;
wired_log_str("session closed, state released\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 = {
.incoming_cpu = -1,
.wt_on_session = on_session,
.wt_on_datagram = on_datagram,
.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"
"errors"
"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
},
}
_, s, err := d.Dial(ctx, os.Args[1]+"/lifecycle", http.Header{})
if err != nil {
panic(err)
}
fmt.Println("session open")
// The first datagram makes the server send WT_DRAIN_SESSION, the second
// makes it close the session.
for _, msg := range []string{"one", "two"} {
if err := s.SendDatagram([]byte(msg)); err != nil {
panic(err)
}
}
// Any session call returns the close once it arrives.
_, err = s.AcceptUniStream(ctx)
var se *webtransport.SessionError
if !errors.As(err, &se) {
panic(err)
}
fmt.Printf("closed by server: code %d, reason %q\n", se.ErrorCode, se.Message)
conn.CloseWithError(0, "")
}

実行結果

Terminal window
$ ./wt-session-lifecycle --port 4433
session open
drain sent
close sent
session closed, state released
conn closed by peer (CONNECTION_CLOSE)
Terminal window
$ go run . https://127.0.0.1:4433
session open
closed by server: code 4001, reason "demo done"
  • アプリの状態は、wired_wt_session のポインターをキーにした小さな表に置きます。 wt_on_session で枠を確保し、wt_on_session_close で解放します。 SDK はセッションの領域を後のコネクションで再利用します。 解放し忘れた枠は、同じポインターに当たった次のクライアントに、終わったセッションの状態を引き継がせてしまいます。
  • wired_server_wt_drain_session(s) は WT_DRAIN_SESSION を送ります。 これは、近いうちに終えるよう求める要求です。 セッションは開いたままなので、2 つ目のデータグラムも届きます。 送るのはセッションごとに 1 回までです。
  • クライアント側の webtransport-go v0.13 には、drain を扱う API がありません。 未知のカプセル(WebTransport の制御用メッセージ)として読み飛ばすため、クライアントの出力には何も現れません。
  • wired_server_wt_close_session(s, 4001, reason) は、そのコードと理由を載せた WT_CLOSE_SESSION を送り、セッションを終えます。 送信はその場では行わずキューに積み、理由の文字列はコピーします。 クライアントには、同じコードと理由を持つ *webtransport.SessionError として届きます。
  • サーバーは、コネクションが閉じる前に session closed をログに出します。 セッションを終えたのがサーバー自身だからです。 クライアントは、そのあとで QUIC コネクションを閉じます。
  • 逆向きもあります。 opt.wt_on_session_draining を登録すると、クライアントが WT_DRAIN_SESSION を送ったことを受け取れます。 クライアントが何度送っても、呼ばれるのはセッションごとに 1 回です。 webtransport-go v0.13 は drain を送れないため、このページでは示していません。