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