@@ -2862,6 +2862,159 @@ added: REPLACEME
28622862Called when informational (1xx) headers are received from the server
28632863(e.g., 103 Early Hints).
28642864
2865+ ## HTTP/3 support
2866+
2867+ <!-- YAML
2868+ added: REPLACEME
2869+ -->
2870+
2871+ When the negotiated ALPN identifier is ` 'h3' ` (or one of the ` 'h3-*' `
2872+ draft variants), the QUIC session runs the HTTP/3 application backed
2873+ by ` nghttp3 ` . ` 'h3' ` is the default ALPN for ` quic.connect() ` and
2874+ ` quic.listen() ` , so HTTP/3 is what you get unless you select a
2875+ different ALPN explicitly.
2876+
2877+ Selecting the HTTP/3 application enables a number of stream- and
2878+ session-level capabilities that are not available to non-HTTP/3
2879+ applications:
2880+
2881+ * ** Headers and trailers** — request and response header blocks
2882+ (including pseudo-headers such as ` :method ` , ` :path ` , ` :scheme ` ,
2883+ ` :authority ` , and ` :status ` ), trailing headers, and informational
2884+ (` 1xx ` ) responses. See [ ` stream.sendHeaders() ` ] [ ] ,
2885+ [ ` stream.sendTrailers() ` ] [ ] , and
2886+ [ ` stream.sendInformationalHeaders() ` ] [ ] .
2887+ * ** Stream priority (RFC 9218)** — per-stream urgency and
2888+ incremental flags. See [ ` stream.priority ` ] [ ] and
2889+ [ ` stream.setPriority() ` ] [ ] .
2890+ * ** HTTP/3 datagrams (RFC 9297)** — unreliable application-layer
2891+ datagrams. The peer must advertise ` SETTINGS_H3_DATAGRAM=1 ` , which
2892+ is enabled by setting [ ` application.enableDatagrams ` ] [ ] to ` true `
2893+ on both peers. See [ ` session.sendDatagram() ` ] [ ] and
2894+ [ ` session.ondatagram ` ] [ ] .
2895+ * ** ORIGIN frame (RFC 9412)** — servers automatically advertise the
2896+ hostnames in their [ ` sessionOptions.sni ` ] [ ] map (entries with
2897+ ` authoritative: true ` ); clients receive the list via
2898+ [ ` session.onorigin ` ] [ ] .
2899+ * ** GOAWAY** — graceful shutdown. The server emits ` GOAWAY ` as part
2900+ of [ ` session.close() ` ] [ ] ; the client observes it via
2901+ [ ` session.ongoaway ` ] [ ] and stops opening new bidirectional streams.
2902+ * ** Extended CONNECT settings (RFC 9220)** — the
2903+ ` SETTINGS_ENABLE_CONNECT_PROTOCOL ` setting can be enabled via
2904+ [ ` application.enableConnectProtocol ` ] [ ] . The setting is exchanged
2905+ but the application is responsible for handling the ` :protocol `
2906+ pseudo-header and any payload framing on top.
2907+ * ** QPACK tuning** — dynamic-table size and blocked-streams limits
2908+ via [ ` application.qpackMaxDTableCapacity ` ] [ ] and friends.
2909+
2910+ ### Minimal HTTP/3 client
2911+
2912+ ``` mjs
2913+ import { connect } from ' node:quic' ;
2914+ import process from ' node:process' ;
2915+
2916+ const session = await connect (' example.com:443' , {
2917+ // ALPN defaults to 'h3'.
2918+ servername: ' example.com' ,
2919+ });
2920+ await session .opened ;
2921+
2922+ const stream = await session .createBidirectionalStream ({
2923+ headers: {
2924+ ' :method' : ' GET' ,
2925+ ' :path' : ' /' ,
2926+ ' :scheme' : ' https' ,
2927+ ' :authority' : ' example.com' ,
2928+ },
2929+ onheaders (headers ) {
2930+ console .log (' status:' , headers[' :status' ]);
2931+ },
2932+ });
2933+
2934+ const decoder = new TextDecoder ();
2935+ for await (const chunk of stream ) {
2936+ process .stdout .write (decoder .decode (chunk, { stream: true }));
2937+ }
2938+
2939+ await session .close ();
2940+ ```
2941+
2942+ A few things to note:
2943+
2944+ * ` session.createBidirectionalStream({ headers }) ` automatically
2945+ marks the HEADERS frame as terminal when no ` body ` is provided —
2946+ the request is ` HEADERS ` followed by ` END_STREAM ` .
2947+ * The ` onheaders ` callback receives the response pseudo-headers and
2948+ regular headers in a single object with lowercase string keys.
2949+ After the callback returns, the same object is also accessible
2950+ via [ ` stream.headers ` ] [ ] .
2951+ * Reading ` for await (const chunk of stream) ` consumes the response
2952+ body as ` Uint8Array ` chunks.
2953+ * HTTP semantic helpers (URL parsing, method/status validation,
2954+ redirects, content negotiation, and so on) are intentionally not
2955+ built in. The caller is responsible for any HTTP-level handling
2956+ beyond the wire framing.
2957+
2958+ ### Minimal HTTP/3 server
2959+
2960+ ``` mjs
2961+ import { listen } from ' node:quic' ;
2962+
2963+ const encoder = new TextEncoder ();
2964+
2965+ const endpoint = await listen ((session ) => {
2966+ // The session.onstream callback fires for each new client-initiated stream.
2967+ }, {
2968+ sni: { ' *' : { keys: [defaultKey], certs: [defaultCert] } },
2969+ // ALPN defaults to 'h3'.
2970+ onheaders (headers ) {
2971+ // `this` is the QuicStream. Pseudo-headers are available on the
2972+ // request header block (`:method`, `:path`, `:scheme`,
2973+ // `:authority`).
2974+ if (headers[' :path' ] === ' /health' ) {
2975+ this .sendHeaders ({ ' :status' : ' 200' , ' content-type' : ' text/plain' });
2976+ const w = this .writer ;
2977+ w .writeSync (encoder .encode (' ok\n ' ));
2978+ w .endSync ();
2979+ } else {
2980+ this .sendHeaders ({ ' :status' : ' 404' }, { terminal: true });
2981+ }
2982+ },
2983+ });
2984+
2985+ console .log (' listening on' , endpoint .address );
2986+ ```
2987+
2988+ Server-side notes:
2989+
2990+ * Setting ` onheaders ` at the [ ` listen() ` ] [ `quic.listen()` ] level
2991+ applies it to every incoming stream (it is wired up before
2992+ ` onstream ` fires). Setting it inside ` onstream ` is too late for
2993+ HTTP/3, where the request HEADERS frame is the first thing that
2994+ arrives on the stream.
2995+ * ` this.sendHeaders(headers, { terminal: true }) ` marks the
2996+ response HEADERS frame as terminal (no body follows).
2997+ * For body responses, send headers first, then write to
2998+ ` this.writer ` and call ` endSync() ` to send the body and close the
2999+ stream cleanly.
3000+
3001+ ### What is not implemented
3002+
3003+ * ** Server push** — ` PUSH_PROMISE ` and the related push-stream
3004+ machinery are not implemented and are not on the near-term
3005+ roadmap. Server push has limited deployment in practice, and most
3006+ use cases are better served by Early Hints (` 103 ` ) or by direct
3007+ fetches from the client.
3008+ * ** WebTransport / extended-CONNECT helpers** — the
3009+ ` SETTINGS_ENABLE_CONNECT_PROTOCOL ` setting can be negotiated but
3010+ there is no built-in support for the ` :protocol ` pseudo-header,
3011+ WebTransport datagram demultiplexing, or capsule framing.
3012+ * ** Higher-level HTTP semantics** — there is no built-in
3013+ request/response router, URL parsing, content-encoding
3014+ negotiation, body-type coercion, redirect following, or
3015+ cookie handling. These are deliberately left to higher-level
3016+ libraries built on top of ` node:quic ` .
3017+
28653018## Performance measurement
28663019
28673020<!-- YAML
@@ -3343,6 +3496,9 @@ throughput issues caused by flow control.
33433496[`PerformanceEntry`]: perf_hooks.md#class-performanceentry
33443497[`PerformanceObserver`]: perf_hooks.md#class-performanceobserver
33453498[`QuicError`]: #class-quicerror
3499+ [`application.enableConnectProtocol`]: #sessionoptionsapplication
3500+ [`application.enableDatagrams`]: #sessionoptionsapplication
3501+ [`application.qpackMaxDTableCapacity`]: #sessionoptionsapplication
33463502[`endpoint.maxConnectionsPerHost`]: #endpointmaxconnectionsperhost
33473503[`endpoint.maxConnectionsTotal`]: #endpointmaxconnectionstotal
33483504[`error.errorCode`]: #errorerrorcode
@@ -3352,18 +3508,26 @@ throughput issues caused by flow control.
33523508[`session.close()`]: #sessioncloseoptions
33533509[`session.destroy()`]: #sessiondestroyerror-options
33543510[`session.maxPendingDatagrams`]: #sessionmaxpendingdatagrams
3511+ [`session.ondatagram`]: #sessionondatagram
33553512[`session.onerror`]: #sessiononerror
3513+ [`session.ongoaway`]: #sessionongoaway
33563514[`session.onkeylog`]: #sessiononkeylog
33573515[`session.onnewtoken`]: #sessiononnewtoken
3516+ [`session.onorigin`]: #sessiononorigin
33583517[`session.onqlog`]: #sessiononqlog
3518+ [`session.sendDatagram()`]: #sessionsenddatagramdatagram-encoding
33593519[`sessionOptions.datagramDropPolicy`]: #sessionoptionsdatagramdroppolicy
33603520[`sessionOptions.keylog`]: #sessionoptionskeylog
33613521[`sessionOptions.qlog`]: #sessionoptionsqlog
33623522[`sessionOptions.sni`]: #sessionoptionssni-server-only
33633523[`stream.destroy()`]: #streamdestroyerror-options
3524+ [`stream.headers`]: #streamheaders
33643525[`stream.onerror`]: #streamonerror
33653526[`stream.onwanttrailers`]: #streamonwanttrailers
33663527[`stream.pendingTrailers`]: #streampendingtrailers
3528+ [`stream.priority`]: #streampriority
3529+ [`stream.sendHeaders()`]: #streamsendheadersheaders-options
3530+ [`stream.sendInformationalHeaders()`]: #streamsendinformationalheadersheaders
33673531[`stream.sendTrailers()`]: #streamsendtrailersheaders
33683532[`stream.setBody()`]: #streamsetbodybody
33693533[`stream.setPriority()`]: #streamsetpriorityoptions
0 commit comments