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.
@@ -0,0 +1,911 @@
1
+ /*
2
+ * quico: HTTP/3 and QUIC implementation for Node.js
3
+ * Copyright 2025 colocohen
4
+ *
5
+ * Licensed under the Apache License, Version 2.0 (the "License");
6
+ * you may not use this file except in compliance with the License.
7
+ * You may obtain a copy of the License at:
8
+ *
9
+ * https://www.apache.org/licenses/LICENSE-2.0
10
+ *
11
+ * This file is part of the open-source project hosted at:
12
+ * https://github.com/colocohen/quico
13
+ */
14
+
15
+ import { EventEmitter } from 'node:events';
16
+ import { Duplex } from 'node:stream';
17
+
18
+ import { H2Connection, SOCKET_HIGH_WATER } from './h2.js';
19
+ import * as F from './h2_frames.js';
20
+ import { buildResponsePseudoHeaders, corkForTick, pseudoHeadersToRequest, runRequestHandler, STATUS_TEXT, maxRequestsPerSocket,
21
+ DEFAULT_HEADERS_TIMEOUT, DEFAULT_REQUEST_TIMEOUT, startDeadline, responsePriority, trailerFields, statusHasNoBody, httpDate, streamResetLimiter } from './http_common.js';
22
+ import { observeH2 } from './h2_fingerprint.js';
23
+ import { IncomingMessage, ServerResponse } from './streams.js';
24
+ import { createTcpListener } from './tcp_listener.js';
25
+
26
+
27
+ // ============================================================
28
+ // HTTP/2 server.
29
+ //
30
+ // The same three layers as everywhere else in this stack: tcp_listener owns
31
+ // the socket and ALPN, h2.js turns bytes into stream events, and streams.js
32
+ // wraps those in the IncomingMessage / ServerResponse pair every framework
33
+ // expects. This file is only the wiring between them — which is why it is a
34
+ // fraction of h2.js's size despite being the thing applications touch.
35
+ //
36
+ // It is also why an Express app runs unchanged over HTTP/2: the req and res
37
+ // it receives are the same objects h1_server and h3_server hand it, and
38
+ // nothing in the middle knows which protocol produced them.
39
+ // ============================================================
40
+
41
+ var DEFAULT_KEEPALIVE_TIMEOUT = 0; // 0 = no idle limit; h2 connections are long-lived
42
+
43
+
44
+ // ============================================================
45
+ // Protocol-independent refusal codes.
46
+ //
47
+ // HTTP/2 and HTTP/3 both let a server refuse a stream, and both use entirely
48
+ // different numbers for it. Code that limits request rates should not have to
49
+ // know which protocol carried the request, so it names an INTENT and each
50
+ // layer maps it to its own registry.
51
+ //
52
+ // REFUSED specifically, and not CANCELLED, is what a rate limiter wants:
53
+ // RFC 9113 §8.1 lets a client safely retry a REFUSED_STREAM on another
54
+ // connection, because it states the request was never processed. Cancelling
55
+ // says the opposite, and a client that retries after it may duplicate work.
56
+ // ============================================================
57
+
58
+ var STREAM_REFUSED = 'refused';
59
+ var STREAM_CANCELLED = 'cancelled';
60
+ var CONN_OVERLOADED = 'overloaded';
61
+
62
+ function translateStreamError(code) {
63
+ if (typeof code === 'number') return code; // caller wants a raw code
64
+ if (code === STREAM_CANCELLED) return F.ERROR.CANCEL;
65
+ return F.ERROR.REFUSED_STREAM;
66
+ }
67
+
68
+ function translateConnError(code) {
69
+ if (typeof code === 'number') return code;
70
+ if (code === CONN_OVERLOADED) return F.ERROR.ENHANCE_YOUR_CALM;
71
+ return F.ERROR.NO_ERROR;
72
+ }
73
+
74
+
75
+ function createServer(options, handler) {
76
+ if (typeof options === 'function') { handler = options; options = {}; }
77
+ options = options || {};
78
+
79
+ var ev = new EventEmitter();
80
+ var listener = null;
81
+ var connections = new Set();
82
+ var closing = false;
83
+
84
+ var altSvc = options.altSvc || null;
85
+ // How long a request may take to arrive (see http_common). headersTimeout
86
+ // bounds a header block left open, which holds the whole connection;
87
+ // requestTimeout each request, from its head to its last byte.
88
+ var headersTimeout = options.headersTimeout != null ? options.headersTimeout : DEFAULT_HEADERS_TIMEOUT;
89
+ var requestTimeout = options.requestTimeout != null ? options.requestTimeout : DEFAULT_REQUEST_TIMEOUT;
90
+ // Rapid Reset (http_common streamResetLimiter): the options checked now.
91
+ streamResetLimiter(options);
92
+ // node:http2's maxSessionMemory: megabytes a connection may have queued.
93
+ var maxSessionMemory = (options.maxSessionMemory > 0 ? options.maxSessionMemory : 10) * 1024 * 1024;
94
+ // node:http's maxRequestsPerSocket, as HTTP/2 can honour it: after the Nth
95
+ // request, GOAWAY — those in flight finish, the client opens a new
96
+ // connection for the next (nginx's keepalive_requests, the same way).
97
+ var maxRequests = maxRequestsPerSocket(options.maxRequestsPerSocket);
98
+
99
+ // node:http's shouldUpgradeCallback, for extended CONNECT (RFC 8441): taken
100
+ // up as an 'upgrade' (true) or handled as an ordinary 'request' (false).
101
+ // Default: true when something listens for 'upgrade'.
102
+ var shouldUpgradeCallback = typeof options.shouldUpgradeCallback === 'function'
103
+ ? options.shouldUpgradeCallback
104
+ : function () { return ev.listenerCount('upgrade') > 0; };
105
+
106
+
107
+ function handleConnection(socket) {
108
+ if (closing) { try { socket.destroy(); } catch (e) {} return; }
109
+
110
+ var h2 = new H2Connection({
111
+ isServer: true,
112
+ stats: options.stats || null,
113
+ maxConcurrentStreams: options.maxConcurrentStreams,
114
+ initialWindowSize: options.initialWindowSize,
115
+ connectionWindowSize: options.connectionWindowSize,
116
+ maxFrameSize: options.maxFrameSize,
117
+ maxHeaderListSize: options.maxHeaderListSize,
118
+ headerTableSize: options.headerTableSize,
119
+ // RFC 8441. On by default: a server that can accept WebSocket over
120
+ // HTTP/2 should say so, or browsers open a second HTTP/1.1 connection
121
+ // instead. Passing false turns it off for a server that has no
122
+ // WebSocket endpoint and no reason to invite extended CONNECT.
123
+ enableConnectProtocol: options.enableConnectProtocol,
124
+ // DATA is held while the socket's unsent bytes are over this mark — the
125
+ // flow-control windows bound what the peer may receive, this what we
126
+ // queue for it (see flushStream in h2.js).
127
+ isWriteBlocked: function () { return socket.writableLength >= SOCKET_HIGH_WATER; },
128
+ // Credit is returned when the application reads, not when the frame
129
+ // arrives. Without this the receive window never closes and a client can
130
+ // push a body far faster than the handler consumes it — the bytes pile
131
+ // up in our heap and flow control does nothing at all.
132
+ autoWindowUpdate: false
133
+ });
134
+
135
+ var streams = {}; // id -> { req, res, deadline }
136
+ var served = 0; // requests on this connection (maxRequestsPerSocket)
137
+ var destroyed = false;
138
+
139
+ // ---- How long a request may take to arrive ----
140
+ //
141
+ // A header block left open (HEADERS without END_HEADERS) holds the
142
+ // connection: no other frame may come until its CONTINUATIONs end it
143
+ // (§6.10). Trickled, it held every other request on the connection with
144
+ // it, and nothing timed it. Past headersTimeout the connection goes —
145
+ // there is no stream to give up instead.
146
+ var headerBlockTimer = null;
147
+ h2.on('header_block_open', function () {
148
+ if (headerBlockTimer) clearTimeout(headerBlockTimer);
149
+ headerBlockTimer = startDeadline(headersTimeout, function () {
150
+ headerBlockTimer = null;
151
+ if (destroyed) return;
152
+ h2.sendGoaway(F.ERROR.NO_ERROR);
153
+ destroy(new Error('timeout: header block not completed within ' + headersTimeout + ' ms'), true);
154
+ });
155
+ });
156
+ h2.on('header_block_closed', function () {
157
+ if (headerBlockTimer) { clearTimeout(headerBlockTimer); headerBlockTimer = null; }
158
+ });
159
+
160
+ // A request whose body has not all arrived within requestTimeout of its
161
+ // head (CONNECT excepted — a tunnel): the stream is reset (CANCEL), both ways, and the request and its
162
+ // response end as they do when the client cancels — 'aborted', and a
163
+ // 'streamError' for the log. Only this stream: the connection serves on.
164
+ // (HTTP/1.1 answers 408 and closes; a stream of its own needs no more
165
+ // than its reset.)
166
+ function armRequestDeadline(streamId, st) {
167
+ st.deadline = startDeadline(requestTimeout, function () {
168
+ st.deadline = null;
169
+ if (streams[streamId] !== st || st.req.complete) return;
170
+ delete streams[streamId];
171
+ h2.sendRstStream(streamId, F.ERROR.CANCEL);
172
+ abortStream(st, 'request not received in full within ' + requestTimeout + ' ms', 'ERR_HTTP_REQUEST_TIMEOUT');
173
+ });
174
+ }
175
+ function clearRequestDeadline(st) {
176
+ if (st && st.deadline) { clearTimeout(st.deadline); st.deadline = null; }
177
+ }
178
+
179
+ // Accumulates the connection's HTTP/2 fingerprint from the opening frames.
180
+ // Attached before execute() is ever called, because everything it needs
181
+ // arrives in the peer's first flight — the fingerprint exists before a
182
+ // single byte of content has been served, which is what makes it useful
183
+ // for deciding whether to serve any.
184
+ var getFingerprint = observeH2(h2);
185
+
186
+ /**
187
+ * What a rate limiter is handed: enough to build a key from, and the two
188
+ * ways to refuse.
189
+ *
190
+ * The methods are named identically to the HTTP/3 equivalents even though
191
+ * the wire mechanisms differ — RST_STREAM on a frame here, RESET_STREAM on
192
+ * a QUIC stream there — so that one piece of limiting code works against
193
+ * both without branching on version.
194
+ */
195
+ var connHandle = {
196
+ httpVersion: '2.0',
197
+ get remoteAddress() { return socket.remoteAddress; },
198
+ get remotePort() { return socket.remotePort; },
199
+ get encrypted() { return !!socket.encrypted; },
200
+ // The session ticket: { data, resumed, offered } (see lemon-tls
201
+ // getTicketInfo); null over cleartext.
202
+ getTicketInfo: function () { return socket.getTicketInfo ? socket.getTicketInfo() : null; },
203
+ isSessionReused: function () { return !!(socket.isSessionReused && socket.isSessionReused()); },
204
+ get activeStreams() { return Object.keys(streams).length; },
205
+ fingerprint: getFingerprint,
206
+
207
+ sendRstStream: function (streamId, code) {
208
+ h2.sendRstStream(streamId, translateStreamError(code));
209
+ },
210
+
211
+ // Stops new streams; in-flight ones finish. See the note in h2.js.
212
+ goaway: function (code) { h2.goaway(translateConnError(code)); },
213
+
214
+ // For when stopping is not enough.
215
+ destroy: function () { destroy(null); }
216
+ };
217
+
218
+ h2.on('stream_open', function (streamId) {
219
+ ev.emit('stream_open', connHandle, streamId);
220
+ });
221
+
222
+ // Rapid Reset: past streamResetBurst / streamResetRate, GOAWAY
223
+ // ENHANCE_YOUR_CALM and the connection closed, as node:http2 does.
224
+ var resetTooMany = streamResetLimiter(options);
225
+ h2.on('stream_reset', function (streamId, code) {
226
+ ev.emit('stream_reset', connHandle, streamId, code);
227
+ if (destroyed || !resetTooMany()) return;
228
+ var rr = new Error('too many stream resets (streamResetBurst, streamResetRate)');
229
+ rr.code = 'ERR_HTTP2_STREAM_RESET_RATE';
230
+ try { h2.goaway(F.ERROR.ENHANCE_YOUR_CALM); } catch (e) {}
231
+ ev.emit('sessionError', rr, connHandle);
232
+ destroy(rr, true);
233
+ });
234
+
235
+ // The response is complete — END_STREAM written — and the request is
236
+ // not (RFC 9113 §8.1): a 413, a 401, a refusal answered before the upload
237
+ // is read. Unless the application is reading the body, the client is
238
+ // told to stop sending it: RST_STREAM with NO_ERROR, after the response,
239
+ // which a client must not take as a failure of it. node:http2 decides it
240
+ // the same way (the stream is closed if nothing has read from it). The
241
+ // stream used to stay open: the client went on uploading into a window
242
+ // nobody would open again, and held one of its concurrent streams until
243
+ // it gave up.
244
+ h2.on('local_end', function (streamId) {
245
+ var st = streams[streamId]; // taken now: the response's end removes the entry
246
+ if (!st || !st.req || st.duplex) return;
247
+ process.nextTick(function () {
248
+ var req = st.req;
249
+ if (req.complete || req.aborted || destroyed) return;
250
+ if (!req.destroyed && req.readableFlowing !== null) return; // still being read
251
+ if (streams[streamId] === st) delete streams[streamId];
252
+ clearRequestDeadline(st);
253
+ h2.sendRstStream(streamId, F.ERROR.NO_ERROR);
254
+ });
255
+ });
256
+
257
+ connections.add(socket);
258
+ socket._quicoH2 = {
259
+ idle: function () { return h2.activeStreams === 0; },
260
+ drain: function () {
261
+ if (destroyed || draining) return;
262
+ draining = true;
263
+ try { h2.sendGoaway(F.ERROR.NO_ERROR); } catch (e) {}
264
+ finishIfDone();
265
+ },
266
+ destroy: function () {
267
+ if (destroyed) return;
268
+ try { h2.sendGoaway(F.ERROR.NO_ERROR); } catch (e) {}
269
+ destroy(null, true);
270
+ }
271
+ };
272
+
273
+ // `linger`: end the socket rather than destroy it — what was written last
274
+ // (the GOAWAY saying why) leaves first — and destroy it if the peer has
275
+ // not closed its side in time. destroy() right after end() dropped the
276
+ // GOAWAY the comment below meant to deliver.
277
+ function destroy(err, linger) {
278
+ if (destroyed) return;
279
+ destroyed = true;
280
+ connections.delete(socket);
281
+ if (headerBlockTimer) { clearTimeout(headerBlockTimer); headerBlockTimer = null; }
282
+ for (var id in streams) {
283
+ var st = streams[id];
284
+ clearRequestDeadline(st);
285
+ // An extended-CONNECT tunnel (a WebSocket, CONNECT-UDP) goes with the
286
+ // connection: its Duplex is destroyed. Its request had ended at the
287
+ // accept, so the line below never reached it — the tunnel's user
288
+ // waited for an end that could not come.
289
+ if (st.duplex) { try { st.duplex.destroy(); } catch (e) {} continue; }
290
+ // 'aborted', an Error only to a listener, 'close' (see _abort); the
291
+ // response, mid-write, closed (_abortResponse).
292
+ if (st.req && !st.req.complete) st.req._abort(err || null);
293
+ if (st.res) st.res._abortResponse();
294
+ }
295
+ streams = {};
296
+ if (!linger) { try { socket.destroy(); } catch (e) {} return; }
297
+ try { socket.end(); } catch (e) {}
298
+ var t = setTimeout(function () { try { socket.destroy(); } catch (e) {} }, 1000);
299
+ if (t.unref) t.unref();
300
+ }
301
+
302
+ // An HTTP/2 connection's errors are 'sessionError' (err, session), as
303
+ // node:http2 reports them — not 'clientError' (err, socket), which is
304
+ // HTTP/1.1's, and whose listeners answer by writing an HTTP/1.1 status
305
+ // line onto the socket: on an HTTP/2 connection, after its GOAWAY, that is
306
+ // bytes the client cannot parse.
307
+ h2.on('send', function (data) {
308
+ if (destroyed || !socket.writable) return;
309
+ corkForTick(socket);
310
+ try { socket.write(data); } catch (e) { destroy(e); return; }
311
+ // What is queued on the socket and not yet sent, bounded per connection
312
+ // (node's maxSessionMemory, in MB, default 10). DATA waits for the
313
+ // socket (isWriteBlocked), so what can pile up here is everything else —
314
+ // replies the peer provokes (PING and SETTINGS acknowledgements, resets)
315
+ // while it does not read. Past the bound the connection goes.
316
+ if (socket.writableLength > maxSessionMemory) {
317
+ var e = new Error('peer is not reading: ' + socket.writableLength + ' bytes queued');
318
+ e.code = 'ERR_HTTP2_SESSION_MEMORY';
319
+ ev.emit('sessionError', e, connHandle);
320
+ destroy(e);
321
+ }
322
+ });
323
+
324
+ // DATA was waiting for the socket; it has room again.
325
+ socket.on('drain', function () { if (!destroyed) h2.flushAll(); });
326
+
327
+ h2.on('error', function (err) {
328
+ ev.emit('sessionError', err, connHandle);
329
+ // GOAWAY has already been queued by the connection layer; let it reach
330
+ // the peer before the socket goes, so the client learns why.
331
+ destroy(err, true);
332
+ });
333
+
334
+ socket.on('data', function (chunk) {
335
+ if (destroyed) return;
336
+ try { h2.execute(chunk); }
337
+ catch (err) { ev.emit('sessionError', err, connHandle); destroy(err); }
338
+ });
339
+
340
+ socket.on('error', function (err) {
341
+ ev.emit('sessionError', err, connHandle);
342
+ destroy(err);
343
+ });
344
+
345
+ socket.on('close', function () { destroy(null); });
346
+
347
+ // The client half-closes (TCP FIN, or TLS close_notify): it will send
348
+ // nothing more, and still reads (RFC 9293 §3.6; RFC 9113 has nothing
349
+ // against it). The connection was destroyed on the spot, and every
350
+ // response in progress with it. Now: a request whose body was still
351
+ // arriving cannot complete — reset (CANCEL), the request 'aborted';
352
+ // the ones fully received are answered; and when the last stream closes,
353
+ // GOAWAY and our own end. The socket is half-open for this connection
354
+ // only (HTTP/1.1 ends its socket with the client's, as node:http does).
355
+ socket.allowHalfOpen = true;
356
+ var peerEnded = false;
357
+ // Closing (server.close(), closeIdleConnections): GOAWAY sent — no new
358
+ // streams — and the connection ends with its last stream, as when the
359
+ // peer half-closes.
360
+ var draining = false;
361
+ function finishIfDone() {
362
+ if (destroyed || !(peerEnded || draining) || h2.activeStreams > 0) return;
363
+ try { h2.sendGoaway(F.ERROR.NO_ERROR); } catch (e) {}
364
+ destroy(null, true);
365
+ }
366
+ socket.on('end', function () {
367
+ if (destroyed) return;
368
+ peerEnded = true;
369
+ if (h2.windowBlocked()) { destroy(null); return; } // see 'window_blocked' below
370
+ for (var id in streams) {
371
+ var st = streams[id];
372
+ // A tunnel (WebSocket, CONNECT-UDP) cannot go on: nothing more can
373
+ // come from the client on it. Reset and destroyed — left open, it
374
+ // held the connection half-open for good (finishIfDone waits for it).
375
+ if (st.duplex) {
376
+ delete streams[id];
377
+ h2.sendRstStream(Number(id), F.ERROR.CANCEL);
378
+ try { st.duplex.destroy(); } catch (e) {}
379
+ continue;
380
+ }
381
+ if (st.req && !st.req.complete && !st.req.aborted) {
382
+ delete streams[id];
383
+ clearRequestDeadline(st);
384
+ h2.sendRstStream(Number(id), F.ERROR.CANCEL);
385
+ st.req._abort(null);
386
+ if (st.res) st.res._abortResponse();
387
+ }
388
+ }
389
+ finishIfDone();
390
+ });
391
+ h2.on('stream_close', function (id) {
392
+ // A tunnel closed both ways: its entry goes (nothing else removes it —
393
+ // a long-lived connection kept every WebSocket it ever carried).
394
+ var st = streams[id];
395
+ if (st && st.duplex) delete streams[id];
396
+ if (peerEnded || draining) process.nextTick(finishIfDone);
397
+ });
398
+ // A response that waits for the client to open a window, from a client
399
+ // that has half-closed: it sends nothing more — no WINDOW_UPDATE either —
400
+ // so the response can never finish, and the connection would be held
401
+ // open for good (a client that destroyed its socket looks the same until
402
+ // a write reaches it, and none would). Over, as before half-close.
403
+ h2.on('window_blocked', function () {
404
+ if (!peerEnded || destroyed) return;
405
+ process.nextTick(function () { if (!destroyed) destroy(null); });
406
+ });
407
+
408
+
409
+ // ---- Requests ----
410
+
411
+ h2.on('headers', function (streamId, headers, endStream, connectProtocol) {
412
+
413
+ // ---- Extended CONNECT (RFC 8441) ----
414
+ //
415
+ // A CONNECT carrying `:protocol` is not a request/response exchange but
416
+ // a bidirectional channel — this is how WebSocket runs over HTTP/2. The
417
+ // application gets it through the SAME 'upgrade' event that HTTP/1.1
418
+ // uses, so a WebSocketServer attached to this server works over both
419
+ // without knowing which carried the connection.
420
+ //
421
+ // What it gets is a Duplex over the stream rather than a socket: reads
422
+ // are DATA frames, writes are DATA frames, and the HTTP/2 connection
423
+ // underneath keeps serving every other stream. That is the whole
424
+ // advantage over HTTP/1.1, where an upgrade consumes the connection.
425
+ if (connectProtocol) {
426
+ var parsedC = pseudoHeadersToRequest(headers);
427
+ var creq = new IncomingMessage({
428
+ httpVersion: '2.0',
429
+ method: 'CONNECT',
430
+ url: parsedC.url,
431
+ headers: parsedC.headers,
432
+ streamId: streamId,
433
+ socket: socket
434
+ });
435
+ creq.rawHeaders = [];
436
+ creq.headers[':protocol'] = connectProtocol;
437
+ }
438
+ // Not taken up: an ordinary request, answered by the handler — as
439
+ // node:http does with an upgrade nobody asked for.
440
+ if (connectProtocol && !shouldUpgradeCallback(creq)) connectProtocol = null;
441
+ if (connectProtocol) {
442
+ creq._endData();
443
+
444
+ var stream = makeStreamDuplex(streamId);
445
+ streams[streamId] = { req: creq, res: null, duplex: stream };
446
+
447
+ if (ev.listenerCount('upgrade') === 0) {
448
+ // Accepted by shouldUpgradeCallback, yet nobody listens. Where
449
+ // node:http destroys the socket, here only this stream is lost: §4
450
+ // says to answer with an error status rather than leave the client
451
+ // waiting for a 200 that will never come.
452
+ h2.sendHeaders(streamId, [[':status', '501']], true);
453
+ delete streams[streamId];
454
+ return;
455
+ }
456
+ creq.upgrade = true;
457
+ ev.emit('upgrade', creq, stream, Buffer.alloc(0));
458
+ return;
459
+ }
460
+
461
+ var parsed = pseudoHeadersToRequest(headers);
462
+
463
+ var req = new IncomingMessage({
464
+ httpVersion: '2.0',
465
+ method: parsed.method,
466
+ url: parsed.url,
467
+ headers: parsed.headers,
468
+ streamId: streamId,
469
+ socket: socket
470
+ });
471
+ req.scheme = headers[':scheme'];
472
+ req.authority = headers[':authority'];
473
+ // Its HEADERS read from 0-RTT data (TLS early data): see h1_server.
474
+ req.earlyData = socket.isEarlyData === true;
475
+ // Per-connection, not per-request: the same function on every request of
476
+ // a connection returns the same fingerprint, because it describes how
477
+ // the connection was set up.
478
+ req.h2Fingerprint = getFingerprint;
479
+
480
+ var res = new ServerResponse({ streamId: streamId, socket: socket });
481
+ res.req = req;
482
+ streams[streamId] = { req: req, res: res };
483
+
484
+ // Back-pressure: a Readable that nobody is reading must slow the peer
485
+ // down, and the only lever h2 has is the flow-control window. Credit
486
+ // goes back as the application READS: from _read, which the stream
487
+ // calls when its consumer wants more. Bytes that arrived are counted in
488
+ // (see 'data' below) and returned there.
489
+ //
490
+ // This was a 'data' listener, which says the same in its comment and
491
+ // the opposite in effect: a 'data' listener switches a Readable to
492
+ // flowing, so every body chunk was emitted the moment it arrived — with
493
+ // nobody listening yet if the handler did anything asynchronous before
494
+ // reading the body (an auth check, a lookup). The chunks and the 'end'
495
+ // were lost, and the request waited forever for a body it had already
496
+ // been given. node:http keeps a request paused until someone reads it;
497
+ // so does this now.
498
+ var uncredited = 0;
499
+ req._h2Arrived = function (n) { uncredited += n; };
500
+ req._read = function () {
501
+ if (uncredited > 0) { h2.consume(streamId, uncredited); uncredited = 0; }
502
+ };
503
+
504
+ wireResponse(streamId, req, res);
505
+
506
+ if (endStream) req._endData();
507
+ else if (req.method !== 'CONNECT') armRequestDeadline(streamId, streams[streamId]); // a tunnel stays open
508
+
509
+ // A handler that throws loses its own stream, reset INTERNAL_ERROR —
510
+ // not the connection, and not the other requests on it (see
511
+ // runRequestHandler). A response it had already ended is left to finish.
512
+ if (maxRequests > 0 && ++served === maxRequests) h2.goaway(F.ERROR.NO_ERROR);
513
+ runRequestHandler(function () {
514
+ if (typeof handler === 'function') handler(req, res);
515
+ ev.emit('request', req, res);
516
+ }, function () {
517
+ if (res.writableEnded || streams[streamId] === undefined) return;
518
+ clearRequestDeadline(streams[streamId]);
519
+ delete streams[streamId];
520
+ h2.sendRstStream(streamId, F.ERROR.INTERNAL_ERROR);
521
+ req._abort(null);
522
+ res._abortResponse();
523
+ });
524
+ });
525
+
526
+ h2.on('data', function (streamId, chunk) {
527
+ var st = streams[streamId];
528
+ if (!st) return;
529
+ if (st.duplex) { st.duplex.push(chunk); h2.consume(streamId, chunk.length); return; }
530
+ if (st.req) {
531
+ // Counted before the push: a consumer already reading pulls
532
+ // synchronously from inside it, and _read hands the credit back.
533
+ st.req._h2Arrived(chunk.length);
534
+ st.req._pushData(chunk);
535
+ }
536
+ });
537
+
538
+ h2.on('trailers', function (streamId, trailers) {
539
+ var st = streams[streamId];
540
+ if (st && st.req) st.req.trailers = trailers;
541
+ });
542
+
543
+ h2.on('end', function (streamId) {
544
+ var st = streams[streamId];
545
+ if (!st) return;
546
+ if (st.duplex) { st.duplex.push(null); return; }
547
+ clearRequestDeadline(st);
548
+ if (st.req) st.req._endData();
549
+ if (st.responseDone) delete streams[streamId];
550
+ });
551
+
552
+ h2.on('rst', function (streamId, code) {
553
+ var st = streams[streamId];
554
+ if (!st) return;
555
+ delete streams[streamId];
556
+ clearRequestDeadline(st);
557
+ if (st.duplex) { st.duplex.destroy(); return; }
558
+ abortStream(st, 'stream reset: ' + (F.ERROR_NAME[code] || code),
559
+ 'ERR_HTTP2_STREAM_ERROR');
560
+ });
561
+
562
+ /**
563
+ * End a request that the network killed under it.
564
+ *
565
+ * A reset arrives because the PEER cancelled, which is ordinary traffic —
566
+ * a browser navigating away resets every in-flight stream. Destroying the
567
+ * IncomingMessage with an Error means an application that never registered
568
+ * an 'error' handler takes the whole process down with it, and almost none
569
+ * do: node:http teaches people to listen for 'aborted', not 'error'.
570
+ *
571
+ * So: 'aborted' always, an Error only to a listener that asked for one, and
572
+ * the server's own 'streamError' for logging either way. h2spec found this
573
+ * on its fourth test — it resets a stream as a matter of course, and the
574
+ * server died.
575
+ */
576
+ function abortStream(st, message, code) {
577
+ var err = new Error(message);
578
+ err.code = code;
579
+
580
+ if (st.req) st.req._abort(err); // 'aborted', an Error only to a listener, 'close'
581
+ // The response gets 'close' only, as node:http's and node:http2's do
582
+ // when the client goes. An 'error' here was guarded by "a listener
583
+ // asked for it" — but pipe() is such a listener, and rethrows when it
584
+ // is the only one: a client cancelling a piped response took the
585
+ // server down.
586
+ if (st.res) st.res._abortResponse();
587
+ ev.emit('streamError', err, st.req, st.res);
588
+ }
589
+
590
+ h2.on('goaway', function () {
591
+ // The client will not open new streams; existing ones run to completion
592
+ // and the socket closes when they do.
593
+ closingConnection = true;
594
+ });
595
+
596
+ var closingConnection = false;
597
+
598
+
599
+ /**
600
+ * A Duplex over one HTTP/2 stream, for extended CONNECT.
601
+ *
602
+ * ws_server was written against a socket: it calls write(), listens for
603
+ * 'data', and destroys when finished. Giving it this instead of a socket
604
+ * is the entire integration — the WebSocket framing code never learns
605
+ * whether its bytes are TCP or DATA frames.
606
+ *
607
+ * The `_h2Accept` hook is the one thing a socket does not have: an HTTP/2
608
+ * extended CONNECT is accepted with a 200 response rather than a 101, so
609
+ * the layer above needs a way to say "accepted" that is not writing bytes.
610
+ */
611
+ function makeStreamDuplex(streamId) {
612
+ var stream = new Duplex({
613
+ read: function () {},
614
+ write: function (chunk, encoding, callback) {
615
+ if (typeof chunk === 'string') chunk = Buffer.from(chunk, encoding);
616
+ var more = h2.sendData(streamId, chunk, false);
617
+ if (more) { callback(); return; }
618
+ // Send window closed: hold the callback so back-pressure reaches
619
+ // whoever is writing, exactly as a socket would.
620
+ var released = false;
621
+ function onDrain(id) {
622
+ if (id !== streamId || released) return;
623
+ released = true;
624
+ h2.off('drain', onDrain);
625
+ callback();
626
+ }
627
+ h2.on('drain', onDrain);
628
+ },
629
+ final: function (callback) {
630
+ h2.sendData(streamId, null, true);
631
+ callback();
632
+ },
633
+ // Destroyed by its user while the stream is open: RST_STREAM CANCEL,
634
+ // so the peer hears the tunnel is gone. (Destroyed by a reset or the
635
+ // connection's end, the entry is already gone and nothing is sent.)
636
+ destroy: function (err, callback) {
637
+ var st = streams[streamId];
638
+ if (st && st.duplex === stream && !destroyed) {
639
+ delete streams[streamId];
640
+ try { h2.sendRstStream(streamId, F.ERROR.CANCEL); } catch (e) {}
641
+ }
642
+ callback(err);
643
+ }
644
+ });
645
+
646
+ stream._streamId = streamId;
647
+ stream.encrypted = !!socket.encrypted;
648
+ stream.remoteAddress = socket.remoteAddress;
649
+ stream.remotePort = socket.remotePort;
650
+ stream.setNoDelay = function () { return stream; };
651
+ stream.setKeepAlive = function () { return stream; };
652
+ stream.setTimeout = function () { return stream; };
653
+
654
+ /**
655
+ * Accept the extended CONNECT.
656
+ *
657
+ * HTTP/2 has no 101: RFC 8441 §4 says a 2xx response accepts the channel
658
+ * and anything else refuses it. ws_server calls this instead of writing
659
+ * an HTTP/1.1 status line — which is why the WebSocket handshake code
660
+ * has to know which transport it is on for exactly one line, and no more.
661
+ */
662
+ stream._h2Accept = function (extraHeaders) {
663
+ var list = [[':status', '200']];
664
+ for (var n in (extraHeaders || {})) list.push([String(n).toLowerCase(), extraHeaders[n]]);
665
+ h2.sendHeaders(streamId, list, false);
666
+ };
667
+
668
+ /** Refuse it, with a status (and fields: a Proxy-Status, say). */
669
+ stream._h2Reject = function (status, message, extraHeaders) {
670
+ var list = [[':status', String(status)]];
671
+ for (var n in (extraHeaders || {})) list.push([String(n).toLowerCase(), String(extraHeaders[n])]);
672
+ h2.sendHeaders(streamId, list, !message);
673
+ if (message) h2.sendData(streamId, Buffer.from(String(message)), true);
674
+ delete streams[streamId];
675
+ };
676
+
677
+ return stream;
678
+ }
679
+
680
+
681
+ // ============================================================
682
+ // Response wiring
683
+ //
684
+ // ServerResponse is transport-agnostic — h1_server overrides these same
685
+ // three hooks to write HTTP/1.1 bytes, h3_server to write H3 frames.
686
+ // ============================================================
687
+
688
+ function wireResponse(streamId, req, res) {
689
+ var bodyAllowed = true;
690
+ var finished = false;
691
+
692
+ res._flushHeaders = function () {
693
+ if (res.headersSent || res._aborted || destroyed) return;
694
+ var headers = res.getHeaders();
695
+ var code = res.statusCode;
696
+
697
+ // HTTP/2 has no reason phrase and no connection-specific fields.
698
+ // buildResponsePseudoHeaders drops the latter; sending them would make
699
+ // the message malformed for the peer and, worse, would be forwarded by
700
+ // a proxy into an HTTP/1.1 hop where they change the framing.
701
+ delete headers['connection'];
702
+ delete headers['keep-alive'];
703
+ delete headers['transfer-encoding'];
704
+ delete headers['upgrade'];
705
+
706
+ bodyAllowed = !statusHasNoBody(code) && req.method !== 'HEAD';
707
+
708
+ if (!('date' in headers) && res.sendDate !== false) headers['date'] = httpDate();
709
+ // A function when the server was built before its port was known —
710
+ // the unified server builds its layers at creation and learns the
711
+ // port from listen(), or from options.port when fed from outside.
712
+ var altSvcNow = typeof altSvc === 'function' ? altSvc() : altSvc;
713
+ if (altSvcNow && !('alt-svc' in headers)) headers['alt-svc'] = altSvcNow;
714
+
715
+ var list = buildResponsePseudoHeaders(code, headers);
716
+ try { h2.sendHeaders(streamId, list, false); }
717
+ catch (e) { destroy(e); return; }
718
+ res.headersSent = true;
719
+ };
720
+
721
+ res._write = function (chunk, encoding, callback) {
722
+ // The peer reset this stream while the handler was still producing.
723
+ // Discard rather than error: the handler is mid-flight and cannot be
724
+ // expected to check on every write.
725
+ if (res._aborted || destroyed) { callback(); return; }
726
+ if (!res.headersSent) res._flushHeaders();
727
+ if (typeof chunk === 'string') chunk = Buffer.from(chunk, encoding);
728
+
729
+ // A HEAD response, or a bodiless status: the application still wrote
730
+ // the body it WOULD have sent, and the headers already describe it.
731
+ if (!bodyAllowed) { callback(); return; }
732
+
733
+ var more = h2.sendData(streamId, chunk, false);
734
+ if (more) { callback(); return; }
735
+
736
+ // The send window is closed. Hold the callback until it reopens —
737
+ // that is what makes res.write() return false and a piped Readable
738
+ // pause, instead of queueing the whole body in memory.
739
+ var released = false;
740
+ function onDrain(id) {
741
+ if (id !== streamId || released) return;
742
+ released = true;
743
+ h2.off('drain', onDrain);
744
+ callback();
745
+ }
746
+ h2.on('drain', onDrain);
747
+ };
748
+
749
+ res._final = function (callback) {
750
+ if (res._aborted || destroyed) { callback(); return; }
751
+ if (finished) { callback(); return; }
752
+ finished = true;
753
+ if (!res.headersSent) res._flushHeaders();
754
+ var trailers = trailerFields(res.trailers);
755
+ if (trailers) {
756
+ h2.sendTrailers(streamId, trailers);
757
+ } else {
758
+ h2.sendData(streamId, null, true);
759
+ }
760
+ // The entry goes when the request is over too. Removed here, with the
761
+ // request body still arriving — an application reading it after
762
+ // answering — every DATA frame after the response was dropped, its
763
+ // flow-control credit never returned, and the client's upload stalled.
764
+ var st = streams[streamId];
765
+ if (st) {
766
+ st.responseDone = true;
767
+ if (req.complete || req.aborted) delete streams[streamId];
768
+ }
769
+ callback();
770
+ };
771
+
772
+ /**
773
+ * 103 Early Hints (RFC 8297): a HEADERS frame with :status 103, the
774
+ * stream left open — the final response follows on it. node:http2's
775
+ * signature: (hints, callback). Nothing once the head has gone.
776
+ */
777
+ res.writeEarlyHints = function (hints, callback) {
778
+ if (!res.headersSent && !res._aborted && !destroyed) {
779
+ var h = {};
780
+ for (var n in (hints || {})) {
781
+ if (Object.prototype.hasOwnProperty.call(hints, n) && hints[n] != null) h[String(n).toLowerCase()] = hints[n];
782
+ }
783
+ try { h2.sendHeaders(streamId, buildResponsePseudoHeaders(103, h), false); }
784
+ catch (e) { /* the stream went; the final response will find out */ }
785
+ }
786
+ if (typeof callback === 'function') process.nextTick(callback);
787
+ };
788
+
789
+ // 102 Processing (node:http's writeProcessing), as Early Hints go.
790
+ res.writeProcessing = function () {
791
+ if (res.headersSent || res._aborted || destroyed) return;
792
+ try { h2.sendHeaders(streamId, buildResponsePseudoHeaders(102, {}), false); } catch (e) {}
793
+ };
794
+
795
+ /**
796
+ * The response's priority, as the server decides it: { urgency (0..7,
797
+ * 0 first), incremental } — RFC 9218, as h3_server's. It replaces what
798
+ * the client asked for, and later requests from it do not override it.
799
+ */
800
+ res.setPriority = function (p) { h2.setStreamPriority(streamId, responsePriority(p)); };
801
+
802
+ /** Reset the stream — the HTTP/2 way to abandon a response. */
803
+ res.destroyStream = function (code) {
804
+ h2.sendRstStream(streamId, code === undefined ? F.ERROR.CANCEL : code);
805
+ clearRequestDeadline(streams[streamId]);
806
+ delete streams[streamId];
807
+ };
808
+
809
+ // The application giving the exchange up — res.destroy([err]),
810
+ // req.destroy([err]), either of which ends the stream on node:http2:
811
+ // the response unfinished, RST_STREAM CANCEL (INTERNAL_ERROR for a
812
+ // failure of ours); complete, RST_STREAM NO_ERROR for the rest of the
813
+ // request body (RFC 9113 §8.1). It was local only: the client waited
814
+ // for a response that would not come, the stream open until the
815
+ // connection closed.
816
+ var giveUp = function (err) {
817
+ if (destroyed || !streams[streamId]) return;
818
+ if (!finished) res.destroyStream(err ? F.ERROR.INTERNAL_ERROR : F.ERROR.CANCEL);
819
+ else if (!req.complete) res.destroyStream(F.ERROR.NO_ERROR);
820
+ };
821
+ res._onDestroy(giveUp);
822
+ req._onDestroy(function (err) { if (!res.writableFinished && !res.destroyed) res.destroy(err); else giveUp(err); });
823
+ }
824
+
825
+ // The client speaks first, but the server's SETTINGS must go out
826
+ // immediately — a client may not send anything until it has ours.
827
+ h2.sendPreface();
828
+ }
829
+
830
+
831
+ // ============================================================
832
+ // Listening
833
+ //
834
+ // Convenience only, for an h2-only server. The unified server builds one
835
+ // tcp_listener and registers this handleConnection under 'h2' alongside
836
+ // h1_server's under 'http/1.1'.
837
+ // ============================================================
838
+
839
+ function listen(port, host, callback) {
840
+ if (typeof host === 'function') { callback = host; host = undefined; }
841
+
842
+ listener = createTcpListener({
843
+ tls: options.tls,
844
+ key: options.key,
845
+ cert: options.cert,
846
+ ca: options.ca,
847
+ SNICallback: options.SNICallback
848
+ });
849
+ listener.register('h2', handleConnection, { managesClose: true });
850
+ listener.on('error', function (err) { ev.emit('error', err); });
851
+ listener.on('clientError', function (err, sock) { ev.emit('clientError', err, sock); });
852
+ listener.on('tlsClientError', function (err, sock) { ev.emit('tlsClientError', err, sock); });
853
+ listener.listen(port, host, function () {
854
+ ev.emit('listening');
855
+ if (typeof callback === 'function') callback();
856
+ });
857
+ return server;
858
+ }
859
+
860
+ // ---- Closing, as node:http (see h1_server) ----
861
+ // Idle: no stream open. Closed with GOAWAY first, so the client knows
862
+ // nothing it sent was lost.
863
+ function closeIdleConnections() {
864
+ for (var s of connections) if (s._quicoH2 && s._quicoH2.idle()) s._quicoH2.drain();
865
+ }
866
+ function closeAllConnections() {
867
+ for (var s of connections) if (s._quicoH2) s._quicoH2.destroy();
868
+ }
869
+ // GOAWAY on every connection: those idle end now, the others when their
870
+ // last stream does. It used to end every socket at once — responses in
871
+ // progress were cut off.
872
+ function beginClose() {
873
+ closing = true;
874
+ for (var s of connections) if (s._quicoH2) s._quicoH2.drain();
875
+ }
876
+
877
+ function close(callback) {
878
+ beginClose();
879
+ if (!listener) { if (callback) callback(); return; }
880
+ listener.close(callback);
881
+ }
882
+
883
+ function setLimits(o) {
884
+ if (o.headersTimeout !== undefined) headersTimeout = o.headersTimeout;
885
+ if (o.requestTimeout !== undefined) requestTimeout = o.requestTimeout;
886
+ if (o.maxRequestsPerSocket !== undefined) maxRequests = maxRequestsPerSocket(o.maxRequestsPerSocket);
887
+ }
888
+
889
+
890
+ var server = {
891
+ listen: listen,
892
+ close: close,
893
+ address: function () { return listener && listener.address ? listener.address() : null; },
894
+ handleConnection: handleConnection,
895
+ on: function (n, f) { ev.on(n, f); return server; },
896
+ once: function (n, f) { ev.once(n, f); return server; },
897
+ off: function (n, f) { ev.off(n, f); return server; },
898
+ emit: function () { ev.emit.apply(ev, arguments); return server; },
899
+ listenerCount: function (n) { return ev.listenerCount(n); },
900
+ closeIdleConnections: closeIdleConnections,
901
+ closeAllConnections: closeAllConnections,
902
+ beginClose: beginClose,
903
+ setLimits: setLimits,
904
+ get connectionCount() { return connections.size; }
905
+ };
906
+
907
+ return server;
908
+ }
909
+
910
+
911
+ export { createServer, STREAM_REFUSED, STREAM_CANCELLED, CONN_OVERLOADED };