quico 0.7.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
@@ -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,58 @@ 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
+ 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
+
201
257
 
202
258
  // ============================================================
203
259
  // HTTP/3 — delegates to existing h3_server.js
@@ -309,7 +365,7 @@ function createServer(options, handler) {
309
365
  key: tlsKey,
310
366
  cert: tlsCert,
311
367
  ca: tlsCa,
312
- SNICallback: options.SNICallback || null,
368
+ SNICallback: (tlsKey && tlsCert) || userSni ? serverSni : null,
313
369
  ticketKeys: ticketKeys,
314
370
  ticketData: ticketData,
315
371
  ech: ech,
@@ -317,6 +373,9 @@ function createServer(options, handler) {
317
373
  // The HTTP/3 side's stateless reset secret (see quic_server). Random when
318
374
  // not given.
319
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,
320
379
  // HTTP/3 address validation with Retry — node:quic's validateAddress.
321
380
  // Off unless set; see setValidateAddress below.
322
381
  validateAddress: options.validateAddress === true,
@@ -539,6 +598,10 @@ function createServer(options, handler) {
539
598
  callback();
540
599
  };
541
600
 
601
+ // 102 Processing — the HTTP/3 one.
602
+ if (plainRes.writeProcessing) {
603
+ res.writeProcessing = function () { if (!res.headersSent) plainRes.writeProcessing(); };
604
+ }
542
605
  // 103 Early Hints — the HTTP/3 one, written on the same stream.
543
606
  if (plainRes.writeEarlyHints) {
544
607
  res.writeEarlyHints = function (hints, callback) {
@@ -549,7 +612,8 @@ function createServer(options, handler) {
549
612
 
550
613
  // ---- Methods of the HTTP/3 response: its priority, and WebTransport's ----
551
614
  ['setPriority', 'sendDatagram', 'maxDatagramSize', 'createBidirectionalStream', 'createUnidirectionalStream',
552
- 'createSendGroup', 'canOpenStream', 'setWebTransportProtocol', 'getStats', 'closeSession', 'drainSession'].forEach(function (m) {
615
+ 'createSendGroup', 'canOpenStream', 'setWebTransportProtocol', 'getStats', 'closeSession', 'drainSession',
616
+ 'exportKeyingMaterial'].forEach(function (m) {
553
617
  if (typeof plainRes[m] === 'function') res[m] = plainRes[m].bind(plainRes);
554
618
  });
555
619
  if ('datagrams' in plainRes) {
@@ -657,7 +721,34 @@ function createServer(options, handler) {
657
721
  var tcpServer = null;
658
722
  var hasTlsCredentials = (tlsKey && tlsCert) || options.SNICallback;
659
723
 
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
+ }
749
+
660
750
  function onRequest(req, res) {
751
+ if (isWebTransportRequest(req)) { refuseWebTransport(req, res); return; }
661
752
  if (req.earlyData === true) { earlyGate(req, res); return; }
662
753
  runHandler(req, res);
663
754
  }
@@ -683,7 +774,9 @@ function createServer(options, handler) {
683
774
 
684
775
  if (needsTcp) {
685
776
  var shared = {
686
- shouldUpgradeCallback: shouldUpgradeCallback,
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); },
687
780
  // Not over cleartext: HTTP/3 is always TLS, and browsers take Alt-Svc
688
781
  // only from an https origin — on http:// it advertised a service that
689
782
  // could not exist there.
@@ -721,7 +814,7 @@ function createServer(options, handler) {
721
814
  key: tlsKey,
722
815
  cert: tlsCert,
723
816
  ca: tlsCa,
724
- SNICallback: options.SNICallback,
817
+ SNICallback: (tlsKey && tlsCert) || userSni ? serverSni : null,
725
818
  ticketKeys: ticketKeys,
726
819
  ticketData: ticketData,
727
820
  ech: ech,
@@ -773,8 +866,10 @@ function createServer(options, handler) {
773
866
  // are only meaningful with TLS: without it there is no ALPN to carry them.
774
867
  for (var a = 0; a < tcpAlpn.length; a++) {
775
868
  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);
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 });
778
873
  else if (hasTlsCredentials) tcpServer.register(proto, closeAfterHandshake);
779
874
  }
780
875
 
@@ -847,6 +942,8 @@ function createServer(options, handler) {
847
942
  remaining--;
848
943
  if (remaining <= 0 && !called) {
849
944
  called = true;
945
+ listening = true;
946
+ closed = false;
850
947
  // After listen(0) the OS chose the port; Alt-Svc must name that one.
851
948
  if (port === 0 && tcpServer && tcpServer.address()) altSvcPort = tcpServer.address().port;
852
949
  ev.emit('listening');
@@ -858,27 +955,98 @@ function createServer(options, handler) {
858
955
  // asynchronously, like a real bind would.
859
956
  if (remaining === 0) { remaining = 1; setImmediate(done); return server; }
860
957
 
958
+ if (ref === false) { if (enableH3) h3srv.unref(); if (tcpServer) tcpServer.unref(); }
861
959
  if (bindUdp) h3srv.listen(port, host, done);
862
960
  if (needsTcp) tcpServer.listen(port, host || '0.0.0.0', done);
863
961
 
864
962
  return server;
865
963
  }
866
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
+ */
867
975
  function close(callback) {
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;
868
985
  // h3srv is closed whenever HTTP/3 is on, bound or not: in external mode it
869
986
  // still runs a sweep timer, started at creation.
870
987
  var remaining = (enableH3 ? 1 : 0) + (tcpServer ? 1 : 0);
871
988
 
872
989
  function onClose() {
873
990
  remaining--;
874
- if (remaining <= 0 && typeof callback === 'function') callback();
991
+ if (remaining > 0) return;
992
+ ev.emit('close');
993
+ if (typeof callback === 'function') callback();
875
994
  }
876
995
 
877
- if (remaining === 0) { if (typeof callback === 'function') callback(); return; }
996
+ if (remaining === 0) { remaining = 1; process.nextTick(onClose); return server; }
878
997
  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; }
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);
882
1050
  }
883
1051
 
884
1052
  function address() {
@@ -915,11 +1083,28 @@ function createServer(options, handler) {
915
1083
  });
916
1084
  }
917
1085
 
918
- var server = {
1086
+ var api = {
919
1087
  listen: listen,
920
1088
  close: close,
921
1089
  address: address,
922
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; },
923
1108
 
924
1109
  /**
925
1110
  * The session ticket keys: 48 bytes, the key every ticket is sealed with
@@ -932,6 +1117,32 @@ function createServer(options, handler) {
932
1117
  * open — each of those clients does one full handshake and gets a new
933
1118
  * ticket, as after a restart with random keys.
934
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
+ },
935
1146
  getTicketKeys: function () { return ticketKeys; },
936
1147
  setTicketKeys: function (keys) {
937
1148
  if (!Buffer.isBuffer(keys) || keys.length !== 48) {
@@ -1006,6 +1217,16 @@ function createServer(options, handler) {
1006
1217
  getResetTokenSecret: function () { return h3srv.getResetTokenSecret(); },
1007
1218
  setResetTokenSecret: function (secret) { h3srv.setResetTokenSecret(secret); },
1008
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
+
1009
1230
  /**
1010
1231
  * Carry another protocol over this server's TLS port, beside HTTP.
1011
1232
  *
@@ -1046,12 +1267,6 @@ function createServer(options, handler) {
1046
1267
  */
1047
1268
  getStats: function () { return stats.snapshot(); },
1048
1269
 
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
1270
  setTimeout: function (msecs, callback) {
1056
1271
  if (typeof msecs === 'function') { callback = msecs; msecs = undefined; }
1057
1272
  if (msecs !== undefined) serverTimeout = Math.max(0, Number(msecs) || 0);
@@ -1068,13 +1283,42 @@ function createServer(options, handler) {
1068
1283
  return h3srv.handlePacket(msg, rinfo);
1069
1284
  },
1070
1285
 
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);
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);
1075
1293
  }
1076
1294
  };
1077
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
+
1078
1322
  return server;
1079
1323
  }
1080
1324