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/README.md +247 -33
- package/index.d.ts +1034 -77
- package/index.js +7 -3
- package/package.json +9 -5
- package/src/client.js +1 -0
- package/src/connect_udp.js +5 -2
- package/src/connect_udp_client.js +10 -0
- package/src/crypto.js +6 -0
- package/src/h1_server.js +51 -9
- package/src/h2_server.js +60 -11
- package/src/h3.js +115 -70
- package/src/h3_client.js +25 -5
- package/src/h3_server.js +104 -6
- package/src/hpack_primitives.js +64 -25
- package/src/http_common.js +7 -0
- package/src/qpack.js +11 -2
- package/src/quic_connection.js +379 -63
- package/src/quic_server.js +213 -72
- package/src/quic_socket.js +5 -5
- package/src/server.js +277 -24
- package/src/streams.js +183 -10
- package/src/tcp_listener.js +11 -0
- package/src/tls_bridge.js +62 -38
- package/src/transport.js +6 -2
- package/src/utils.js +36 -0
- package/src/webtransport.js +432 -57
- 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,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:
|
|
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'
|
|
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
|
-
|
|
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:
|
|
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
|
-
|
|
777
|
-
|
|
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
|
|
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,
|
|
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
|
|
997
|
+
if (remaining > 0) return;
|
|
998
|
+
ev.emit('close');
|
|
999
|
+
if (typeof callback === 'function') callback();
|
|
875
1000
|
}
|
|
876
1001
|
|
|
877
|
-
if (remaining === 0) {
|
|
1002
|
+
if (remaining === 0) { remaining = 1; process.nextTick(onClose); return server; }
|
|
878
1003
|
if (enableH3) h3srv.close(onClose);
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
|
|
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
|
|
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
|
-
/**
|
|
1072
|
-
*
|
|
1073
|
-
|
|
1074
|
-
|
|
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
|
|