quico 0.7.0 → 0.9.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
@@ -21,6 +21,7 @@ import { createServer as createH3Server } from './h3_server.js';
21
21
  import { createServer as createH2Server } from './h2_server.js';
22
22
  import { createServer as createH1Server } from './h1_server.js';
23
23
  import { createTcpListener } from './tcp_listener.js';
24
+ import { createSecureContext } from './tls_bridge.js';
24
25
  import { IncomingMessage, ServerResponse, _makeSocketStub, adoptApp } from './streams.js';
25
26
  import { creditAsRead } from './utils.js';
26
27
  import { runEarlyDataFilter } from './http_common.js';
@@ -159,6 +160,9 @@ function createServer(options, handler) {
159
160
 
160
161
  var ev = new EventEmitter();
161
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()
162
166
 
163
167
  // ---- TLS credentials ----
164
168
  var tlsKey = options.key || null;
@@ -198,6 +202,48 @@ function createServer(options, handler) {
198
202
  tlsCert = fs.readFileSync(tlsCert);
199
203
  }
200
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
+ // createSecureContext (lemon-tls's) refuses a key that is not the
222
+ // certificate's (ERR_OSSL_X509_KEY_VALUES_MISMATCH) - before it replaces
223
+ // anything, not found out by every client's handshake failing.
224
+ var ctx = createSecureContext(Object.assign({ passphrase: options.passphrase }, o));
225
+ // Its chain compressed now, not in a client's handshake (RFC 8879).
226
+ if (typeof lemonTls.precompressCertificate === 'function' && !(Array.isArray(options.certificateCompression) && options.certificateCompression.length === 0)) {
227
+ try { lemonTls.precompressCertificate(ctx, options.certificateCompression || undefined); } catch (e) {}
228
+ }
229
+ return ctx;
230
+ }
231
+ function contextFor(servername) {
232
+ var name = String(servername || '');
233
+ for (var i = sniContexts.length - 1; i >= 0; i--) if (sniContexts[i][0].test(name)) return sniContexts[i][1];
234
+ return defaultContext;
235
+ }
236
+ function serverSni(servername, cb, info) {
237
+ if (userSni) {
238
+ return userSni(servername, function (err, ctx) {
239
+ if (err) cb(err);
240
+ else cb(null, ctx || defaultContext);
241
+ }, info);
242
+ }
243
+ cb(null, contextFor(servername));
244
+ }
245
+ if (tlsKey && tlsCert) defaultContext = toContext({ key: tlsKey, cert: tlsCert, ca: tlsCa }, 'createServer');
246
+
201
247
 
202
248
  // ============================================================
203
249
  // HTTP/3 — delegates to existing h3_server.js
@@ -309,7 +355,7 @@ function createServer(options, handler) {
309
355
  key: tlsKey,
310
356
  cert: tlsCert,
311
357
  ca: tlsCa,
312
- SNICallback: options.SNICallback || null,
358
+ SNICallback: (tlsKey && tlsCert) || userSni ? serverSni : null,
313
359
  ticketKeys: ticketKeys,
314
360
  ticketData: ticketData,
315
361
  ech: ech,
@@ -317,6 +363,9 @@ function createServer(options, handler) {
317
363
  // The HTTP/3 side's stateless reset secret (see quic_server). Random when
318
364
  // not given.
319
365
  resetTokenSecret: options.resetTokenSecret || null,
366
+ // The secret HTTP/3 address-validation tokens (Retry, NEW_TOKEN) are
367
+ // sealed under (see quic_server). Random when not given.
368
+ tokenSecret: options.tokenSecret || null,
320
369
  // HTTP/3 address validation with Retry — node:quic's validateAddress.
321
370
  // Off unless set; see setValidateAddress below.
322
371
  validateAddress: options.validateAddress === true,
@@ -353,6 +402,8 @@ function createServer(options, handler) {
353
402
  maxUdpPayloadSize: options.maxUdpPayloadSize,
354
403
  initialRtt: options.initialRtt,
355
404
  reorderWindow: options.reorderWindow,
405
+ // The UDP sockets' buffers, bytes: 4 MB by default; 0, the OS's (utils.js).
406
+ socketBufferSize: options.socketBufferSize,
356
407
  // Per QUIC connection, asked once its ClientHello is in: (info, callback)
357
408
  // → { transportParams, initialCongestionWindow, carefulResume, startupCap,
358
409
  // pmtuDiscovery, maxUdpPayloadSize, initialRtt, reorderWindow }.
@@ -539,6 +590,10 @@ function createServer(options, handler) {
539
590
  callback();
540
591
  };
541
592
 
593
+ // 102 Processing — the HTTP/3 one.
594
+ if (plainRes.writeProcessing) {
595
+ res.writeProcessing = function () { if (!res.headersSent) plainRes.writeProcessing(); };
596
+ }
542
597
  // 103 Early Hints — the HTTP/3 one, written on the same stream.
543
598
  if (plainRes.writeEarlyHints) {
544
599
  res.writeEarlyHints = function (hints, callback) {
@@ -549,7 +604,8 @@ function createServer(options, handler) {
549
604
 
550
605
  // ---- Methods of the HTTP/3 response: its priority, and WebTransport's ----
551
606
  ['setPriority', 'sendDatagram', 'maxDatagramSize', 'createBidirectionalStream', 'createUnidirectionalStream',
552
- 'createSendGroup', 'canOpenStream', 'setWebTransportProtocol', 'getStats', 'closeSession', 'drainSession'].forEach(function (m) {
607
+ 'createSendGroup', 'canOpenStream', 'setWebTransportProtocol', 'getStats', 'closeSession', 'drainSession',
608
+ 'exportKeyingMaterial'].forEach(function (m) {
553
609
  if (typeof plainRes[m] === 'function') res[m] = plainRes[m].bind(plainRes);
554
610
  });
555
611
  if ('datagrams' in plainRes) {
@@ -657,7 +713,34 @@ function createServer(options, handler) {
657
713
  var tcpServer = null;
658
714
  var hasTlsCredentials = (tlsKey && tlsCert) || options.SNICallback;
659
715
 
716
+ // ---- A WebTransport session asked for over HTTP/1.1 or HTTP/2 ----
717
+ //
718
+ // WebTransport runs here over HTTP/3 only (an extended CONNECT, RFC 9220;
719
+ // HTTP/2 advertises no WebTransport). A browser that cannot reach HTTP/3
720
+ // may still send its session request over TCP: Firefox 158, with HTTP/3
721
+ // excluded for the origin, sends a plain "GET /" with
722
+ // Sec-Fetch-Dest: webtransport on a new HTTP/1.1 connection. Handed to the
723
+ // application it is answered as a page, and that body lands in the
724
+ // browser's WebTransport session — Firefox aborts the whole browser on it
725
+ // (MOZ_RELEASE_ASSERT in WebTransportSessionProxy::OnDataAvailable).
726
+ //
727
+ // So the server answers it itself: 501 and no body (RFC 9110 §15.6.2 — the
728
+ // functionality asked for is not offered on this version). The session
729
+ // fails cleanly, and the client can fall back to whatever it has. The
730
+ // application never sees it. Two signs, either enough: the Fetch
731
+ // destination a browser sends (Sec-Fetch-Dest: webtransport), and an
732
+ // extended CONNECT for :protocol webtransport over HTTP/2.
733
+ function isWebTransportRequest(req) {
734
+ var h = req.headers || {};
735
+ return h['sec-fetch-dest'] === 'webtransport' || h[':protocol'] === 'webtransport';
736
+ }
737
+ function refuseWebTransport(req, res) {
738
+ req.resume();
739
+ try { res.writeHead(501, { 'content-length': '0', 'cache-control': 'no-store' }); res.end(); } catch (e) {}
740
+ }
741
+
660
742
  function onRequest(req, res) {
743
+ if (isWebTransportRequest(req)) { refuseWebTransport(req, res); return; }
661
744
  if (req.earlyData === true) { earlyGate(req, res); return; }
662
745
  runHandler(req, res);
663
746
  }
@@ -683,7 +766,9 @@ function createServer(options, handler) {
683
766
 
684
767
  if (needsTcp) {
685
768
  var shared = {
686
- shouldUpgradeCallback: shouldUpgradeCallback,
769
+ // An extended CONNECT for WebTransport over HTTP/2 is not taken up: it
770
+ // goes on as a request, and onRequest refuses it (above).
771
+ shouldUpgradeCallback: function (req) { return !isWebTransportRequest(req) && shouldUpgradeCallback(req); },
687
772
  // Not over cleartext: HTTP/3 is always TLS, and browsers take Alt-Svc
688
773
  // only from an https origin — on http:// it advertised a service that
689
774
  // could not exist there.
@@ -721,7 +806,7 @@ function createServer(options, handler) {
721
806
  key: tlsKey,
722
807
  cert: tlsCert,
723
808
  ca: tlsCa,
724
- SNICallback: options.SNICallback,
809
+ SNICallback: (tlsKey && tlsCert) || userSni ? serverSni : null,
725
810
  ticketKeys: ticketKeys,
726
811
  ticketData: ticketData,
727
812
  ech: ech,
@@ -773,8 +858,10 @@ function createServer(options, handler) {
773
858
  // are only meaningful with TLS: without it there is no ALPN to carry them.
774
859
  for (var a = 0; a < tcpAlpn.length; a++) {
775
860
  var proto = tcpAlpn[a];
776
- if (proto === 'h2') tcpServer.register('h2', h2srv.handleConnection);
777
- else if (proto === 'http/1.1') tcpServer.register('http/1.1', h1srv.handleConnection);
861
+ // managesClose: each closes its own connections (beginClose) — the
862
+ // ones in flight finish first.
863
+ if (proto === 'h2') tcpServer.register('h2', h2srv.handleConnection, { managesClose: true });
864
+ else if (proto === 'http/1.1') tcpServer.register('http/1.1', h1srv.handleConnection, { managesClose: true });
778
865
  else if (hasTlsCredentials) tcpServer.register(proto, closeAfterHandshake);
779
866
  }
780
867
 
@@ -847,8 +934,10 @@ function createServer(options, handler) {
847
934
  remaining--;
848
935
  if (remaining <= 0 && !called) {
849
936
  called = true;
937
+ listening = true;
938
+ closed = false;
850
939
  // After listen(0) the OS chose the port; Alt-Svc must name that one.
851
- if (port === 0 && tcpServer && tcpServer.address()) altSvcPort = tcpServer.address().port;
940
+ if (port === 0) { var a = address(); if (a && a.port) altSvcPort = a.port; }
852
941
  ev.emit('listening');
853
942
  if (typeof callback === 'function') callback();
854
943
  }
@@ -858,27 +947,112 @@ function createServer(options, handler) {
858
947
  // asynchronously, like a real bind would.
859
948
  if (remaining === 0) { remaining = 1; setImmediate(done); return server; }
860
949
 
950
+ if (ref === false) { if (enableH3) h3srv.unref(); if (tcpServer) tcpServer.unref(); }
951
+ // No host: TCP as node:net, '::' where the host has IPv6 (both
952
+ // families), else '0.0.0.0' — as UDP. It was '0.0.0.0': HTTP/1.1 and
953
+ // HTTP/2 on IPv4 alone, HTTP/3 on both.
954
+ var tcpHost = host || undefined;
955
+ // listen(0): TCP first, then UDP on the port the OS gave TCP — the port
956
+ // Alt-Svc names and address() reports. Each took a port of its own, and
957
+ // Alt-Svc named TCP's: a browser never reached HTTP/3.
958
+ if (bindUdp && needsTcp && port === 0) {
959
+ tcpServer.listen(0, tcpHost, function () {
960
+ done();
961
+ h3srv.listen(tcpServer.address().port, host, done);
962
+ });
963
+ return server;
964
+ }
861
965
  if (bindUdp) h3srv.listen(port, host, done);
862
- if (needsTcp) tcpServer.listen(port, host || '0.0.0.0', done);
966
+ if (needsTcp) tcpServer.listen(port, tcpHost, done);
863
967
 
864
968
  return server;
865
969
  }
866
970
 
971
+ /**
972
+ * As node:http's close(): no new connections; the idle ones closed now;
973
+ * each with a request in flight closes once it is answered — HTTP/2 and
974
+ * HTTP/3 with GOAWAY (RFC 9113 §6.8, RFC 9114 §5.2), HTTP/1.1 at the end
975
+ * of its response. The callback, and 'close', when every connection is
976
+ * gone. It used to close every connection at once, on all three versions:
977
+ * a response being written was cut off (HTTP/1.1: ECONNRESET; HTTP/3: the
978
+ * client waited for the rest until a timeout). A second close(): the
979
+ * callback gets ERR_SERVER_NOT_RUNNING, as node:http's.
980
+ */
867
981
  function close(callback) {
982
+ if (closed) {
983
+ if (typeof callback === 'function') {
984
+ var e = new Error('Server is not running.'); e.code = 'ERR_SERVER_NOT_RUNNING';
985
+ process.nextTick(callback, e);
986
+ }
987
+ return server;
988
+ }
989
+ closed = true;
990
+ listening = false;
868
991
  // h3srv is closed whenever HTTP/3 is on, bound or not: in external mode it
869
992
  // still runs a sweep timer, started at creation.
870
993
  var remaining = (enableH3 ? 1 : 0) + (tcpServer ? 1 : 0);
871
994
 
872
995
  function onClose() {
873
996
  remaining--;
874
- if (remaining <= 0 && typeof callback === 'function') callback();
997
+ if (remaining > 0) return;
998
+ ev.emit('close');
999
+ if (typeof callback === 'function') callback();
875
1000
  }
876
1001
 
877
- if (remaining === 0) { if (typeof callback === 'function') callback(); return; }
1002
+ if (remaining === 0) { remaining = 1; process.nextTick(onClose); return server; }
878
1003
  if (enableH3) h3srv.close(onClose);
879
- // tcp_listener.close() ends idle connections, including ones handed in
880
- // through handleConnection, and closes its port only if it bound one.
881
- if (tcpServer) { tcpServer.close(onClose); tcpServer = null; }
1004
+ if (tcpServer) {
1005
+ if (h1srv) h1srv.beginClose();
1006
+ if (h2srv) h2srv.beginClose();
1007
+ // The port, and what tcp_listener holds that HTTP did not claim.
1008
+ // Its callback waits for every socket, as node:net's.
1009
+ tcpServer.close(onClose);
1010
+ tcpServer = null;
1011
+ }
1012
+ return server;
1013
+ }
1014
+
1015
+ // node:http's: the connections with nothing in flight closed now (Node
1016
+ // does this from close() too); all of them, in flight or not. A socket
1017
+ // handed to 'upgrade' over HTTP/1.1 is not this server's any more and
1018
+ // stays; HTTP/2 and HTTP/3 carry WebSockets and WebTransport on the
1019
+ // connection itself, and close with it.
1020
+ function closeIdleConnections() {
1021
+ if (h1srv) h1srv.closeIdleConnections();
1022
+ if (h2srv) h2srv.closeIdleConnections();
1023
+ if (enableH3) h3srv.closeIdleConnections();
1024
+ }
1025
+ function closeAllConnections() {
1026
+ if (h1srv) h1srv.closeAllConnections();
1027
+ if (h2srv) h2srv.closeAllConnections();
1028
+ if (enableH3) h3srv.closeAllConnections();
1029
+ }
1030
+
1031
+ // node:net's getConnections(cb): TCP connections and QUIC ones.
1032
+ function getConnections(cb) {
1033
+ var n = (h1srv ? h1srv.connectionCount : 0) + (h2srv ? h2srv.connectionCount : 0) + (enableH3 ? h3srv.connectionCount : 0);
1034
+ if (typeof cb === 'function') process.nextTick(cb, null, n);
1035
+ return server;
1036
+ }
1037
+
1038
+ // node:http's timeouts and limits, readable and settable while the server
1039
+ // runs, as server.keepAliveTimeout = 5000. Each version takes what it has:
1040
+ // keepAliveTimeout and maxHeadersCount are HTTP/1.1's; the rest all three.
1041
+ var limits = {
1042
+ headersTimeout: options.headersTimeout != null ? options.headersTimeout : 60000,
1043
+ requestTimeout: options.requestTimeout != null ? options.requestTimeout : 300000,
1044
+ keepAliveTimeout: options.keepAliveTimeout != null ? options.keepAliveTimeout : 5000,
1045
+ maxHeadersCount: options.maxHeaderCount != null ? options.maxHeaderCount : 2000,
1046
+ maxRequestsPerSocket: options.maxRequestsPerSocket != null ? options.maxRequestsPerSocket : 0
1047
+ };
1048
+ function setLimit(name, value) {
1049
+ if (typeof value !== 'number' || !(value >= 0)) throw new RangeError('server.' + name + ' must be a non-negative number, got ' + value);
1050
+ limits[name] = value;
1051
+ var o = {};
1052
+ o[name === 'maxHeadersCount' ? 'maxHeaderCount' : name] = value;
1053
+ if (h1srv) h1srv.setLimits(o);
1054
+ if (h2srv) h2srv.setLimits(o);
1055
+ if (enableH3) h3srv.setLimits(o);
882
1056
  }
883
1057
 
884
1058
  function address() {
@@ -888,6 +1062,9 @@ function createServer(options, handler) {
888
1062
  var addr = tcpServer.address();
889
1063
  if (addr) return addr;
890
1064
  }
1065
+ // HTTP/3 alone: the UDP socket's (the same port, when both listen).
1066
+ var a3 = enableH3 && h3srv.address ? h3srv.address() : null;
1067
+ if (a3) return a3;
891
1068
  return { address: '0.0.0.0', family: 'IPv4', port: altSvcPort };
892
1069
  }
893
1070
 
@@ -915,11 +1092,28 @@ function createServer(options, handler) {
915
1092
  });
916
1093
  }
917
1094
 
918
- var server = {
1095
+ var api = {
919
1096
  listen: listen,
920
1097
  close: close,
921
1098
  address: address,
922
1099
  handleConnection: handleConnection,
1100
+ closeIdleConnections: closeIdleConnections,
1101
+ closeAllConnections: closeAllConnections,
1102
+ getConnections: getConnections,
1103
+ get listening() { return listening; },
1104
+ get headersTimeout() { return limits.headersTimeout; },
1105
+ set headersTimeout(v) { setLimit('headersTimeout', v); },
1106
+ get requestTimeout() { return limits.requestTimeout; },
1107
+ set requestTimeout(v) { setLimit('requestTimeout', v); },
1108
+ get keepAliveTimeout() { return limits.keepAliveTimeout; },
1109
+ set keepAliveTimeout(v) { setLimit('keepAliveTimeout', v); },
1110
+ get maxHeadersCount() { return limits.maxHeadersCount; },
1111
+ set maxHeadersCount(v) { setLimit('maxHeadersCount', v); },
1112
+ get maxRequestsPerSocket() { return limits.maxRequestsPerSocket; },
1113
+ set maxRequestsPerSocket(v) { setLimit('maxRequestsPerSocket', v); },
1114
+ // node:net's: unref() lets the process exit while the server listens.
1115
+ ref: function () { ref = true; if (enableH3) h3srv.ref(); if (tcpServer) tcpServer.ref(); return server; },
1116
+ unref: function () { ref = false; if (enableH3) h3srv.unref(); if (tcpServer) tcpServer.unref(); return server; },
923
1117
 
924
1118
  /**
925
1119
  * The session ticket keys: 48 bytes, the key every ticket is sealed with
@@ -932,6 +1126,32 @@ function createServer(options, handler) {
932
1126
  * open — each of those clients does one full handshake and gets a new
933
1127
  * ticket, as after a restart with random keys.
934
1128
  */
1129
+ /**
1130
+ * node:tls's server.setSecureContext(options): the certificate from the
1131
+ * next handshake on — HTTP/1.1, HTTP/2 and HTTP/3 alike; connections open
1132
+ * keep theirs. { key, cert[, ca, passphrase] } (files' contents or paths,
1133
+ * as createServer takes them), or a quico.createSecureContext(). A bad
1134
+ * one throws here, and the one in use stays. The way to renew a
1135
+ * certificate (Let's Encrypt) without a restart.
1136
+ */
1137
+ setSecureContext: function (o) {
1138
+ if (!hasTlsCredentials) throw new Error('setSecureContext: this server has no TLS (it was made without key/cert or SNICallback)');
1139
+ var ctx = toContext(o, 'setSecureContext');
1140
+ defaultContext = ctx;
1141
+ },
1142
+ /**
1143
+ * node:tls's server.addContext(hostname, context): this certificate for
1144
+ * that name (the client's SNI) — '*.example.com' covering one label —
1145
+ * on every version, from the next handshake on. The latest added wins
1146
+ * where two match. Not consulted when createServer had an SNICallback
1147
+ * (as in Node).
1148
+ */
1149
+ addContext: function (hostname, ctx) {
1150
+ if (typeof hostname !== 'string' || hostname === '') throw new TypeError('addContext: hostname must be a non-empty string');
1151
+ if (!hasTlsCredentials) throw new Error('addContext: this server has no TLS (it was made without key/cert or SNICallback)');
1152
+ var re = new RegExp('^' + hostname.replace(/([.^$+?\-\\[\]{}()|])/g, '\\$1').replace(/\*/g, '[^.]*') + '$', 'i');
1153
+ sniContexts.push([re, toContext(ctx, 'addContext')]);
1154
+ },
935
1155
  getTicketKeys: function () { return ticketKeys; },
936
1156
  setTicketKeys: function (keys) {
937
1157
  if (!Buffer.isBuffer(keys) || keys.length !== 48) {
@@ -1006,6 +1226,16 @@ function createServer(options, handler) {
1006
1226
  getResetTokenSecret: function () { return h3srv.getResetTokenSecret(); },
1007
1227
  setResetTokenSecret: function (secret) { h3srv.setResetTokenSecret(secret); },
1008
1228
 
1229
+ /**
1230
+ * The secret HTTP/3 address-validation tokens — Retry's, and NEW_TOKEN's
1231
+ * for a client's next connection — are sealed under: node:quic's
1232
+ * tokenSecret, at least 16 bytes. Random unless given. Servers that share
1233
+ * it open each other's tokens — several servers on one port (a thread
1234
+ * each, SO_REUSEPORT), or one restarted.
1235
+ */
1236
+ getTokenSecret: function () { return h3srv.getTokenSecret(); },
1237
+ setTokenSecret: function (secret) { h3srv.setTokenSecret(secret); },
1238
+
1009
1239
  /**
1010
1240
  * Carry another protocol over this server's TLS port, beside HTTP.
1011
1241
  *
@@ -1046,12 +1276,6 @@ function createServer(options, handler) {
1046
1276
  */
1047
1277
  getStats: function () { return stats.snapshot(); },
1048
1278
 
1049
- // server.on('request', app) — the other way an app is attached.
1050
- on: function (name, fn) { if (name === 'request') adoptApp(fn); ev.on(name, fn); return server; },
1051
- once: function (name, fn) { if (name === 'request') adoptApp(fn); ev.once(name, fn); return server; },
1052
- off: function (name, fn) { ev.off(name, fn); return server; },
1053
- removeListener: function (name, fn) { ev.removeListener(name, fn); return server; },
1054
- emit: function () { ev.emit.apply(ev, arguments); return server; },
1055
1279
  setTimeout: function (msecs, callback) {
1056
1280
  if (typeof msecs === 'function') { callback = msecs; msecs = undefined; }
1057
1281
  if (msecs !== undefined) serverTimeout = Math.max(0, Number(msecs) || 0);
@@ -1068,13 +1292,42 @@ function createServer(options, handler) {
1068
1292
  return h3srv.handlePacket(msg, rinfo);
1069
1293
  },
1070
1294
 
1071
- /** Returns true if the 5-tuple matches an active QUIC connection.
1072
- * Used by demuxers for routing decisions on shared UDP ports. */
1073
- hasConnection: function (rinfo) {
1074
- return h3srv.hasConnection(rinfo);
1295
+ /** hasConnection(rinfo): the address matches an active QUIC connection.
1296
+ * hasConnection(rinfo, msg): the datagram is addressed to one, by its
1297
+ * connection ID — for servers sharing a port across threads
1298
+ * (quic_server.js). Used by demuxers for routing decisions on shared
1299
+ * UDP ports. */
1300
+ hasConnection: function (rinfo, msg) {
1301
+ return h3srv.hasConnection(rinfo, msg);
1075
1302
  }
1076
1303
  };
1077
1304
 
1305
+ // The server IS an EventEmitter, as node:http's is (a net.Server): every
1306
+ // method of one, and instanceof. It was a plain object passing five of
1307
+ // them on to an emitter inside — socket.io, which takes the 'request'
1308
+ // listeners off (listeners(), removeAllListeners()) to put its own first,
1309
+ // failed at once.
1310
+ var server = ev;
1311
+ Object.defineProperties(server, Object.getOwnPropertyDescriptors(api));
1312
+ // server.on('request', app) — the other way an app is attached: an Express
1313
+ // app is adopted (adoptApp, streams.js), whichever method adds it.
1314
+ ['on', 'addListener', 'once', 'prependListener', 'prependOnceListener'].forEach(function (m) {
1315
+ var base = EventEmitter.prototype[m];
1316
+ Object.defineProperty(server, m, {
1317
+ configurable: true, writable: true,
1318
+ value: function (name, fn) { if (name === 'request') adoptApp(fn); return base.call(this, name, fn); }
1319
+ });
1320
+ });
1321
+ // node:net's: `await using server = createServer(...)` closes it.
1322
+ if (typeof Symbol.asyncDispose === 'symbol') {
1323
+ server[Symbol.asyncDispose] = function () {
1324
+ return new Promise(function (resolve, reject) {
1325
+ if (closed) { resolve(); return; }
1326
+ close(function (err) { if (err) reject(err); else resolve(); });
1327
+ });
1328
+ };
1329
+ }
1330
+
1078
1331
  return server;
1079
1332
  }
1080
1333