quico 0.4.0 → 0.8.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/src/server.js CHANGED
@@ -14,12 +14,21 @@
14
14
 
15
15
  import { EventEmitter } from 'node:events';
16
16
  import fs from 'node:fs';
17
- import http2 from 'node:http2';
18
- import nodeHttps from 'node:https';
19
- import nodeTls from 'node:tls';
17
+ import lemonTls from 'lemon-tls';
18
+ import crypto from 'node:crypto';
20
19
 
21
20
  import { createServer as createH3Server } from './h3_server.js';
22
- import { IncomingMessage, ServerResponse, _makeSocketStub } from './streams.js';
21
+ import { createServer as createH2Server } from './h2_server.js';
22
+ import { createServer as createH1Server } from './h1_server.js';
23
+ import { createTcpListener } from './tcp_listener.js';
24
+ import { createSecureContext } from './tls_bridge.js';
25
+ import { IncomingMessage, ServerResponse, _makeSocketStub, adoptApp } from './streams.js';
26
+ import { creditAsRead } from './utils.js';
27
+ import { runEarlyDataFilter } from './http_common.js';
28
+ import { serverTlsOptions, pickTlsOptions } from './tls_options.js';
29
+ import { STATUS_CODES } from 'node:http';
30
+ import { ServerStats } from './server_stats.js';
31
+ import { createConnectUdp, isConnectUdp, datagramCapsule, STREAM_QUEUE_MAX } from './connect_udp.js';
23
32
 
24
33
 
25
34
  // ============================================================
@@ -29,6 +38,10 @@ import { IncomingMessage, ServerResponse, _makeSocketStub } from './streams.js';
29
38
  // Options:
30
39
  // http2: true (default) — TCP with node:http2
31
40
  // http1: true (default) — allowHTTP1 on TCP (or standalone if http2:false)
41
+ // ALPNProtocols — the node:tls option: ALPN names to accept, in
42
+ // preference order. Applies to TCP and QUIC,
43
+ // each taking the names that belong to it — see
44
+ // tcpAlpnList() and quicAlpnList().
32
45
  //
33
46
  // Combinations:
34
47
  // { } → H3 + H2 + H1 (default)
@@ -37,22 +50,150 @@ import { IncomingMessage, ServerResponse, _makeSocketStub } from './streams.js';
37
50
  // { http2: false, http1: false } → H3 only
38
51
  // ============================================================
39
52
 
53
+ // ============================================================
54
+ // ALPN on TCP
55
+ //
56
+ // The http2 / http1 flags decide which HTTP versions run. ALPNProtocols —
57
+ // the node:tls option, same name and same shape — adds further names and
58
+ // sets the order in which they are preferred.
59
+ //
60
+ // An HTTP protocol that is being served but missing from the list is put in
61
+ // front of it rather than dropped. The list is the node:tls one, and code
62
+ // written against node:tls would name only what it adds; silently turning off
63
+ // HTTP/2 because a caller wrote ['acme-tls/1'] would be a failure nobody sees
64
+ // — browsers just fall back, a little slower. Turning a version off is what
65
+ // the flags are for.
66
+ //
67
+ // 'h3' is skipped here: it exists only over QUIC. A name the flags have
68
+ // disabled is an error rather than an omission, because it can only mean the
69
+ // two options disagree about what the server should do.
70
+ // ============================================================
71
+
72
+ function tcpAlpnList(requested, h2, h1) {
73
+ var served = [];
74
+ if (h2) served.push('h2');
75
+ if (h1) served.push('http/1.1');
76
+ if (requested === undefined || requested === null) return served;
77
+
78
+ if (!Array.isArray(requested)) throw new TypeError('ALPNProtocols must be an array of strings');
79
+
80
+ var listed = [];
81
+ var seen = {};
82
+ for (var i = 0; i < requested.length; i++) {
83
+ var name = requested[i];
84
+ if (typeof name !== 'string' || name.length === 0) {
85
+ throw new TypeError('ALPNProtocols entries must be non-empty strings');
86
+ }
87
+ if (name === 'h3') continue;
88
+ if (name === 'h2' && !h2) throw new Error("ALPNProtocols lists 'h2' but http2 is false");
89
+ if (name === 'http/1.1' && !h1) throw new Error("ALPNProtocols lists 'http/1.1' but http1 is false");
90
+ if (seen[name]) continue;
91
+ seen[name] = true;
92
+ listed.push(name);
93
+ }
94
+
95
+ var missing = [];
96
+ for (var j = 0; j < served.length; j++) if (!seen[served[j]]) missing.push(served[j]);
97
+ return missing.concat(listed);
98
+ }
99
+
100
+ // The QUIC side of the same list, by the same rule as TCP: 'h3' is always
101
+ // served — HTTP/3 is what quico is for — so it goes first when missing, and
102
+ // keeps the position it was given when listed. 'h2' and 'http/1.1' are
103
+ // skipped: they exist only over TCP.
104
+ function quicAlpnList(requested) {
105
+ if (requested === undefined || requested === null) return ['h3'];
106
+ var out = [];
107
+ for (var i = 0; i < requested.length; i++) {
108
+ var name = requested[i];
109
+ if (name === 'h2' || name === 'http/1.1') continue;
110
+ if (out.indexOf(name) === -1) out.push(name);
111
+ }
112
+ if (out.indexOf('h3') === -1) out.unshift('h3');
113
+ return out;
114
+ }
115
+
116
+ // A name accepted in the handshake that quico does not speak. The application
117
+ // asked only that the handshake agree to it — ACME tls-alpn-01 is the case in
118
+ // point: the CA checks the certificate the handshake presented, sends nothing
119
+ // afterwards, and closes. Ending our side too keeps the socket from lingering
120
+ // if a client does not.
121
+ function closeAfterHandshake(socket) {
122
+ try { socket.end(); } catch (e) {}
123
+ }
124
+
125
+
40
126
  function createServer(options, handler) {
41
127
  if (typeof options === 'function') { handler = options; options = {}; }
42
128
  options = options || {};
129
+ // An Express app runs on quico's req/res only once its request/response
130
+ // objects inherit from them — see adoptApp in streams.js.
131
+ adoptApp(handler);
43
132
 
44
133
  var enableH2 = options.http2 !== false;
45
134
  var enableH1 = options.http1 !== false;
46
135
  var needsTcp = enableH2 || enableH1;
47
136
 
137
+ // http3: false — run no HTTP/3 on this server.
138
+ //
139
+ // listen() used to start the QUIC listener unconditionally, so every
140
+ // server bound a UDP port alongside its TCP one. That is wrong whenever
141
+ // HTTP/3 is served somewhere else on the same port — a gateway that owns
142
+ // UDP 443 and hands QUIC packets to a dedicated server — because a
143
+ // TCP-only server then competes for the same UDP port. The loser's bind
144
+ // failed, and since nothing forwarded that error (see below), its listen()
145
+ // callback simply never fired. The failure was silent.
146
+ //
147
+ // Alt-Svc is still sent when this is off. The usual reason to disable
148
+ // HTTP/3 here is that it is served by another server on the same port, and
149
+ // Alt-Svc is how a browser on TCP finds it.
150
+ var enableH3 = options.http3 !== false;
151
+ // node:tls's TLS options, checked now so a bad one throws from
152
+ // createServer — for HTTP/3 too when it runs (TLS 1.3 only).
153
+ serverTlsOptions(options, enableH3);
154
+
155
+ // Computed here rather than in listen(), so a contradictory list throws
156
+ // from createServer — at the line that wrote it — and not later from a
157
+ // listen() call somewhere else.
158
+ var tcpAlpn = tcpAlpnList(options.ALPNProtocols, enableH2, enableH1);
159
+ var quicAlpn = quicAlpnList(options.ALPNProtocols);
160
+
48
161
  var ev = new EventEmitter();
49
162
  var port = null;
163
+ var listening = false; // node:net's server.listening
164
+ var closed = false; // close() called (a second one is an error)
165
+ var ref = true; // ref() / unref()
50
166
 
51
167
  // ---- TLS credentials ----
52
168
  var tlsKey = options.key || null;
53
169
  var tlsCert = options.cert || null;
54
170
  var tlsCa = options.ca || null;
55
171
 
172
+ // Session tickets: one key for this server, TCP and QUIC alike — random
173
+ // unless the application passes ticketKeys it keeps across restarts. And
174
+ // the application's ticketData, carried inside each ticket (lemon-tls).
175
+ var ticketKeys = options.ticketKeys || crypto.randomBytes(48);
176
+ var ticketData = typeof options.ticketData === 'function' ? options.ticketData : null;
177
+ // Encrypted Client Hello keys (RFC 9849), TCP and QUIC alike:
178
+ // { keys: [{ config, privateKey, retry }] } — see lemon-tls generateEchKey.
179
+ // Checked now: a key that does not match its config would otherwise show
180
+ // only as every ECH client falling back.
181
+ var ech = options.ech || null;
182
+ if (ech) lemonTls.checkEchKeys(ech);
183
+
184
+ // OCSP stapling, node:tls's way: server.on('OCSPRequest', (certificate,
185
+ // issuer, callback) => callback(null, ocspResponse)) — asked when a client
186
+ // requests status, TCP and QUIC alike. Without a listener nothing is
187
+ // stapled and nothing waits.
188
+ function ocspRequest(certificate, issuer, callback) {
189
+ if (ev.listenerCount('OCSPRequest') === 0) return callback(null, null);
190
+ ev.emit('OCSPRequest', Buffer.from(certificate), issuer ? Buffer.from(issuer) : null, callback);
191
+ }
192
+ // node:tls's TLS options — client certificates, versions, ciphers, curves,
193
+ // signature schemes, the ticket lifetime, the handshake timeout — passed to
194
+ // both stacks (tls_options.js), each reading them for itself.
195
+ var tlsPassOn = pickTlsOptions(options);
196
+
56
197
  // Read from disk if paths were given (not PEM strings)
57
198
  if (typeof tlsKey === 'string' && tlsKey.indexOf('-----') === -1) {
58
199
  tlsKey = fs.readFileSync(tlsKey);
@@ -61,6 +202,58 @@ function createServer(options, handler) {
61
202
  tlsCert = fs.readFileSync(tlsCert);
62
203
  }
63
204
 
205
+ // ---- The certificate, chosen per connection — TCP and QUIC alike ----
206
+ //
207
+ // One resolver for both stacks, so that what node:tls's server offers to
208
+ // change while running applies to every version at once:
209
+ // setSecureContext(options) the default certificate
210
+ // addContext(hostname, ctx) a certificate for a name (SNI; *.x too)
211
+ // From the next handshake on; connections open keep what they have, as
212
+ // in Node. The application's SNICallback, if it gave one, decides first
213
+ // (Node ignores addContext then, as here); when it answers with nothing,
214
+ // the default is used.
215
+ var defaultContext = null;
216
+ var sniContexts = []; // [regex, context], the latest added first matched
217
+ var userSni = typeof options.SNICallback === 'function' ? options.SNICallback : null;
218
+ function toContext(o, what) {
219
+ if (o && typeof o === 'object' && o.certificateChain && o.privateKey) return o; // made already (quico.createSecureContext)
220
+ if (!o || typeof o !== 'object') throw new TypeError(what + ' takes { key, cert[, ca, passphrase] } or a context from quico.createSecureContext()');
221
+ var ctx = createSecureContext(Object.assign({ passphrase: options.passphrase }, o));
222
+ checkKeyMatches(ctx);
223
+ // Its chain compressed now, not in a client's handshake (RFC 8879).
224
+ if (typeof lemonTls.precompressCertificate === 'function' && !(Array.isArray(options.certificateCompression) && options.certificateCompression.length === 0)) {
225
+ try { lemonTls.precompressCertificate(ctx, options.certificateCompression || undefined); } catch (e) {}
226
+ }
227
+ return ctx;
228
+ }
229
+ // A certificate whose key is another's is refused here — before it
230
+ // replaces anything — not found out by every client's handshake failing.
231
+ function checkKeyMatches(ctx) {
232
+ if (!ctx.keyObj || !ctx.certObjs || !ctx.certObjs[0]) return;
233
+ var a = crypto.createPublicKey(ctx.keyObj).export({ type: 'spki', format: 'der' });
234
+ var b = ctx.certObjs[0].publicKey.export({ type: 'spki', format: 'der' });
235
+ if (Buffer.compare(Buffer.from(a), Buffer.from(b)) !== 0) {
236
+ var e = new Error('the private key does not match the certificate');
237
+ e.code = 'ERR_OSSL_X509_KEY_VALUES_MISMATCH';
238
+ throw e;
239
+ }
240
+ }
241
+ function contextFor(servername) {
242
+ var name = String(servername || '');
243
+ for (var i = sniContexts.length - 1; i >= 0; i--) if (sniContexts[i][0].test(name)) return sniContexts[i][1];
244
+ return defaultContext;
245
+ }
246
+ function serverSni(servername, cb, info) {
247
+ if (userSni) {
248
+ return userSni(servername, function (err, ctx) {
249
+ if (err) cb(err);
250
+ else cb(null, ctx || defaultContext);
251
+ }, info);
252
+ }
253
+ cb(null, contextFor(servername));
254
+ }
255
+ if (tlsKey && tlsCert) defaultContext = toContext({ key: tlsKey, cert: tlsCert, ca: tlsCa }, 'createServer');
256
+
64
257
 
65
258
  // ============================================================
66
259
  // HTTP/3 — delegates to existing h3_server.js
@@ -68,25 +261,191 @@ function createServer(options, handler) {
68
261
  // plain req/res objects into proper Node.js streams.
69
262
  // ============================================================
70
263
 
264
+ // node:http's shouldUpgradeCallback(request): whether an upgrade request —
265
+ // HTTP/1.1 Upgrade, or extended CONNECT over HTTP/2 and HTTP/3 — reaches
266
+ // 'upgrade' or is handled as an ordinary 'request'. Node's default: taken up
267
+ // when the application listens for 'upgrade'. Decided here, for every
268
+ // version, because the layers below always listen (to forward the event
269
+ // up): their own "nobody listens" check could never fire, and an upgrade
270
+ // with no listener was left hanging.
271
+ var userShouldUpgrade = typeof options.shouldUpgradeCallback === 'function' ? options.shouldUpgradeCallback : null;
272
+ function shouldUpgradeCallback(req) {
273
+ if (wantsConnectUdp(req)) return true;
274
+ return userShouldUpgrade ? !!userShouldUpgrade(req) : ev.listenerCount('upgrade') > 0;
275
+ }
276
+
277
+ // ---- CONNECT-UDP (RFC 9298): proxying UDP, on every version ----
278
+ //
279
+ // server.on('connectUdp', (req, tunnel) => tunnel.accept()) — see
280
+ // connect_udp.js. Off until something listens: a request for it then goes
281
+ // the way it always did (to 'upgrade' or the request handler), and none is
282
+ // proxied. options.connectUdp: { template, allowPrivate,
283
+ // maxTunnelsPerConnection, idleTimeout, lookup, proxyName }.
284
+ var udpProxy = createConnectUdp(options.connectUdp || {});
285
+ function wantsConnectUdp(req) { return ev.listenerCount('connectUdp') > 0 && isConnectUdp(req); }
286
+ function emitConnectUdp(tunnel) {
287
+ if (ev.listenerCount('connectUdp') === 0) return false;
288
+ ev.emit('connectUdp', tunnel.req, tunnel);
289
+ return true;
290
+ }
291
+
292
+ // HTTP/2's and HTTP/1.1's carrier: capsules on a byte stream — the
293
+ // extended CONNECT's Duplex (DATA frames), or the upgraded socket. Past
294
+ // STREAM_QUEUE_MAX waiting to be written, a payload is dropped, as a full
295
+ // UDP queue drops.
296
+ function byteStreamCarrier(req, stream, version, head, accept, reject) {
297
+ var closed = false;
298
+ var carrier = {
299
+ httpVersion: version,
300
+ connKey: req.socket || stream,
301
+ sink: null,
302
+ mode: function () { return 'capsule'; },
303
+ accept: function (h) { if (!closed) accept(h); },
304
+ reject: function (status, h) { if (closed) return; closed = true; reject(status, h); },
305
+ sendPayload: function (u8) {
306
+ if (closed || stream.destroyed || stream.writableLength > STREAM_QUEUE_MAX) return false;
307
+ stream.write(datagramCapsule(u8));
308
+ return true;
309
+ },
310
+ end: function () { if (closed) return; closed = true; try { stream.end(); } catch (e) {} },
311
+ abort: function () { if (closed) return; closed = true; try { stream.destroy(); } catch (e) {} }
312
+ };
313
+ stream.on('error', function () {});
314
+ udpProxy.handle(req, carrier, emitConnectUdp);
315
+ if (!carrier.sink) return;
316
+ stream.on('data', function (d) { carrier.sink.data(d); });
317
+ stream.on('end', function () { carrier.sink.end(); });
318
+ stream.on('close', function () { carrier.sink.abort(); });
319
+ if (head && head.length) carrier.sink.data(head);
320
+ }
321
+ function fieldLines(h) {
322
+ var out = '';
323
+ for (var n in (h || {})) out += n + ': ' + String(h[n]).replace(/[\r\n]/g, '') + '\r\n';
324
+ return out;
325
+ }
326
+ function connectUdpH2(req, stream) {
327
+ byteStreamCarrier(req, stream, '2.0', null,
328
+ function (h) { stream._h2Accept(h); },
329
+ function (status, h) { stream._h2Reject(status, null, h); });
330
+ }
331
+ // HTTP/1.1: a GET with Upgrade: connect-udp, answered 101 (§3.2).
332
+ function connectUdpH1(req, socket, head) {
333
+ byteStreamCarrier(req, socket, '1.1', head,
334
+ function (h) {
335
+ socket.write('HTTP/1.1 101 Switching Protocols\r\nConnection: Upgrade\r\nUpgrade: connect-udp\r\n' + fieldLines(h) + '\r\n');
336
+ },
337
+ function (status, h) {
338
+ try {
339
+ socket.end('HTTP/1.1 ' + status + ' ' + (STATUS_CODES[status] || 'Error') + '\r\nConnection: close\r\nContent-Length: 0\r\n' + fieldLines(h) + '\r\n');
340
+ } catch (e) {}
341
+ });
342
+ }
343
+
344
+ // The server's counters (server_stats.js): each layer counts into them
345
+ // where things happen; getStats() copies them out.
346
+ var stats = new ServerStats();
347
+
71
348
  var h3Options = {
349
+ stats: stats,
350
+ shouldUpgradeCallback: shouldUpgradeCallback,
351
+ // CONNECT-UDP over HTTP/3: h3_server builds the carrier (QUIC datagrams,
352
+ // or capsules in DATA frames); the request is wrapped as the others are.
353
+ connectUdpHandler: {
354
+ wants: wantsConnectUdp,
355
+ handle: function (plainReq, carrier) {
356
+ var req = new IncomingMessage({
357
+ httpVersion: '3.0', method: plainReq.method, url: plainReq.url,
358
+ headers: plainReq.headers || {}, streamId: plainReq.stream_id, socket: plainReq.socket || _makeSocketStub()
359
+ });
360
+ if (req.headers.host === undefined && req.headers[':authority'] !== undefined) req.headers.host = req.headers[':authority'];
361
+ req._endData();
362
+ udpProxy.handle(req, carrier, emitConnectUdp);
363
+ }
364
+ },
72
365
  key: tlsKey,
73
366
  cert: tlsCert,
74
367
  ca: tlsCa,
75
- SNICallback: options.SNICallback || null,
368
+ SNICallback: (tlsKey && tlsCert) || userSni ? serverSni : null,
369
+ ticketKeys: ticketKeys,
370
+ ticketData: ticketData,
371
+ ech: ech,
372
+ ocspRequest: ocspRequest,
373
+ // The HTTP/3 side's stateless reset secret (see quic_server). Random when
374
+ // not given.
375
+ resetTokenSecret: options.resetTokenSecret || null,
376
+ // The secret HTTP/3 address-validation tokens (Retry, NEW_TOKEN) are
377
+ // sealed under (see quic_server). Random when not given.
378
+ tokenSecret: options.tokenSecret || null,
379
+ // HTTP/3 address validation with Retry — node:quic's validateAddress.
380
+ // Off unless set; see setValidateAddress below.
381
+ validateAddress: options.validateAddress === true,
382
+ // Handshakes under way: the threshold of 'handshakeLoad', and the cap per
383
+ // client address (quic_server, "Handshakes under way").
384
+ handshakeLoadThreshold: options.handshakeLoadThreshold,
385
+ // 0-RTT (HTTP/3): off unless true; see h3_server for earlyDataFilter.
386
+ earlyData: options.earlyData, earlyDataMaxAge: options.earlyDataMaxAge,
387
+ earlyDataReplay: options.earlyDataReplay, earlyDataFilter: options.earlyDataFilter,
388
+ maxHandshakesPerAddress: options.maxHandshakesPerAddress,
389
+ // Every version's: how long a request may take to arrive (http_common).
390
+ headersTimeout: options.headersTimeout,
391
+ requestTimeout: options.requestTimeout,
392
+ maxRequestsPerSocket: options.maxRequestsPerSocket,
393
+ // Rapid Reset: cancelled streams a connection may send (http_common).
394
+ streamResetBurst: options.streamResetBurst, streamResetRate: options.streamResetRate,
395
+ // WebTransport sessions a connection may hold at once (SETTINGS_WT_MAX_
396
+ // SESSIONS); unset, no limit.
397
+ maxWebTransportSessions: options.maxWebTransportSessions,
398
+ // QUIC's transport parameters (tls_bridge): windows and stream limits.
399
+ transportParams: options.transportParams,
400
+ // The congestion window a connection starts with, in packets (20).
401
+ initialCongestionWindow: options.initialCongestionWindow,
402
+ // A returning client's connection starts from what the last one measured
403
+ // (RFC 9959 Careful Resume); on unless false.
404
+ carefulResume: options.carefulResume,
405
+ // Startup's cap on the congestion window: 'stall' (default), 'always',
406
+ // 'off' (quic_connection, startupCap); per connection by connectionOptions.
407
+ startupCap: options.startupCap,
408
+ // Path MTU discovery (off by default on Windows), the largest UDP
409
+ // payload, the RTT assumed before a sample, the reordering a packet is
410
+ // allowed (quic_connection's CONNECTION_OPTIONS).
411
+ pmtuDiscovery: options.pmtuDiscovery,
412
+ maxUdpPayloadSize: options.maxUdpPayloadSize,
413
+ initialRtt: options.initialRtt,
414
+ reorderWindow: options.reorderWindow,
415
+ // Per QUIC connection, asked once its ClientHello is in: (info, callback)
416
+ // → { transportParams, initialCongestionWindow, carefulResume, startupCap,
417
+ // pmtuDiscovery, maxUdpPayloadSize, initialRtt, reorderWindow }.
418
+ connectionOptions: options.connectionOptions,
419
+ // The QUIC versions accepted, in preference (quic_version.js): ['v1', 'v2'].
420
+ quicVersions: options.quicVersions,
421
+ alpn: quicAlpn,
76
422
  maxConnections: options.maxConnections || 10000,
77
423
  // External UDP sockets (shared UDP port scenario). When provided,
78
424
  // the UDP layer is owned externally — caller feeds packets via
79
425
  // server.handlePacket(msg, rinfo). See RFC 9443.
80
426
  socket: options.socket || null,
81
- socket6: options.socket6 || null
427
+ socket6: options.socket6 || null,
428
+ // grease_quic_bit (RFC 9287): on, except with external sockets — a port
429
+ // they share with other protocols may tell QUIC apart by that bit.
430
+ // true: the port is QUIC's alone, grease there too.
431
+ greaseQuicBit: options.greaseQuicBit
82
432
  };
433
+ // node:tls's options (tls_options.js) — to a QUIC server that will run:
434
+ // one that does not (http3: false) is still built, and would refuse what
435
+ // only HTTP/3 cannot do (maxVersion TLSv1.2, TLS 1.2-only ciphers).
436
+ if (enableH3) Object.assign(h3Options, tlsPassOn);
83
437
 
84
438
  var isExternalUdp = !!(options.socket || options.socket6);
85
439
 
86
440
  var h3srv = createH3Server(h3Options, function (plainReq, plainRes) {
87
- var socketStub = _makeSocketStub();
88
- socketStub.remoteAddress = plainReq.remoteAddress || null;
89
- socketStub.remotePort = plainReq.remotePort || null;
441
+ // h3_server already attached a socket stand-in carrying the QUIC
442
+ // connection's address and its TLS fingerprints. Building a fresh stub
443
+ // here and using that instead threw all of it away — the application saw
444
+ // an object with no getJA4 and concluded, reasonably, that this
445
+ // connection had no TLS.
446
+ var socketStub = plainReq.socket || _makeSocketStub();
447
+ if (!socketStub.remoteAddress) socketStub.remoteAddress = plainReq.remoteAddress || null;
448
+ if (!socketStub.remotePort) socketStub.remotePort = plainReq.remotePort || null;
90
449
 
91
450
  // ---- Wrap plain req into IncomingMessage (Readable) ----
92
451
  var req = new IncomingMessage({
@@ -98,9 +457,68 @@ function createServer(options, handler) {
98
457
  socket: socketStub
99
458
  });
100
459
 
101
- // Bridge data from plain req → IncomingMessage
102
- plainReq.on('data', function (chunk) { req._pushData(chunk); });
103
- plainReq.on('end', function () { req._endData(); });
460
+ // req.headers.host, from :authority — as HTTP/2 already provides it.
461
+ //
462
+ // HTTP/2 and HTTP/3 both carry the host as :authority and no Host header,
463
+ // and the HTTP/2 path has always synthesized `host` from it
464
+ // (pseudoHeadersToRequest in http_common). This path never did, so the
465
+ // same application saw headers.host on one protocol and undefined on the
466
+ // other — including for every WebTransport session, which only exists
467
+ // over HTTP/3.
468
+ //
469
+ // ADDED, not substituted: the pseudo-headers stay, since code routes on
470
+ // them (:protocol is how a WebTransport CONNECT is recognised). And added
471
+ // AFTER the IncomingMessage is built, so rawHeaders — the record of what
472
+ // the client actually sent, and what JA4H is computed from — is untouched.
473
+ if (req.headers.host === undefined && req.headers[':authority'] !== undefined) {
474
+ req.headers.host = req.headers[':authority'];
475
+ }
476
+
477
+ // Carried across the wrapper, like the socket above: built on the
478
+ // connection, and lost if only the fields listed in the constructor
479
+ // survive.
480
+ if (plainReq.h3Fingerprint) req.h3Fingerprint = plainReq.h3Fingerprint;
481
+ // Came in 0-RTT (and earlyDataFilter let it through): it may be a
482
+ // replay — nothing with an effect that must happen once.
483
+ req.earlyData = plainReq.earlyData === true;
484
+ if (plainReq.webTransportProtocols) req.webTransportProtocols = plainReq.webTransportProtocols;
485
+
486
+ // Bridge data from plain req → IncomingMessage.
487
+ //
488
+ // The body's flow-control credit goes back to the client as the
489
+ // application READS it (_read), not as it arrives — as over HTTP/2 (see
490
+ // h2_server). A handler that does not read at once — an authorization
491
+ // check first, a lookup — had the whole upload taken in and held in
492
+ // memory meanwhile, as fast as the client could send it. Now the client
493
+ // can be ahead of the reader by one window, and no more.
494
+ var holdsBody = typeof plainReq._holdBody === 'function';
495
+ var received = null;
496
+ if (holdsBody) {
497
+ plainReq._holdBody();
498
+ // Credited for what has been read (utils' creditAsRead).
499
+ received = creditAsRead(req, function (n) { plainReq._creditBody(n); });
500
+ // Read to the end, or abandoned (destroyed, aborted): nothing more to
501
+ // wait for.
502
+ req.on('close', function () { plainReq._releaseBody(); });
503
+ }
504
+ plainReq.on('data', function (chunk) {
505
+ // Nobody will read it: let it go, credit and all.
506
+ if (req.destroyed) { if (holdsBody) plainReq._releaseBody(); return; }
507
+ if (received) received(chunk.length);
508
+ req._pushData(chunk);
509
+ });
510
+ plainReq.on('end', function () {
511
+ // Its trailers, if it sent any — before 'end', where node:http has them.
512
+ if (plainReq.trailers) {
513
+ req.trailers = plainReq.trailers;
514
+ req.rawTrailers = [];
515
+ for (var tn in plainReq.trailers) req.rawTrailers.push(tn, String(plainReq.trailers[tn]));
516
+ }
517
+ req._endData();
518
+ });
519
+ // Once the response is complete: is the rest of the body still wanted?
520
+ // As node:http2 decides it — if the application has started reading it.
521
+ plainReq._wantsRest = function () { return !req.destroyed && req.readableFlowing !== null; };
104
522
 
105
523
  // ---- Wrap plain res into ServerResponse (Writable) ----
106
524
  // ServerResponse._write → plainRes.write
@@ -112,6 +530,24 @@ function createServer(options, handler) {
112
530
  });
113
531
  res.req = req;
114
532
 
533
+ // The stream was cancelled under this request (see releaseHttpStream in
534
+ // h3_server) — handled as h2_server's abortStream does: 'aborted' always,
535
+ // an Error only to a listener that asked for one, and 'close' on the
536
+ // response so a handler still writing can stop.
537
+ plainReq.on('aborted', function () {
538
+ if (req.aborted) return;
539
+ var err = new Error('stream reset by peer'); err.code = 'ERR_STREAM_RESET';
540
+ req._abort(err); // 'aborted', an Error only to a listener, 'close'
541
+ res._abortResponse();
542
+ });
543
+ // The application giving the exchange up — res.destroy([err]),
544
+ // req.destroy([err]), as on node:http2 where either ends the stream —
545
+ // reaches the client: h3_server's abort (RESET_STREAM and STOP_SENDING,
546
+ // or only STOP_SENDING once the response is complete).
547
+ var giveUp = function (err) { plainRes.abort(err ? 0x0102 : 0x010c); }; // H3_INTERNAL_ERROR : H3_REQUEST_CANCELLED
548
+ res._onDestroy(giveUp);
549
+ req._onDestroy(function (err) { if (!res.writableFinished && !res.destroyed) res.destroy(err); else giveUp(err); });
550
+
115
551
  // Store wrapper references for WebTransport event forwarding.
116
552
  // MUST come after `res` is created (was previously set while res was
117
553
  // still undefined due to var hoisting).
@@ -119,10 +555,12 @@ function createServer(options, handler) {
119
555
  plainRes._wrapper = res;
120
556
 
121
557
  // Override internal methods to delegate to the plain H3 res.
122
- // Key insight: we buffer one chunk behind so that _final can
123
- // send the last data + FIN together via plainRes.end(chunk),
124
- // instead of plainRes.write(chunk) + plainRes.end() separately.
125
- // This avoids FIN-only packets that can get lost without PTO.
558
+ // One chunk is held back so that _final can send the last data and the
559
+ // FIN together via plainRes.end(chunk): res.end('ok') is one packet, not
560
+ // the data and then a FIN-only packet. (A FIN-only packet is recovered
561
+ // like any other when lost — see fin_pns in quic_connection — so this is
562
+ // about packets, not correctness.) Backpressure therefore bounds what is
563
+ // held at the high-water mark plus that one chunk.
126
564
  var _pendingChunk = null;
127
565
 
128
566
  res._flushHeaders = function () {
@@ -140,28 +578,46 @@ function createServer(options, handler) {
140
578
  plainRes.write(_pendingChunk);
141
579
  }
142
580
  _pendingChunk = chunk;
143
- callback();
581
+ // Done with this write once the stream has caught up to within the
582
+ // high-water mark — not at once. Calling back at once told Writable
583
+ // nothing was ever buffered, so write() never returned false and a
584
+ // piped file was read into memory as fast as the disk allowed.
585
+ plainRes.whenSent(res.writableHighWaterMark, callback);
144
586
  };
145
587
 
146
588
  res._final = function (callback) {
147
589
  if (!res.headersSent) res._flushHeaders();
590
+ // res.addTrailers(): a HEADERS frame after the body, which carries the
591
+ // FIN instead (gRPC's grpc-status rides there).
592
+ var trailers = res.trailers && Object.keys(res.trailers).length ? res.trailers : null;
148
593
  if (_pendingChunk !== null) {
149
- plainRes.end(_pendingChunk); // data + FIN together
594
+ plainRes.end(_pendingChunk, trailers); // data + FIN together
150
595
  } else {
151
- plainRes.end(); // FIN only (no body written)
596
+ plainRes.end(null, trailers); // FIN only (no body written)
152
597
  }
153
598
  callback();
154
599
  };
155
600
 
156
- // ---- WebTransport method proxying ----
157
- if (plainRes.sendDatagram) {
158
- res.sendDatagram = plainRes.sendDatagram.bind(plainRes);
601
+ // 102 Processing — the HTTP/3 one.
602
+ if (plainRes.writeProcessing) {
603
+ res.writeProcessing = function () { if (!res.headersSent) plainRes.writeProcessing(); };
159
604
  }
160
- if (plainRes.createBidirectionalStream) {
161
- res.createBidirectionalStream = plainRes.createBidirectionalStream.bind(plainRes);
605
+ // 103 Early Hints — the HTTP/3 one, written on the same stream.
606
+ if (plainRes.writeEarlyHints) {
607
+ res.writeEarlyHints = function (hints, callback) {
608
+ if (res.headersSent) { if (typeof callback === 'function') process.nextTick(callback); return; }
609
+ plainRes.writeEarlyHints(hints, callback);
610
+ };
162
611
  }
163
- if (plainRes.createUnidirectionalStream) {
164
- res.createUnidirectionalStream = plainRes.createUnidirectionalStream.bind(plainRes);
612
+
613
+ // ---- Methods of the HTTP/3 response: its priority, and WebTransport's ----
614
+ ['setPriority', 'sendDatagram', 'maxDatagramSize', 'createBidirectionalStream', 'createUnidirectionalStream',
615
+ 'createSendGroup', 'canOpenStream', 'setWebTransportProtocol', 'getStats', 'closeSession', 'drainSession',
616
+ 'exportKeyingMaterial'].forEach(function (m) {
617
+ if (typeof plainRes[m] === 'function') res[m] = plainRes[m].bind(plainRes);
618
+ });
619
+ if ('datagrams' in plainRes) {
620
+ Object.defineProperty(res, 'datagrams', { enumerable: true, get: function () { return plainRes.datagrams; } });
165
621
  }
166
622
 
167
623
  if (typeof handler === 'function') handler(req, res);
@@ -169,92 +625,295 @@ function createServer(options, handler) {
169
625
  });
170
626
 
171
627
 
628
+
629
+ // RFC 9220: WebSocket over HTTP/3 arrives as extended CONNECT on a QUIC
630
+ // stream and surfaces on the same 'upgrade' event as HTTP/1.1 and HTTP/2.
631
+ // Three transports, one event, one WebSocketServer.
632
+ h3srv.on('upgrade', function (req, stream, head) { ev.emit('upgrade', req, stream, head); });
633
+ h3srv.on('stream_open', function (conn, id) { ev.emit('stream_open', conn, id); });
634
+ h3srv.on('stream_reset', function (conn, id, code) { ev.emit('stream_reset', conn, id, code); });
635
+
636
+ // A new QUIC connection, before its handshake. On a server that runs all
637
+ // three protocols this shares the event with TCP connections, so a listener
638
+ // may receive either a net.Socket or a QUIC connection handle. Both carry
639
+ // remoteAddress, destroyed and destroy() — the three things screening code
640
+ // uses — and the handle's httpVersion ('3.0') tells them apart where it
641
+ // matters.
642
+ h3srv.on('connection', function (conn) { ev.emit('connection', conn); });
643
+ h3srv.on('handshakeLoad', function (e) { ev.emit('handshakeLoad', e); });
644
+ h3srv.on('secureConnection', function (conn) { ev.emit('secureConnection', conn); });
645
+ h3srv.on('tlsClientError', function (err, conn) { ev.emit('tlsClientError', err, conn); });
646
+
172
647
  // ============================================================
173
- // TCP — HTTP/2 and/or HTTP/1.1
648
+ // TCP — HTTP/2 and HTTP/1.1, both ours.
649
+ //
650
+ // Previously node:http2 and node:https. They are gone: h2_server and
651
+ // h1_server implement the same two protocols, and tcp_listener owns the
652
+ // socket and the ALPN choice between them — one port, one certificate, one
653
+ // preference order, decided in one place.
654
+ //
655
+ // The three request paths now converge completely. h3_server, h2_server and
656
+ // h1_server each hand the application the same IncomingMessage /
657
+ // ServerResponse pair from streams.js, so `handler` cannot tell which
658
+ // protocol produced a request except by reading req.httpVersion — which is
659
+ // exactly the property that lets Express and Fastify run unchanged over all
660
+ // three.
174
661
  // ============================================================
175
662
 
176
- var tcpServer = null;
663
+ // The QUIC layer's errors — a UDP bind that fails, most importantly — are
664
+ // this server's errors. h3_server emits them, and nothing here listened,
665
+ // so a port that could not be bound left listen() waiting forever with no
666
+ // sign of why.
667
+ h3srv.on('error', function (err) {
668
+ if (err && !err.transport) err.transport = 'udp';
669
+ ev.emit('error', err);
670
+ });
177
671
 
178
- function startTcpServer(listenPort, host, callback) {
179
- var hasTlsCredentials = (tlsKey && tlsCert) || options.SNICallback;
180
- if (!needsTcp || !hasTlsCredentials) {
181
- if (callback) callback();
182
- return;
183
- }
672
+ // ============================================================
673
+ // Building versus binding.
674
+ //
675
+ // Everything a request needs — the TLS layer, ALPN, HTTP/2, HTTP/1.1,
676
+ // HTTP/3 — is built HERE, when the server is created. listen() only binds
677
+ // the ports this server owns.
678
+ //
679
+ // It used to be the other way round: the protocol layers were built inside
680
+ // listen(), so a server whose sockets belonged to someone else still had to
681
+ // call listen() to exist — a listen() that listened to nothing — and a
682
+ // separate flag told it not to bind. Now the rule is simply:
683
+ //
684
+ // listen() not called → nothing bound; feed it through
685
+ // handlePacket() (UDP) and handleConnection()
686
+ // (TCP), from whoever owns the ports
687
+ // listen(port) called → binds TCP, and UDP unless `socket` was given
688
+ //
689
+ // A server can also be built and bound — the ordinary case — with nothing
690
+ // changing for callers that always called listen().
691
+ // ============================================================
184
692
 
185
- // Bridge SNICallback: detect context type and route accordingly.
186
- // quico context (_nodeContext) → use pre-built node:tls context
187
- // quico context (_rawKey) → build node:tls context from stored creds
188
- // node:tls context → use directly (someone used tls.createSecureContext)
189
- var tcpSniCallback = null;
190
- if (options.SNICallback) {
191
- tcpSniCallback = function (servername, cb) {
192
- options.SNICallback(servername, function (err, ctx) {
193
- if (err) return cb(err);
194
- if (!ctx) return cb(new Error('SNICallback returned null'));
195
-
196
- // 1. Pre-built node:tls context (fastest path)
197
- if (ctx._nodeContext) {
198
- return cb(null, ctx._nodeContext);
199
- }
200
- // 2. Raw key/cert stored — build node:tls context
201
- if (ctx._rawKey && ctx._rawCert) {
202
- var nodeOpts = { key: ctx._rawKey, cert: ctx._rawCert };
203
- if (ctx._rawCa) nodeOpts.ca = ctx._rawCa;
204
- try { return cb(null, nodeTls.createSecureContext(nodeOpts)); }
205
- catch (e) { return cb(e); }
206
- }
207
- // 3. Assume it's already a node:tls context (tls.createSecureContext)
208
- cb(null, ctx);
209
- });
210
- };
211
- }
693
+ // The port Alt-Svc advertises. listen(port) sets it; a server that is fed
694
+ // from outside never calls listen(), and takes it from `options.port`
695
+ // instead — quico cannot otherwise know which port its clients see.
696
+ var altSvcPort = (options.port !== undefined && options.port !== null) ? options.port : 443;
697
+
698
+ // A function, so HTTP/1.1 and HTTP/2 read the value per response. They are
699
+ // built before listen() can say which port is real.
700
+ //
701
+ // Alt-Svc advertises HTTP/3 on the same authority (RFC 9114 §3.3), which is
702
+ // how a browser that arrived over TCP learns to try QUIC next time.
703
+ //
704
+ // Only for an HTTP/3 this server runs. With http3:false it was sent anyway,
705
+ // on the guess that HTTP/3 was served elsewhere on the same port — and when
706
+ // it was not, every browser was sent to try a QUIC endpoint that does not
707
+ // exist, failing over back to TCP each time the advertisement was renewed.
708
+ // HTTP/3 served by something else is said with `altSvc`, the header value
709
+ // as is (`'h3=":443"; ma=86400'`); `altSvc: false` sends none at all.
710
+ function altSvcValue() { return 'h3=":' + altSvcPort + '"; ma=86400'; }
711
+ var altSvcOption = options.altSvc;
712
+ if (altSvcOption !== undefined && altSvcOption !== false && altSvcOption !== null && typeof altSvcOption !== 'string') {
713
+ throw new TypeError('options.altSvc must be a string or false');
714
+ }
715
+ function altSvcHeader() {
716
+ if (typeof altSvcOption === 'string') return altSvcOption;
717
+ if (altSvcOption === false || altSvcOption === null || !enableH3) return null;
718
+ return altSvcValue();
719
+ }
212
720
 
213
- var altSvcHeader = 'h3=":' + listenPort + '"; ma=86400';
721
+ var tcpServer = null;
722
+ var hasTlsCredentials = (tlsKey && tlsCert) || options.SNICallback;
214
723
 
215
- if (enableH2) {
216
- // ---- HTTP/2 (+ optional HTTP/1.1) via node:http2 ----
217
- var tcpOpts = {
218
- allowHTTP1: enableH1,
219
- settings: { enablePush: false }
220
- };
221
- if (tlsKey) tcpOpts.key = tlsKey;
222
- if (tlsCert) tcpOpts.cert = tlsCert;
223
- if (tlsCa) tcpOpts.ca = tlsCa;
224
- if (tcpSniCallback) tcpOpts.SNICallback = tcpSniCallback;
724
+ // ---- A WebTransport session asked for over HTTP/1.1 or HTTP/2 ----
725
+ //
726
+ // WebTransport runs here over HTTP/3 only (an extended CONNECT, RFC 9220;
727
+ // HTTP/2 advertises no WebTransport). A browser that cannot reach HTTP/3
728
+ // may still send its session request over TCP: Firefox 158, with HTTP/3
729
+ // excluded for the origin, sends a plain "GET /" with
730
+ // Sec-Fetch-Dest: webtransport on a new HTTP/1.1 connection. Handed to the
731
+ // application it is answered as a page, and that body lands in the
732
+ // browser's WebTransport session — Firefox aborts the whole browser on it
733
+ // (MOZ_RELEASE_ASSERT in WebTransportSessionProxy::OnDataAvailable).
734
+ //
735
+ // So the server answers it itself: 501 and no body (RFC 9110 §15.6.2 — the
736
+ // functionality asked for is not offered on this version). The session
737
+ // fails cleanly, and the client can fall back to whatever it has. The
738
+ // application never sees it. Two signs, either enough: the Fetch
739
+ // destination a browser sends (Sec-Fetch-Dest: webtransport), and an
740
+ // extended CONNECT for :protocol webtransport over HTTP/2.
741
+ function isWebTransportRequest(req) {
742
+ var h = req.headers || {};
743
+ return h['sec-fetch-dest'] === 'webtransport' || h[':protocol'] === 'webtransport';
744
+ }
745
+ function refuseWebTransport(req, res) {
746
+ req.resume();
747
+ try { res.writeHead(501, { 'content-length': '0', 'cache-control': 'no-store' }); res.end(); } catch (e) {}
748
+ }
225
749
 
226
- tcpServer = http2.createSecureServer(tcpOpts);
750
+ function onRequest(req, res) {
751
+ if (isWebTransportRequest(req)) { refuseWebTransport(req, res); return; }
752
+ if (req.earlyData === true) { earlyGate(req, res); return; }
753
+ runHandler(req, res);
754
+ }
755
+ function runHandler(req, res) {
756
+ if (typeof handler === 'function') handler(req, res);
757
+ ev.emit('request', req, res);
758
+ }
227
759
 
228
- tcpServer.on('request', function (req, res) {
229
- res.setHeader('alt-svc', altSvcHeader);
230
- if (typeof handler === 'function') handler(req, res);
231
- ev.emit('request', req, res);
232
- });
760
+ // A request read from TLS early data (HTTP/2, HTTP/1.1; HTTP/3's is
761
+ // h3_server's): handled now only if earlyDataFilter lets it — by default a
762
+ // GET, HEAD or OPTIONS — or answered 425 Too Early, which the client
763
+ // sends again after the handshake (RFC 8470 §5.2). No answer within
764
+ // 100 ms: 425.
765
+ function earlyGate(req, res) {
766
+ runEarlyDataFilter(options.earlyDataFilter, req, function (allow) {
767
+ if (res.writableEnded || res.destroyed) return;
768
+ if (allow) { runHandler(req, res); return; }
769
+ req.resume();
770
+ stats.earlyData.tooEarly++;
771
+ try { res.writeHead(425, { 'content-length': '0' }); res.end(); } catch (e) {}
772
+ });
773
+ }
233
774
 
234
- } else if (enableH1) {
235
- // ---- HTTP/1.1 only via node:https ----
236
- var tcpOpts = {};
237
- if (tlsKey) tcpOpts.key = tlsKey;
238
- if (tlsCert) tcpOpts.cert = tlsCert;
239
- if (tlsCa) tcpOpts.ca = tlsCa;
240
- if (tcpSniCallback) tcpOpts.SNICallback = tcpSniCallback;
775
+ if (needsTcp) {
776
+ var shared = {
777
+ // An extended CONNECT for WebTransport over HTTP/2 is not taken up: it
778
+ // goes on as a request, and onRequest refuses it (above).
779
+ shouldUpgradeCallback: function (req) { return !isWebTransportRequest(req) && shouldUpgradeCallback(req); },
780
+ // Not over cleartext: HTTP/3 is always TLS, and browsers take Alt-Svc
781
+ // only from an https origin — on http:// it advertised a service that
782
+ // could not exist there.
783
+ altSvc: hasTlsCredentials && altSvcHeader() !== null ? altSvcHeader : null,
784
+ maxHeaderListSize: options.maxHeaderListSize,
785
+ headersTimeout: options.headersTimeout,
786
+ keepAliveTimeout: options.keepAliveTimeout,
787
+ requestTimeout: options.requestTimeout,
788
+ // node:http's: requests per connection (HTTP/1.1: the Nth closes it;
789
+ // HTTP/2 and HTTP/3: GOAWAY after it).
790
+ maxRequestsPerSocket: options.maxRequestsPerSocket,
791
+ // Rapid Reset, HTTP/2's (http_common streamResetLimiter).
792
+ streamResetBurst: options.streamResetBurst, streamResetRate: options.streamResetRate,
793
+ maxHeadSize: options.maxHeadSize || options.maxHeaderSize,
794
+ maxHeaderCount: options.maxHeaderCount,
795
+ // These were being dropped: an application could set them and nothing
796
+ // downstream ever saw them, which is worse than not offering the option
797
+ // at all — it looks configured and is not.
798
+ maxConcurrentStreams: options.maxConcurrentStreams,
799
+ maxSessionMemory: options.maxSessionMemory,
800
+ initialWindowSize: options.initialWindowSize,
801
+ connectionWindowSize: options.connectionWindowSize,
802
+ maxFrameSize: options.maxFrameSize,
803
+ headerTableSize: options.headerTableSize,
804
+ enableConnectProtocol: options.enableConnectProtocol,
805
+ stats: stats
806
+ };
241
807
 
242
- tcpServer = nodeHttps.createServer(tcpOpts);
808
+ tcpServer = createTcpListener(Object.assign({
809
+ // Cleartext when there are no credentials: a dev server, or a service
810
+ // behind a terminating proxy. Without TLS there is no ALPN, so
811
+ // tcp_listener tells h2c from HTTP/1.1 by looking for the connection
812
+ // preface instead.
813
+ tls: hasTlsCredentials ? lemonTls : null,
814
+ key: tlsKey,
815
+ cert: tlsCert,
816
+ ca: tlsCa,
817
+ SNICallback: (tlsKey && tlsCert) || userSni ? serverSni : null,
818
+ ticketKeys: ticketKeys,
819
+ ticketData: ticketData,
820
+ ech: ech,
821
+ ocspRequest: ocspRequest,
822
+ // node:http's TCP options, with its defaults: Nagle off, keep-alive off.
823
+ noDelay: options.noDelay,
824
+ keepAlive: options.keepAlive,
825
+ keepAliveInitialDelay: options.keepAliveInitialDelay,
826
+ // 0-RTT over TLS 1.3 (HTTP/2, HTTP/1.1): the same options as HTTP/3's,
827
+ // as they are at each connection (setOptions changes them).
828
+ earlyDataNow: function () { var o = h3srv.getOptions(); return { on: o.earlyData === true, maxAge: o.earlyDataMaxAge }; },
829
+ earlyDataReplay: options.earlyDataReplay,
830
+ stats: stats
831
+ }, tlsPassOn)); // node:tls's options (tls_options.js)
243
832
 
244
- tcpServer.on('request', function (req, res) {
245
- res.setHeader('alt-svc', altSvcHeader);
246
- if (typeof handler === 'function') handler(req, res);
247
- ev.emit('request', req, res);
833
+ var h2srv = null;
834
+ var h1srv = null;
835
+
836
+ if (enableH2) {
837
+ h2srv = createH2Server(shared, onRequest);
838
+ h2srv.on('clientError', function (err, sock) { ev.emit('clientError', err, sock); });
839
+ h2srv.on('sessionError', function (err, session) { ev.emit('sessionError', err, session); });
840
+ // Extended CONNECT (RFC 8441) arrives on the same 'upgrade' event as an
841
+ // HTTP/1.1 Upgrade, so a WebSocketServer attached to this server serves
842
+ // both without knowing which carried the connection.
843
+ h2srv.on('upgrade', function (req, stream, head) {
844
+ if (wantsConnectUdp(req)) { connectUdpH2(req, stream); return; }
845
+ ev.emit('upgrade', req, stream, head);
846
+ });
847
+ // Stream lifecycle, for rate limiting. `conn` carries the address to key
848
+ // on and the two ways to refuse; see h2_server.
849
+ h2srv.on('stream_open', function (conn, id) { ev.emit('stream_open', conn, id); });
850
+ h2srv.on('stream_reset', function (conn, id, code) { ev.emit('stream_reset', conn, id, code); });
851
+ }
852
+ if (enableH1) {
853
+ h1srv = createH1Server(shared, onRequest);
854
+ h1srv.on('clientError', function (err, sock) { ev.emit('clientError', err, sock); });
855
+ // 'upgrade' is how WebSocket (and anything else that switches protocols)
856
+ // gets the raw socket. Same signature as node:http, so the `ws` package
857
+ // works over this server unchanged.
858
+ h1srv.on('upgrade', function (req, socket, head) {
859
+ if (wantsConnectUdp(req)) { connectUdpH1(req, socket, head); return; }
860
+ ev.emit('upgrade', req, socket, head);
248
861
  });
249
862
  }
250
863
 
251
- if (tcpServer) {
252
- tcpServer.on('error', function (err) { ev.emit('error', err); });
253
- var tcpHost = host || '0.0.0.0';
254
- tcpServer.listen(listenPort, tcpHost, callback);
255
- } else {
256
- if (callback) callback();
864
+ // Registration order is the server's ALPN preference — tcpAlpn, which is
865
+ // h2 then HTTP/1.1 unless ALPNProtocols says otherwise. Names beyond HTTP
866
+ // are only meaningful with TLS: without it there is no ALPN to carry them.
867
+ for (var a = 0; a < tcpAlpn.length; a++) {
868
+ var proto = tcpAlpn[a];
869
+ // managesClose: each closes its own connections (beginClose) — the
870
+ // ones in flight finish first.
871
+ if (proto === 'h2') tcpServer.register('h2', h2srv.handleConnection, { managesClose: true });
872
+ else if (proto === 'http/1.1') tcpServer.register('http/1.1', h1srv.handleConnection, { managesClose: true });
873
+ else if (hasTlsCredentials) tcpServer.register(proto, closeAfterHandshake);
874
+ }
875
+
876
+ tcpServer.on('error', function (err) { ev.emit('error', err); });
877
+ tcpServer.on('clientError', function (err, sock) { ev.emit('clientError', err, sock); });
878
+ tcpServer.on('tlsClientError', function (err, sock) { ev.emit('tlsClientError', err, sock); });
879
+ // Fires before the TLS handshake — see tcp_listener. Same name and same
880
+ // argument as node:net, so an IP screen written for node:http works here.
881
+ tcpServer.on('connection', function (sock) {
882
+ armSocketTimeout(sock);
883
+ ev.emit('connection', sock);
884
+ });
885
+ tcpServer.on('secureConnection', function (sock) { ev.emit('secureConnection', sock); });
886
+ }
887
+
888
+ // External UDP: the sockets are the caller's, and so is reading them. What
889
+ // the QUIC layer still needs is its own housekeeping — the idle sweep, and
890
+ // larger kernel buffers — and that starts now, since listen() may never be
891
+ // called. h3srv.listen() in external mode binds nothing; see quic_server.
892
+ if (enableH3 && isExternalUdp) {
893
+ h3srv.listen(altSvcPort, null, function () {});
894
+ }
895
+
896
+
897
+ /**
898
+ * Accept a TCP connection accepted by someone else.
899
+ *
900
+ * For a server whose TCP port belongs to another component — typically one
901
+ * that demultiplexes it by first byte, sending TLS here and other protocols
902
+ * elsewhere, the way RFC 9443 splits a UDP port. The socket is treated
903
+ * exactly as one this server accepted itself: 'connection' fires before the
904
+ * TLS handshake, and ALPN routes it to HTTP/2 or HTTP/1.1.
905
+ *
906
+ * Works without listen(): the layers exist from createServer. Bytes the
907
+ * owner read to classify the connection must be put back first —
908
+ * socket.unshift(chunk) — or the TLS layer starts mid-record.
909
+ */
910
+ function handleConnection(socket) {
911
+ if (!tcpServer) {
912
+ // HTTP/1.1 and HTTP/2 are both off; there is nothing to hand it to.
913
+ try { socket.destroy(); } catch (e) {}
914
+ return;
257
915
  }
916
+ tcpServer.handleConnection(socket);
258
917
  }
259
918
 
260
919
 
@@ -264,52 +923,140 @@ function createServer(options, handler) {
264
923
 
265
924
  function listen(listenPort, host, callback) {
266
925
  if (typeof host === 'function') { callback = host; host = null; }
267
- port = listenPort || 443;
268
926
 
269
- var remaining = needsTcp ? 2 : 1;
927
+ // `|| 443` turned port 0 into 443. Port 0 is not "no port": it is the
928
+ // documented way to ask the OS for a free one, which every test harness
929
+ // and every process that binds more than one server relies on. The old
930
+ // form silently bound 443 instead — and on a machine where that needs
931
+ // privileges, failed with EACCES for a caller that never mentioned 443.
932
+ port = (listenPort === undefined || listenPort === null) ? 443 : listenPort;
933
+ if (port !== 0) altSvcPort = port;
934
+
935
+ // Only what this server owns: UDP unless it was handed sockets, TCP unless
936
+ // both HTTP/1.1 and HTTP/2 are off.
937
+ var bindUdp = enableH3 && !isExternalUdp;
938
+ var remaining = (bindUdp ? 1 : 0) + (needsTcp ? 1 : 0);
270
939
  var called = false;
271
940
 
272
941
  function done() {
273
942
  remaining--;
274
943
  if (remaining <= 0 && !called) {
275
944
  called = true;
945
+ listening = true;
946
+ closed = false;
947
+ // After listen(0) the OS chose the port; Alt-Svc must name that one.
948
+ if (port === 0 && tcpServer && tcpServer.address()) altSvcPort = tcpServer.address().port;
276
949
  ev.emit('listening');
277
950
  if (typeof callback === 'function') callback();
278
951
  }
279
952
  }
280
953
 
281
- // H3 — always. In external mode h3srv.listen() wires up the external
282
- // sockets' message handler and starts the sweep timer, but does not
283
- // bind anything new.
284
- h3srv.listen(port, host, done);
954
+ // Nothing to bind — every socket is external. Still answer, and still
955
+ // asynchronously, like a real bind would.
956
+ if (remaining === 0) { remaining = 1; setImmediate(done); return server; }
285
957
 
286
- // TCP — if needed
287
- if (needsTcp) {
288
- startTcpServer(port, host, done);
289
- }
958
+ if (ref === false) { if (enableH3) h3srv.unref(); if (tcpServer) tcpServer.unref(); }
959
+ if (bindUdp) h3srv.listen(port, host, done);
960
+ if (needsTcp) tcpServer.listen(port, host || '0.0.0.0', done);
290
961
 
291
962
  return server;
292
963
  }
293
964
 
965
+ /**
966
+ * As node:http's close(): no new connections; the idle ones closed now;
967
+ * each with a request in flight closes once it is answered — HTTP/2 and
968
+ * HTTP/3 with GOAWAY (RFC 9113 §6.8, RFC 9114 §5.2), HTTP/1.1 at the end
969
+ * of its response. The callback, and 'close', when every connection is
970
+ * gone. It used to close every connection at once, on all three versions:
971
+ * a response being written was cut off (HTTP/1.1: ECONNRESET; HTTP/3: the
972
+ * client waited for the rest until a timeout). A second close(): the
973
+ * callback gets ERR_SERVER_NOT_RUNNING, as node:http's.
974
+ */
294
975
  function close(callback) {
295
- var remaining = 1 + (tcpServer ? 1 : 0);
976
+ if (closed) {
977
+ if (typeof callback === 'function') {
978
+ var e = new Error('Server is not running.'); e.code = 'ERR_SERVER_NOT_RUNNING';
979
+ process.nextTick(callback, e);
980
+ }
981
+ return server;
982
+ }
983
+ closed = true;
984
+ listening = false;
985
+ // h3srv is closed whenever HTTP/3 is on, bound or not: in external mode it
986
+ // still runs a sweep timer, started at creation.
987
+ var remaining = (enableH3 ? 1 : 0) + (tcpServer ? 1 : 0);
296
988
 
297
989
  function onClose() {
298
990
  remaining--;
299
- if (remaining <= 0 && typeof callback === 'function') callback();
991
+ if (remaining > 0) return;
992
+ ev.emit('close');
993
+ if (typeof callback === 'function') callback();
300
994
  }
301
995
 
302
- h3srv.close(onClose);
303
- if (tcpServer) { tcpServer.close(onClose); tcpServer = null; }
304
- else if (remaining <= 1 && typeof callback === 'function') callback();
996
+ if (remaining === 0) { remaining = 1; process.nextTick(onClose); return server; }
997
+ if (enableH3) h3srv.close(onClose);
998
+ if (tcpServer) {
999
+ if (h1srv) h1srv.beginClose();
1000
+ if (h2srv) h2srv.beginClose();
1001
+ // The port, and what tcp_listener holds that HTTP did not claim.
1002
+ // Its callback waits for every socket, as node:net's.
1003
+ tcpServer.close(onClose);
1004
+ tcpServer = null;
1005
+ }
1006
+ return server;
1007
+ }
1008
+
1009
+ // node:http's: the connections with nothing in flight closed now (Node
1010
+ // does this from close() too); all of them, in flight or not. A socket
1011
+ // handed to 'upgrade' over HTTP/1.1 is not this server's any more and
1012
+ // stays; HTTP/2 and HTTP/3 carry WebSockets and WebTransport on the
1013
+ // connection itself, and close with it.
1014
+ function closeIdleConnections() {
1015
+ if (h1srv) h1srv.closeIdleConnections();
1016
+ if (h2srv) h2srv.closeIdleConnections();
1017
+ if (enableH3) h3srv.closeIdleConnections();
1018
+ }
1019
+ function closeAllConnections() {
1020
+ if (h1srv) h1srv.closeAllConnections();
1021
+ if (h2srv) h2srv.closeAllConnections();
1022
+ if (enableH3) h3srv.closeAllConnections();
1023
+ }
1024
+
1025
+ // node:net's getConnections(cb): TCP connections and QUIC ones.
1026
+ function getConnections(cb) {
1027
+ var n = (h1srv ? h1srv.connectionCount : 0) + (h2srv ? h2srv.connectionCount : 0) + (enableH3 ? h3srv.connectionCount : 0);
1028
+ if (typeof cb === 'function') process.nextTick(cb, null, n);
1029
+ return server;
1030
+ }
1031
+
1032
+ // node:http's timeouts and limits, readable and settable while the server
1033
+ // runs, as server.keepAliveTimeout = 5000. Each version takes what it has:
1034
+ // keepAliveTimeout and maxHeadersCount are HTTP/1.1's; the rest all three.
1035
+ var limits = {
1036
+ headersTimeout: options.headersTimeout != null ? options.headersTimeout : 60000,
1037
+ requestTimeout: options.requestTimeout != null ? options.requestTimeout : 300000,
1038
+ keepAliveTimeout: options.keepAliveTimeout != null ? options.keepAliveTimeout : 5000,
1039
+ maxHeadersCount: options.maxHeaderCount != null ? options.maxHeaderCount : 2000,
1040
+ maxRequestsPerSocket: options.maxRequestsPerSocket != null ? options.maxRequestsPerSocket : 0
1041
+ };
1042
+ function setLimit(name, value) {
1043
+ if (typeof value !== 'number' || !(value >= 0)) throw new RangeError('server.' + name + ' must be a non-negative number, got ' + value);
1044
+ limits[name] = value;
1045
+ var o = {};
1046
+ o[name === 'maxHeadersCount' ? 'maxHeaderCount' : name] = value;
1047
+ if (h1srv) h1srv.setLimits(o);
1048
+ if (h2srv) h2srv.setLimits(o);
1049
+ if (enableH3) h3srv.setLimits(o);
305
1050
  }
306
1051
 
307
1052
  function address() {
1053
+ // The TCP listener knows the port the OS actually assigned, which is the
1054
+ // only useful answer after listen(0).
308
1055
  if (tcpServer && typeof tcpServer.address === 'function') {
309
1056
  var addr = tcpServer.address();
310
1057
  if (addr) return addr;
311
1058
  }
312
- return { address: '0.0.0.0', family: 'IPv4', port: port };
1059
+ return { address: '0.0.0.0', family: 'IPv4', port: altSvcPort };
313
1060
  }
314
1061
 
315
1062
 
@@ -317,21 +1064,217 @@ function createServer(options, handler) {
317
1064
  // Public API
318
1065
  // ============================================================
319
1066
 
320
- var server = {
1067
+ // ---- server.setTimeout / server.timeout — node:http's ----
1068
+ //
1069
+ // Inactivity on a connection's socket: after `serverTimeout` ms with nothing
1070
+ // read or written, 'timeout' is emitted with the socket — and if nothing
1071
+ // listens for it, the socket is destroyed. 0 (the default) disables it.
1072
+ // Applied when a connection is accepted, so a change affects new
1073
+ // connections only — as in Node. TCP connections (HTTP/1.1, HTTP/2); an
1074
+ // HTTP/3 connection has QUIC's own idle timeout. This used to only register
1075
+ // the callback: no connection ever timed out.
1076
+ var serverTimeout = 0;
1077
+ function armSocketTimeout(sock) {
1078
+ if (!(serverTimeout > 0) || typeof sock.setTimeout !== 'function') return;
1079
+ sock.setTimeout(serverTimeout);
1080
+ sock.on('timeout', function () {
1081
+ if (ev.listenerCount('timeout') > 0) ev.emit('timeout', sock);
1082
+ else { try { sock.destroy(); } catch (e) {} }
1083
+ });
1084
+ }
1085
+
1086
+ var api = {
321
1087
  listen: listen,
322
1088
  close: close,
323
1089
  address: address,
324
- on: function (name, fn) { ev.on(name, fn); return server; },
325
- once: function (name, fn) { ev.once(name, fn); return server; },
326
- off: function (name, fn) { ev.off(name, fn); return server; },
327
- removeListener: function (name, fn) { ev.removeListener(name, fn); return server; },
328
- emit: function () { ev.emit.apply(ev, arguments); return server; },
329
- setTimeout: function (msecs, callback) {
330
- if (tcpServer && typeof tcpServer.setTimeout === 'function') {
331
- tcpServer.setTimeout(msecs, callback);
1090
+ handleConnection: handleConnection,
1091
+ closeIdleConnections: closeIdleConnections,
1092
+ closeAllConnections: closeAllConnections,
1093
+ getConnections: getConnections,
1094
+ get listening() { return listening; },
1095
+ get headersTimeout() { return limits.headersTimeout; },
1096
+ set headersTimeout(v) { setLimit('headersTimeout', v); },
1097
+ get requestTimeout() { return limits.requestTimeout; },
1098
+ set requestTimeout(v) { setLimit('requestTimeout', v); },
1099
+ get keepAliveTimeout() { return limits.keepAliveTimeout; },
1100
+ set keepAliveTimeout(v) { setLimit('keepAliveTimeout', v); },
1101
+ get maxHeadersCount() { return limits.maxHeadersCount; },
1102
+ set maxHeadersCount(v) { setLimit('maxHeadersCount', v); },
1103
+ get maxRequestsPerSocket() { return limits.maxRequestsPerSocket; },
1104
+ set maxRequestsPerSocket(v) { setLimit('maxRequestsPerSocket', v); },
1105
+ // node:net's: unref() lets the process exit while the server listens.
1106
+ ref: function () { ref = true; if (enableH3) h3srv.ref(); if (tcpServer) tcpServer.ref(); return server; },
1107
+ unref: function () { ref = false; if (enableH3) h3srv.unref(); if (tcpServer) tcpServer.unref(); return server; },
1108
+
1109
+ /**
1110
+ * The session ticket keys: 48 bytes, the key every ticket is sealed with
1111
+ * — TCP and QUIC alike. tls.Server's names and rules: setTicketKeys takes
1112
+ * a 48-byte Buffer and throws on anything else.
1113
+ *
1114
+ * A server that keeps its keys across restarts (a file, say) passes them
1115
+ * as options.ticketKeys, and can replace them while running: connections
1116
+ * from then on use the new key. Tickets sealed with the old one no longer
1117
+ * open — each of those clients does one full handshake and gets a new
1118
+ * ticket, as after a restart with random keys.
1119
+ */
1120
+ /**
1121
+ * node:tls's server.setSecureContext(options): the certificate from the
1122
+ * next handshake on — HTTP/1.1, HTTP/2 and HTTP/3 alike; connections open
1123
+ * keep theirs. { key, cert[, ca, passphrase] } (files' contents or paths,
1124
+ * as createServer takes them), or a quico.createSecureContext(). A bad
1125
+ * one throws here, and the one in use stays. The way to renew a
1126
+ * certificate (Let's Encrypt) without a restart.
1127
+ */
1128
+ setSecureContext: function (o) {
1129
+ if (!hasTlsCredentials) throw new Error('setSecureContext: this server has no TLS (it was made without key/cert or SNICallback)');
1130
+ var ctx = toContext(o, 'setSecureContext');
1131
+ defaultContext = ctx;
1132
+ },
1133
+ /**
1134
+ * node:tls's server.addContext(hostname, context): this certificate for
1135
+ * that name (the client's SNI) — '*.example.com' covering one label —
1136
+ * on every version, from the next handshake on. The latest added wins
1137
+ * where two match. Not consulted when createServer had an SNICallback
1138
+ * (as in Node).
1139
+ */
1140
+ addContext: function (hostname, ctx) {
1141
+ if (typeof hostname !== 'string' || hostname === '') throw new TypeError('addContext: hostname must be a non-empty string');
1142
+ if (!hasTlsCredentials) throw new Error('addContext: this server has no TLS (it was made without key/cert or SNICallback)');
1143
+ var re = new RegExp('^' + hostname.replace(/([.^$+?\-\\[\]{}()|])/g, '\\$1').replace(/\*/g, '[^.]*') + '$', 'i');
1144
+ sniContexts.push([re, toContext(ctx, 'addContext')]);
1145
+ },
1146
+ getTicketKeys: function () { return ticketKeys; },
1147
+ setTicketKeys: function (keys) {
1148
+ if (!Buffer.isBuffer(keys) || keys.length !== 48) {
1149
+ throw new TypeError('ticket keys must be a 48-byte Buffer');
332
1150
  }
1151
+ ticketKeys = keys;
1152
+ if (tcpServer) tcpServer.setTicketKeys(keys);
1153
+ h3srv.setTicketKeys(keys);
1154
+ },
1155
+
1156
+ /**
1157
+ * The Encrypted Client Hello keys, TCP and QUIC alike. Rotation without a
1158
+ * restart: publish the new config in DNS (ech= in the HTTPS record), then
1159
+ * setEchKeys with the new key and the old one kept as retry: false until
1160
+ * the old record's TTL has run out — clients still holding it connect
1161
+ * meanwhile. New connections use the new set; a bad key throws and the
1162
+ * old set stays.
1163
+ */
1164
+ getEchKeys: function () { return ech; },
1165
+ setEchKeys: function (keys) {
1166
+ if (keys) lemonTls.checkEchKeys(keys);
1167
+ ech = keys || null;
1168
+ if (tcpServer) tcpServer.setEchKeys(ech);
1169
+ h3srv.setEchKeys(ech);
1170
+ },
1171
+
1172
+ /**
1173
+ * The secret HTTP/3 stateless reset tokens are derived from (RFC 9000
1174
+ * §10.3) — node:quic's resetTokenSecret, at least 16 bytes. Kept across
1175
+ * restarts (options.resetTokenSecret, or set here once read), it lets a
1176
+ * restarted server tell each browser still holding a connection to the
1177
+ * old one that it is gone, instead of leaving it to time out.
1178
+ */
1179
+ /**
1180
+ * HTTP/3 address validation with Retry (RFC 9000 §8.1.2): while on, each
1181
+ * new client proves its address with one extra round trip before any
1182
+ * connection state or key exchange is spent on it — the defence against
1183
+ * a flood of Initials from spoofed addresses. quico never turns it on by
1184
+ * itself; the application decides when (always, never, under load) and
1185
+ * can switch it at any moment. Applies from the next new client on.
1186
+ */
1187
+ getValidateAddress: function () { return h3srv.getValidateAddress(); },
1188
+ setValidateAddress: function (on) { h3srv.setValidateAddress(on); },
1189
+
1190
+ /**
1191
+ * HTTP/3 handshakes under way: connections made and not yet complete
1192
+ * (the client's Finished verified). The application decides what load
1193
+ * is — 'handshakeLoad' ({ pending, high }) tells it when the count
1194
+ * reaches handshakeLoadThreshold (high: true) and when it is back under
1195
+ * half of it (high: false) — and what to do: setOptions({
1196
+ * validateAddress: true }), say. maxHandshakesPerAddress caps them per
1197
+ * client address; past it an Initial makes no connection. 0 turns either
1198
+ * off. Both change with setOptions, at once.
1199
+ */
1200
+ getPendingHandshakes: function () { return h3srv.getPendingHandshakes(); },
1201
+ /**
1202
+ * The options that can change while the server runs, by createServer's
1203
+ * names — one call, a partial object, checked whole before anything
1204
+ * changes (a bad value: TypeError / RangeError, nothing changed):
1205
+ * validateAddress, handshakeLoadThreshold, maxHandshakesPerAddress
1206
+ * the server's (from the next client; the threshold at once);
1207
+ * startupCap, initialCongestionWindow, carefulResume, pmtuDiscovery,
1208
+ * maxUdpPayloadSize, initialRtt, reorderWindow
1209
+ * what new HTTP/3 connections are made with — open ones keep
1210
+ * theirs (conn.setOptions, or connectionOptions, for one).
1211
+ * A name that cannot change this way says what can (cert:
1212
+ * setSecureContext()). getOptions(): all of them, as they are now.
1213
+ */
1214
+ getOptions: function () { return h3srv.getOptions(); },
1215
+ setOptions: function (o) { h3srv.setOptions(o); return server; },
1216
+
1217
+ getResetTokenSecret: function () { return h3srv.getResetTokenSecret(); },
1218
+ setResetTokenSecret: function (secret) { h3srv.setResetTokenSecret(secret); },
1219
+
1220
+ /**
1221
+ * The secret HTTP/3 address-validation tokens — Retry's, and NEW_TOKEN's
1222
+ * for a client's next connection — are sealed under: node:quic's
1223
+ * tokenSecret, at least 16 bytes. Random unless given. Servers that share
1224
+ * it open each other's tokens — several servers on one port (a thread
1225
+ * each, SO_REUSEPORT), or one restarted.
1226
+ */
1227
+ getTokenSecret: function () { return h3srv.getTokenSecret(); },
1228
+ setTokenSecret: function (secret) { h3srv.setTokenSecret(secret); },
1229
+
1230
+ /**
1231
+ * Carry another protocol over this server's TLS port, beside HTTP.
1232
+ *
1233
+ * A client that negotiates `name` by ALPN is handed to `handler(socket)`
1234
+ * after the TLS handshake, as a decrypted Duplex. `options.sniff(bytes)`
1235
+ * also claims connections that negotiated no ALPN, by their first bytes —
1236
+ * how TURN over TLS on 443 is recognised from a browser, which offers no
1237
+ * ALPN for it. `options.paused` hands the socket over paused for the
1238
+ * handler to resume. See register() in tcp_listener.
1239
+ *
1240
+ * The name is added to the ALPN list the server advertises, after the
1241
+ * HTTP versions, so an HTTPS client still gets h2 or HTTP/1.1.
1242
+ */
1243
+ addProtocol: function (name, handler, options) {
1244
+ if (!tcpServer) throw new Error('addProtocol needs the TCP side (http1 or http2 enabled)');
1245
+ if (!hasTlsCredentials) throw new Error('addProtocol needs TLS: without it there is no ALPN, and nothing to sniff after');
1246
+ tcpServer.register(name, handler, options);
1247
+ return server;
1248
+ },
1249
+
1250
+ /**
1251
+ * CONNECT-UDP's figures, across this server's tunnels: { active,
1252
+ * accepted, rejected, closed, packetsFromClient, bytesFromClient,
1253
+ * packetsToClient, bytesToClient, droppedFromClient, droppedToClient }
1254
+ * — those of tunnels still open included. Each tunnel has its own
1255
+ * (tunnel.stats, and the 'close' event's argument).
1256
+ */
1257
+ getConnectUdpStats: function () { return udpProxy.stats(); },
1258
+
1259
+ /**
1260
+ * The server's counters since it was made, a copy: connections and TLS
1261
+ * handshakes over QUIC and TCP, the protocols they spoke, 0-RTT taken,
1262
+ * refused and answered 425, how connections ended by code (QUIC and
1263
+ * HTTP/3 CONNECTION_CLOSE, HTTP/2 GOAWAY, HTTP/1.1 requests refused),
1264
+ * streams reset by code, datagrams held at the anti-amplification limit,
1265
+ * internal errors caught. Counts only — no addresses, no names.
1266
+ * See server_stats.js.
1267
+ */
1268
+ getStats: function () { return stats.snapshot(); },
1269
+
1270
+ setTimeout: function (msecs, callback) {
1271
+ if (typeof msecs === 'function') { callback = msecs; msecs = undefined; }
1272
+ if (msecs !== undefined) serverTimeout = Math.max(0, Number(msecs) || 0);
1273
+ if (typeof callback === 'function') ev.on('timeout', callback);
333
1274
  return server;
334
1275
  },
1276
+ get timeout() { return serverTimeout; },
1277
+ set timeout(ms) { serverTimeout = Math.max(0, Number(ms) || 0); },
335
1278
 
336
1279
  /** Feed an incoming UDP packet from a shared/external socket.
337
1280
  * rinfo must be in Node's dgram format: { address, port, family, size }.
@@ -340,13 +1283,42 @@ function createServer(options, handler) {
340
1283
  return h3srv.handlePacket(msg, rinfo);
341
1284
  },
342
1285
 
343
- /** Returns true if the 5-tuple matches an active QUIC connection.
344
- * Used by demuxers for routing decisions on shared UDP ports. */
345
- hasConnection: function (rinfo) {
346
- return h3srv.hasConnection(rinfo);
1286
+ /** hasConnection(rinfo): the address matches an active QUIC connection.
1287
+ * hasConnection(rinfo, msg): the datagram is addressed to one, by its
1288
+ * connection ID — for servers sharing a port across threads
1289
+ * (quic_server.js). Used by demuxers for routing decisions on shared
1290
+ * UDP ports. */
1291
+ hasConnection: function (rinfo, msg) {
1292
+ return h3srv.hasConnection(rinfo, msg);
347
1293
  }
348
1294
  };
349
1295
 
1296
+ // The server IS an EventEmitter, as node:http's is (a net.Server): every
1297
+ // method of one, and instanceof. It was a plain object passing five of
1298
+ // them on to an emitter inside — socket.io, which takes the 'request'
1299
+ // listeners off (listeners(), removeAllListeners()) to put its own first,
1300
+ // failed at once.
1301
+ var server = ev;
1302
+ Object.defineProperties(server, Object.getOwnPropertyDescriptors(api));
1303
+ // server.on('request', app) — the other way an app is attached: an Express
1304
+ // app is adopted (adoptApp, streams.js), whichever method adds it.
1305
+ ['on', 'addListener', 'once', 'prependListener', 'prependOnceListener'].forEach(function (m) {
1306
+ var base = EventEmitter.prototype[m];
1307
+ Object.defineProperty(server, m, {
1308
+ configurable: true, writable: true,
1309
+ value: function (name, fn) { if (name === 'request') adoptApp(fn); return base.call(this, name, fn); }
1310
+ });
1311
+ });
1312
+ // node:net's: `await using server = createServer(...)` closes it.
1313
+ if (typeof Symbol.asyncDispose === 'symbol') {
1314
+ server[Symbol.asyncDispose] = function () {
1315
+ return new Promise(function (resolve, reject) {
1316
+ if (closed) { resolve(); return; }
1317
+ close(function (err) { if (err) reject(err); else resolve(); });
1318
+ });
1319
+ };
1320
+ }
1321
+
350
1322
  return server;
351
1323
  }
352
1324