Keeping a server-opened stream open
The streams page’s wired_server_wt_open_uni sends its
whole payload and closes with FIN right away. wired_server_wt_open_uni_stream
opens the same kind of stream but leaves it open, so the app can append
more rounds later with wired_server_wt_stream_send and decide the FIN
round itself.
Code
#define WIRED_MAIN#include "wired.h"
/* The one server-opened uni stream this single-session demo keeps open * across several rounds, appended to from on_stream_data below. */static i64 g_uni = -1;
/* Open it on connect, WITHOUT closing: wired_server_wt_open_uni_stream * (unlike wired_server_wt_open_uni) leaves the stream open for further * wired_server_wt_stream_send rounds. */static void on_session( void* ctx, wired_wt_session* s, wired_span path, wired_span protocol) { (void)ctx; (void)path; (void)protocol; u8 buf[16]; usz n = wired_wtwire_signal_put(buf, sizeof buf, 0, s->connect_stream_id); memcpy(buf + n, "chunk1 ", 7); g_uni = wired_server_wt_open_uni_stream(s, wired_span_of(buf, n + 7));}
/* The client drives the open stream over its own bidi requests: "more" * appends a round (fin=0, stays open), "bye" appends the closing round * (fin=1). Each request gets a short reply once applied. */static void on_stream_data( void* ctx, wired_wt_session* s, u64 stream_id, wired_span data, int fin) { (void)ctx; if (!fin) return; if (wired_span_eq_cstr(data, "more")) wired_server_wt_stream_send(s, (u64)g_uni, wired_span_cstr("chunk2 "), 0); else if (wired_span_eq_cstr(data, "bye")) wired_server_wt_stream_send(s, (u64)g_uni, wired_span_cstr("chunk3"), 1); wired_server_wt_stream_reply(s, stream_id, wired_span_cstr("ok"));}
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
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}, 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]+"/open", http.Header{}) if err != nil { panic(err) } defer conn.CloseWithError(0, "")
uni, err := s.AcceptUniStream(ctx) if err != nil { panic(err) } done := make(chan []byte, 1) go func() { b, _ := io.ReadAll(uni) done <- b }()
// Each word drives the server-opened stream one round further, and we // wait for its ack before sending the next -- so "bye" only reaches the // server after "more" was fully applied. for _, word := range []string{"more", "bye"} { str, err := s.OpenStreamSync(ctx) if err != nil { panic(err) } str.Write([]byte(word)) str.Close() ack, _ := io.ReadAll(str) fmt.Printf("%s: %q\n", word, ack) }
fmt.Printf("uni stream: %q\n", <-done)}Output
$ ./wt-open-streams --port 4433conn closed by peer (CONNECTION_CLOSE)$ go run . https://127.0.0.1:4433more: "ok"bye: "ok"uni stream: "chunk1 chunk2 chunk3"How it works
Section titled “How it works”on_sessionopens the uni stream withwired_server_wt_open_uni_streamand keeps its id ing_uni–finis never set on this call, so the stream stays open past the first round.- Each client request appends one round:
wired_server_wt_stream_send(s, g_uni, payload, fin)withfin=0keeps it open for more,fin=1ends it after this round’s bytes. - The client’s uni-stream read (
io.ReadAll) only returns once the server sends thatfin=1round – until then it blocks, same as any stream read waiting on data.