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