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/README.md +245 -31
- package/index.d.ts +1034 -77
- package/index.js +7 -3
- package/package.json +7 -4
- package/src/connect_udp_client.js +9 -0
- package/src/h1_server.js +51 -9
- package/src/h2_server.js +60 -11
- package/src/h3.js +29 -60
- package/src/h3_client.js +24 -5
- package/src/h3_server.js +101 -6
- package/src/hpack_primitives.js +64 -25
- package/src/http_common.js +7 -0
- package/src/quic_connection.js +200 -23
- package/src/quic_server.js +94 -13
- package/src/server.js +266 -22
- package/src/streams.js +183 -10
- package/src/tcp_listener.js +11 -0
- package/src/tls_bridge.js +4 -0
- package/src/webtransport.js +424 -54
- package/src/ws.js +120 -0
- package/src/ws_client.js +68 -88
- package/src/ws_server.js +35 -53
- package/src/ws_stream.js +250 -0
- package/src/wt_stream.js +533 -76
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:
|
|
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'
|
|
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
|
-
|
|
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:
|
|
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
|
-
|
|
777
|
-
|
|
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
|
|
991
|
+
if (remaining > 0) return;
|
|
992
|
+
ev.emit('close');
|
|
993
|
+
if (typeof callback === 'function') callback();
|
|
875
994
|
}
|
|
876
995
|
|
|
877
|
-
if (remaining === 0) {
|
|
996
|
+
if (remaining === 0) { remaining = 1; process.nextTick(onClose); return server; }
|
|
878
997
|
if (enableH3) h3srv.close(onClose);
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
|
|
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
|
|
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
|
-
/**
|
|
1072
|
-
*
|
|
1073
|
-
|
|
1074
|
-
|
|
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
|
|