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,666 @@
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
+
17
+ import { H1Parser, buildResponseHead, encodeChunk, encodeLastChunk } from './h1.js';
18
+ import { corkForTick, STATUS_TEXT, runRequestHandler, DEFAULT_HEADERS_TIMEOUT, DEFAULT_REQUEST_TIMEOUT, maxRequestsPerSocket, trailerFields, statusHasNoBody, httpDate } from './http_common.js';
19
+ import { IncomingMessage, ServerResponse } from './streams.js';
20
+ import { createTcpListener } from './tcp_listener.js';
21
+
22
+
23
+ // ============================================================
24
+ // HTTP/1.1 server.
25
+ //
26
+ // Layered exactly like h3_server on top of quic_server: the transport below
27
+ // (net, or lemon-tls's TLSSocket over net) hands us a Duplex per peer, h1.js
28
+ // turns its bytes into messages, and streams.js wraps those in the
29
+ // IncomingMessage / ServerResponse pair every framework expects. Nothing here
30
+ // parses HTTP and nothing in h1.js touches a socket.
31
+ //
32
+ // TLS comes from lemon-tls, not node:tls — that is the whole point of
33
+ // replacing node:https. `options.tls` carries the credentials; without it the
34
+ // server speaks cleartext (http://, ws://), which is what a local dev server
35
+ // or a service behind a terminating proxy wants.
36
+ // ============================================================
37
+
38
+
39
+ // A peer that opens a connection and then says nothing — or sends a head one
40
+ // byte at a time — costs us a socket and a buffer for as long as we let it.
41
+ // h1.js deliberately cannot help here: an incomplete head is indistinguishable
42
+ // from a slow one, and it has no clock. The clock lives here. (The headers and
43
+ // request defaults are every version's: http_common.)
44
+ var DEFAULT_KEEPALIVE_TIMEOUT = 5 * 1000;
45
+
46
+
47
+ // The hints as header fields: names lowercased, and only real values.
48
+ // Everything else about them (which links, which rels) is the caller's.
49
+ function earlyHintHeaders(hints) {
50
+ var out = {};
51
+ for (var n in (hints || {})) {
52
+ if (!Object.prototype.hasOwnProperty.call(hints, n)) continue;
53
+ var v = hints[n];
54
+ if (v === undefined || v === null || (Array.isArray(v) && v.length === 0)) continue;
55
+ out[String(n).toLowerCase()] = v;
56
+ }
57
+ return out;
58
+ }
59
+
60
+
61
+ function createServer(options, handler) {
62
+ if (typeof options === 'function') { handler = options; options = {}; }
63
+ options = options || {};
64
+
65
+ var ev = new EventEmitter();
66
+ var tcpServer = null;
67
+ var connections = new Set();
68
+ var closing = false;
69
+
70
+ var headersTimeout = options.headersTimeout != null ? options.headersTimeout : DEFAULT_HEADERS_TIMEOUT;
71
+ var keepAliveTimeout = options.keepAliveTimeout != null ? options.keepAliveTimeout : DEFAULT_KEEPALIVE_TIMEOUT;
72
+ var requestTimeout = options.requestTimeout != null ? options.requestTimeout : DEFAULT_REQUEST_TIMEOUT;
73
+ // maxHeaderSize is node:http's name for the same limit.
74
+ var maxHeadSize = options.maxHeadSize || options.maxHeaderSize;
75
+ var maxHeaderCount = options.maxHeaderCount;
76
+ // node:http's: the Nth response says Connection: close and the connection
77
+ // ends after it; a request past it (pipelined) is answered 503.
78
+ var maxRequests = maxRequestsPerSocket(options.maxRequestsPerSocket);
79
+
80
+ // Advertised on every response so a client learns HTTP/3 is available on the
81
+ // same authority (RFC 9114 §3.3). The unified server sets it; a standalone
82
+ // h1 server has nothing to advertise.
83
+ var altSvc = options.altSvc || null;
84
+
85
+ // node:http's shouldUpgradeCallback(request): whether an upgrade request
86
+ // reaches 'upgrade' (true) or is handled as an ordinary 'request' (false).
87
+ // Node's default, and ours: true when something listens for 'upgrade'.
88
+ // It used to be answered 501 when nothing did — a refusal Node never gives.
89
+ var shouldUpgradeCallback = typeof options.shouldUpgradeCallback === 'function'
90
+ ? options.shouldUpgradeCallback
91
+ : function () { return ev.listenerCount('upgrade') > 0; };
92
+
93
+
94
+ // ============================================================
95
+ // One connection
96
+ // ============================================================
97
+
98
+ function handleConnection(socket) {
99
+ if (closing) { socket.destroy(); return; }
100
+ connections.add(socket);
101
+
102
+ var parser = new H1Parser({
103
+ isServer: true,
104
+ maxHeadSize: maxHeadSize,
105
+ maxHeaderCount: maxHeaderCount,
106
+ // The request as the callback sees it: method, url, headers — what
107
+ // node:http's IncomingMessage carries at that point.
108
+ shouldUpgrade: function (info) {
109
+ return shouldUpgradeCallback({
110
+ method: info.method, url: info.url, headers: info.headers,
111
+ rawHeaders: info.rawHeaders, httpVersion: info.httpVersion, socket: socket
112
+ });
113
+ }
114
+ });
115
+
116
+ var current = null; // { req, res, info }
117
+ var served = 0; // requests seen on this connection (maxRequestsPerSocket)
118
+ var destroyed = false;
119
+ var timer = null;
120
+
121
+ function setTimer(ms, reason) {
122
+ clearTimer();
123
+ if (!ms || ms <= 0) return;
124
+ timer = setTimeout(function () {
125
+ timer = null;
126
+ // Nothing is half-written at this point in the states we arm the timer
127
+ // for, so a hard destroy is honest: a 408 to a peer that has sent no
128
+ // complete request may itself never be read.
129
+ // requestTimeout with nothing answered yet: a 408 too, as node:http.
130
+ if ((reason === 'headers' && !current) || (reason === 'request' && current && !current.res.headersSent)) {
131
+ try { socket.end(buildResponseHead(408, null, { 'Connection': 'close', 'Content-Length': '0' })); }
132
+ catch (e) {}
133
+ }
134
+ destroy(new Error('timeout: ' + reason));
135
+ }, ms);
136
+ if (timer.unref) timer.unref();
137
+ }
138
+ function clearTimer() { if (timer) { clearTimeout(timer); timer = null; } }
139
+
140
+ function destroy(err) {
141
+ if (destroyed) return;
142
+ destroyed = true;
143
+ clearTimer();
144
+ connections.delete(socket);
145
+ if (current && current.req && !current.req.complete) current.req._abort(err || null);
146
+ if (current && current.res) current.res._abortResponse(); // see ServerResponse
147
+ try { socket.destroy(); } catch (e) {}
148
+ }
149
+
150
+ // Waiting for the first byte of the first request.
151
+ setTimer(headersTimeout, 'headers');
152
+
153
+
154
+ // ---- Reading ----
155
+
156
+ socket.on('data', function (chunk) {
157
+ if (destroyed) return;
158
+ // A request is arriving: not idle (closeIdleConnections, close()).
159
+ socket._quicoBusy = true;
160
+
161
+ try {
162
+ parser.execute(chunk);
163
+ } catch (err) {
164
+ // h1.js reports parse failures via 'error'. A throw from here is a bug
165
+ // in a listener (a handler that threw synchronously), not bad input.
166
+ ev.emit('clientError', err, socket);
167
+ destroy(err);
168
+ }
169
+ });
170
+
171
+ function resumeParsing() {
172
+ if (destroyed) return;
173
+ holdForResponse = false;
174
+ updateReading();
175
+ try { parser.resume(); }
176
+ catch (err) { ev.emit('clientError', err, socket); destroy(err); }
177
+ }
178
+
179
+ // Reading from the socket stops while nothing would take what it brings,
180
+ // as node:http does it — the kernel's buffer fills, and TCP slows the
181
+ // client down:
182
+ // holdForReader the request body is not being read: the
183
+ // IncomingMessage's buffer is full (push() said so),
184
+ // until it asks for more (_read).
185
+ // holdForResponse the request is complete and its response is not:
186
+ // what follows is the next request, not looked at
187
+ // until then (pipelining, RFC 9112 §9.3).
188
+ // Neither stopped the socket. The parser took everything in — a body
189
+ // the handler had not started reading (it was waiting on a lookup, say)
190
+ // was held in memory as fast as the client could send it, hundreds of MB
191
+ // in seconds; so were pipelined bytes behind a slow response.
192
+ var holdForReader = false, holdForResponse = false;
193
+ function updateReading() {
194
+ if (destroyed) return;
195
+ if (holdForReader || holdForResponse) { if (!socket.isPaused()) socket.pause(); }
196
+ else if (socket.isPaused()) socket.resume();
197
+ }
198
+
199
+ socket.on('error', function (err) {
200
+ // A peer that vanishes mid-request is ordinary, not exceptional; surface
201
+ // it for logging but never let it reach an uncaught handler.
202
+ ev.emit('clientError', err, socket);
203
+ destroy(err);
204
+ });
205
+
206
+ socket.on('close', function () { destroy(null); });
207
+
208
+ socket.on('end', function () {
209
+ // The peer half-closed. Anything mid-message is truncated; the parser
210
+ // decides which.
211
+ try { parser.eof(); } catch (e) {}
212
+ });
213
+
214
+
215
+ // ---- Parser events ----
216
+
217
+ parser.on('error', function (err) {
218
+ ev.emit('clientError', err, socket);
219
+
220
+ // Answer if we still can. A framing failure means we no longer know where
221
+ // the next message starts, so the connection always closes after this.
222
+ if (!destroyed && socket.writable) {
223
+ var code = err.statusCode || 400;
224
+ if (options.stats) options.stats.status(options.stats.closes.http1.sent, code);
225
+ try {
226
+ socket.end(buildResponseHead(code, null, {
227
+ 'Connection': 'close',
228
+ 'Content-Length': '0'
229
+ }));
230
+ } catch (e) {}
231
+ }
232
+ destroy(err);
233
+ });
234
+
235
+ parser.on('headers', function (info) {
236
+ socket._quicoBusy = true;
237
+ clearTimer();
238
+ setTimer(requestTimeout, 'request');
239
+ served++;
240
+ if (maxRequests > 0 && served > maxRequests) {
241
+ // Past the limit (node:http: "subsequent requests ... will get 503").
242
+ try {
243
+ socket.end(buildResponseHead(503, null, { 'Connection': 'close', 'Content-Length': '0' }));
244
+ } catch (e) {}
245
+ destroy(null);
246
+ return;
247
+ }
248
+
249
+ var req = new IncomingMessage({
250
+ httpVersion: info.httpVersion,
251
+ method: info.method,
252
+ url: info.url,
253
+ headers: info.headers,
254
+ socket: socket
255
+ });
256
+ req.rawHeaders = info.rawHeaders;
257
+ req.complete = false;
258
+ // Read whole from 0-RTT data (TLS early data): it could be a replay
259
+ // (server.js holds it to earlyDataFilter). A request completed by
260
+ // bytes after the handshake cannot be replayed, and is not early.
261
+ req.earlyData = socket.isEarlyData === true;
262
+
263
+ var res = new ServerResponse({ socket: socket });
264
+ res.req = req;
265
+ // res.destroy([err]) / req.destroy([err]) by the application, the
266
+ // response unfinished: the socket goes, as on node:http — the only way
267
+ // HTTP/1.1 has to say the response will not be completed. It stayed
268
+ // open, and the client waited for the rest.
269
+ res._onDestroy(function () { destroy(null); });
270
+ req._onDestroy(function (err) { if (!res.writableFinished && !res.destroyed) res.destroy(err); });
271
+ // The reader wants more: reading from the socket goes on (see
272
+ // holdForReader).
273
+ req._read = function () {
274
+ if (holdForReader && current && current.req === req) { holdForReader = false; updateReading(); }
275
+ };
276
+
277
+ current = { req: req, res: res, info: info };
278
+ wireResponse(info, req, res, maxRequests > 0 && served >= maxRequests);
279
+
280
+ // Expect: 100-continue (RFC 9110 §10.1.1). The client is waiting for
281
+ // permission before sending the body; if we never answer, it stalls for
282
+ // its own timeout and then sends anyway. Default to allowing it, and let
283
+ // a 'checkContinue' listener take over when one is registered — the same
284
+ // split node:http makes.
285
+ var expect = String(info.headers['expect'] || '').toLowerCase();
286
+ if (expect === '100-continue') {
287
+ if (ev.listenerCount('checkContinue') > 0) {
288
+ runRequestHandler(function () { ev.emit('checkContinue', req, res); }, handlerFailed);
289
+ return; // the listener owns the exchange from here
290
+ }
291
+ try { socket.write('HTTP/1.1 100 Continue\r\n\r\n'); } catch (e) {}
292
+ }
293
+
294
+ // No special case for info.upgrade here any more. A request that will
295
+ // actually switch protocols never reaches this handler — h1.js emits
296
+ // 'upgrade' instead of 'headers' for those. What DOES reach it is an
297
+ // Upgrade request that cannot switch, such as one carrying a body: that
298
+ // is an ordinary request the application should answer (and probably
299
+ // ignore the Upgrade on), not one to drop on the floor. The early return
300
+ // that used to sit here hung exactly those.
301
+ runRequestHandler(function () {
302
+ if (typeof handler === 'function') handler(req, res);
303
+ ev.emit('request', req, res);
304
+ }, handlerFailed);
305
+
306
+ // The handler threw (see runRequestHandler): a 500 if nothing has been
307
+ // sent yet, and the connection closed — the response may be half
308
+ // written, and nothing after it on this connection can be trusted to
309
+ // line up. A response it had already ended is left to finish.
310
+ function handlerFailed() {
311
+ if (destroyed || res.writableEnded) return;
312
+ if (!res.headersSent && socket.writable) {
313
+ try {
314
+ socket.end(buildResponseHead(500, null, { 'Connection': 'close', 'Content-Length': '0' }));
315
+ } catch (e) {}
316
+ }
317
+ destroy(null);
318
+ }
319
+ });
320
+
321
+ parser.on('body', function (chunk) {
322
+ var req = current && current.req;
323
+ if (!req) return;
324
+ // Destroyed by the application: it will read no more. Dropped, and
325
+ // reading goes on — a body nobody reads must not stop the connection.
326
+ if (req.destroyed) return;
327
+ if (req._pushData(chunk) === false && !holdForReader) {
328
+ holdForReader = true;
329
+ updateReading();
330
+ }
331
+ });
332
+
333
+ parser.on('trailers', function (trailers) {
334
+ if (current && current.req) current.req.trailers = trailers;
335
+ });
336
+
337
+ parser.on('end', function () {
338
+ // requestTimeout bounds the request's arrival, as node:http's does —
339
+ // not the response: a long download or an event stream runs as long as
340
+ // the application keeps writing. It ran until the response was done,
341
+ // harmless while the default was 0 (none), not with node's 300 s.
342
+ clearTimer();
343
+
344
+ // The request is complete. Stop looking at anything after it until its
345
+ // response is on the wire — pipelined request N+1 must not be handled
346
+ // before response N is sent. Pausing here rather than at 'headers' is
347
+ // the difference between "do not read ahead" and "do not read the body
348
+ // of the request we are answering", and the latter deadlocks any client
349
+ // that waits for 100-continue.
350
+ if (current && !current.res.finished) { parser.pause(); holdForResponse = true; updateReading(); }
351
+ if (current && current.req) current.req._endData();
352
+ });
353
+
354
+ parser.on('upgrade', function (info, head) {
355
+ clearTimer();
356
+
357
+ var req = new IncomingMessage({
358
+ httpVersion: info.httpVersion,
359
+ method: info.method,
360
+ url: info.url,
361
+ headers: info.headers,
362
+ socket: socket
363
+ });
364
+ req.rawHeaders = info.rawHeaders;
365
+ req.upgrade = true;
366
+ req._endData();
367
+
368
+ // Hand the raw socket over, exactly as node:http does. `head` is the
369
+ // bytes that arrived after the request head and already belong to the
370
+ // new protocol — a WebSocket client may put its first frame in the same
371
+ // TCP segment as the handshake, and dropping it loses that frame.
372
+ //
373
+ // From here we own nothing: no parser, no timers, no keep-alive.
374
+ connections.delete(socket);
375
+ destroyed = true; // stop our own handlers touching the socket
376
+ clearTimer();
377
+ socket.removeAllListeners('data');
378
+ socket.removeAllListeners('end');
379
+
380
+ if (ev.listenerCount('upgrade') === 0) {
381
+ // Accepted by shouldUpgradeCallback, yet nobody listens: as node:http
382
+ // does, the socket is destroyed — the request is half-switched, and
383
+ // there is no one to finish the job.
384
+ try { socket.destroy(); } catch (e) {}
385
+ return;
386
+ }
387
+
388
+ ev.emit('upgrade', req, socket, head);
389
+ });
390
+
391
+
392
+ // ============================================================
393
+ // Response wiring
394
+ //
395
+ // ServerResponse is transport-agnostic (h3_server overrides the same
396
+ // three hooks to write H3 frames). Here they become HTTP/1.1 bytes.
397
+ // ============================================================
398
+
399
+ function wireResponse(info, req, res, lastOnSocket) {
400
+ var chunked = false;
401
+ var bodyAllowed = true;
402
+ var willClose = !!lastOnSocket; // maxRequestsPerSocket reached: this one closes it
403
+ var finished = false;
404
+
405
+ res._flushHeaders = function () {
406
+ if (res.headersSent) return;
407
+
408
+ var headers = res.getHeaders();
409
+ var code = res.statusCode;
410
+
411
+ // Status codes that define themselves as bodiless, plus HEAD, whose
412
+ // response carries the headers of the body it is not sending.
413
+ var bodiless = statusHasNoBody(code);
414
+ bodyAllowed = !bodiless && info.method !== 'HEAD';
415
+
416
+ // A bodiless status must not carry framing headers at all: a
417
+ // Content-Length on a 204 makes some clients wait for a body that is
418
+ // never coming.
419
+ if (bodiless) {
420
+ delete headers['content-length'];
421
+ delete headers['transfer-encoding'];
422
+ }
423
+
424
+ // Framing, in the order RFC 9112 §6 resolves it.
425
+ if (!bodiless) {
426
+ if ('transfer-encoding' in headers) {
427
+ chunked = String(headers['transfer-encoding']).toLowerCase().indexOf('chunked') !== -1;
428
+ } else if ('content-length' in headers) {
429
+ chunked = false;
430
+ } else if (res._endLength !== undefined) {
431
+ // The whole body was given to end() with nothing written before
432
+ // it: its length is known, and sent — as node:http does. It was
433
+ // chunked: res.end('hello') cost a chunk header and a terminating
434
+ // chunk, and a client could not tell the size before it arrived.
435
+ headers['content-length'] = String(res._endLength);
436
+ chunked = false;
437
+ } else if (info.httpVersion === '1.1' && info.keepAlive) {
438
+ // Length unknown and the connection is reusable, so chunk it —
439
+ // otherwise the only way to delimit the body is to close, and
440
+ // keep-alive is lost for no reason.
441
+ headers['transfer-encoding'] = 'chunked';
442
+ chunked = true;
443
+ } else {
444
+ // HTTP/1.0, or the client asked to close: the body ends when the
445
+ // connection does. (1.0 has no chunked encoding.)
446
+ willClose = true;
447
+ }
448
+ }
449
+
450
+ // Connection header. `keepAlive` already accounts for the client's
451
+ // Connection: close and for HTTP/1.0 semantics.
452
+ var keepAlive = info.keepAlive && !willClose && !closing;
453
+ if (String(headers['connection'] || '').toLowerCase() === 'close') keepAlive = false;
454
+
455
+ headers['connection'] = keepAlive ? 'keep-alive' : 'close';
456
+ willClose = !keepAlive;
457
+
458
+ if (keepAlive && info.httpVersion === '1.0') headers['keep-alive'] = 'timeout=5';
459
+ if (!('date' in headers) && res.sendDate !== false) headers['date'] = httpDate();
460
+ // A function when the server was built before its port was known —
461
+ // the unified server builds its layers at creation and learns the
462
+ // port from listen(), or from options.port when fed from outside.
463
+ var altSvcNow = typeof altSvc === 'function' ? altSvc() : altSvc;
464
+ if (altSvcNow && !('alt-svc' in headers)) headers['alt-svc'] = altSvcNow;
465
+
466
+ // The application's reason phrase, else the standard one for the code
467
+ // — and statusMessage then says what was sent, as node:http does.
468
+ var reason = res.statusMessage || STATUS_TEXT[code] || '';
469
+ res.statusMessage = reason;
470
+ var head = buildResponseHead(code, reason, headers);
471
+ // Corked: the head and the body written after it in this tick — all
472
+ // of res.end(body) — leave as one segment, not one each.
473
+ corkForTick(socket);
474
+ try { socket.write(head); } catch (e) { destroy(e); return; }
475
+ res.headersSent = true;
476
+ };
477
+
478
+ // 103 Early Hints before the response (RFC 8297): an interim response,
479
+ // written whole before the final head. HTTP/1.0 has no interim
480
+ // responses, and after the head it is too late — both send nothing.
481
+ // node:http's signature: (hints, callback).
482
+ res.writeEarlyHints = function (hints, callback) {
483
+ var ok = !res.headersSent && info.httpVersion !== '1.0' && !destroyed;
484
+ if (ok) {
485
+ try { socket.write(buildResponseHead(103, null, earlyHintHeaders(hints))); }
486
+ catch (e) { ok = false; }
487
+ }
488
+ if (typeof callback === 'function') process.nextTick(callback);
489
+ };
490
+
491
+ // 102 Processing (node:http's writeProcessing): an interim response,
492
+ // as Early Hints are.
493
+ res.writeProcessing = function () {
494
+ if (res.headersSent || info.httpVersion === '1.0' || destroyed) return;
495
+ try { socket.write(buildResponseHead(102, null, {})); } catch (e) {}
496
+ };
497
+
498
+ res._write = function (chunk, encoding, callback) {
499
+ if (!res.headersSent) res._flushHeaders();
500
+ if (typeof chunk === 'string') chunk = Buffer.from(chunk, encoding);
501
+
502
+ // A HEAD response, or a bodiless status, must send no body — but the
503
+ // application still wrote one, and that is fine: it wrote the body it
504
+ // WOULD have sent. Swallow the bytes; the headers already described
505
+ // them.
506
+ if (!bodyAllowed) { callback(); return; }
507
+
508
+ var ok = true;
509
+ try {
510
+ if (!chunked) ok = socket.write(chunk);
511
+ else {
512
+ var parts = encodeChunk(chunk);
513
+ if (parts.length) corkForTick(socket);
514
+ for (var i = 0; i < parts.length; i++) ok = socket.write(parts[i]);
515
+ }
516
+ } catch (e) { callback(e); return; }
517
+
518
+ // Respect back-pressure: without this a fast producer piped into a slow
519
+ // client buffers the whole body in memory.
520
+ if (ok) callback();
521
+ else socket.once('drain', callback);
522
+ };
523
+
524
+ res._final = function (callback) {
525
+ if (finished) { callback(); return; }
526
+ finished = true;
527
+ if (!res.headersSent) res._flushHeaders();
528
+
529
+ try {
530
+ if (bodyAllowed && chunked) socket.write(encodeLastChunk(trailerFields(res.trailers)));
531
+ } catch (e) { callback(e); return; }
532
+
533
+ callback();
534
+ onResponseComplete();
535
+ };
536
+
537
+ function onResponseComplete() {
538
+ clearTimer();
539
+ current = null;
540
+ socket._quicoBusy = false;
541
+ // Whatever of the body is still to come is dropped from here on (the
542
+ // parser reads it, to find the next request): the reader no longer
543
+ // holds the socket back.
544
+ holdForReader = false;
545
+ updateReading();
546
+
547
+ if (willClose || closing) {
548
+ // Half-close: let the peer read what we wrote before the socket goes.
549
+ try { socket.end(); } catch (e) {}
550
+ return;
551
+ }
552
+
553
+ // Ready for the next request on this connection.
554
+ setTimer(keepAliveTimeout, 'keep-alive');
555
+ resumeParsing();
556
+ }
557
+ }
558
+ }
559
+
560
+
561
+ // ============================================================
562
+ // Listening
563
+ // ============================================================
564
+
565
+ // ============================================================
566
+ // Listening
567
+ //
568
+ // Sockets, TLS and ALPN belong to tcp_listener, which h2_server and
569
+ // ws_server share — a client picks between them with ALPN, and that choice
570
+ // has to be made in one place or the three ALPN lists drift apart. What
571
+ // remains here is a convenience: an h1-only server that owns its listener,
572
+ // so this file can still be used (and tested) on its own.
573
+ //
574
+ // The unified server does NOT call this. It builds one listener, registers
575
+ // handleConnection under 'http/1.1', and registers h2_server's under 'h2'.
576
+ // ============================================================
577
+
578
+ function listen(port, host, callback) {
579
+ if (typeof host === 'function') { callback = host; host = undefined; }
580
+
581
+ tcpServer = createTcpListener({
582
+ tls: options.tls,
583
+ key: options.key,
584
+ cert: options.cert,
585
+ ca: options.ca,
586
+ SNICallback: options.SNICallback
587
+ });
588
+ tcpServer.register('http/1.1', handleConnection, { managesClose: true });
589
+ tcpServer.on('error', function (err) { ev.emit('error', err); });
590
+ tcpServer.on('clientError', function (err, sock) { ev.emit('clientError', err, sock); });
591
+ tcpServer.on('tlsClientError', function (err, sock) { ev.emit('tlsClientError', err, sock); });
592
+ tcpServer.listen(port, host, function () {
593
+ ev.emit('listening');
594
+ if (typeof callback === 'function') callback();
595
+ });
596
+ return server;
597
+ }
598
+
599
+ // ---- Closing, as node:http ----
600
+ //
601
+ // Idle: a connection between requests (or before its first) — no request
602
+ // arriving, no response being written. Closing one costs nothing.
603
+ function closeIdleConnections() {
604
+ for (var s of connections) if (!s._quicoBusy) { try { s.destroy(); } catch (e) {} }
605
+ }
606
+ // All, mid-request too. Not the sockets handed to 'upgrade' (a WebSocket):
607
+ // those left this server at the upgrade, as in node:http.
608
+ function closeAllConnections() {
609
+ for (var s of connections) { try { s.destroy(); } catch (e) {} }
610
+ }
611
+ // No new connections, the idle ones closed; each busy one closes as its
612
+ // response completes (onResponseComplete). It used to end every socket at
613
+ // once: a response being written was cut off.
614
+ function beginClose() {
615
+ closing = true;
616
+ closeIdleConnections();
617
+ }
618
+
619
+ function close(callback) {
620
+ beginClose();
621
+ if (!tcpServer) { if (callback) callback(); return; }
622
+ tcpServer.close(callback);
623
+ }
624
+
625
+ // node:http's timeouts and limits, changed while the server runs: from
626
+ // the next request (or connection) on.
627
+ function setLimits(o) {
628
+ if (o.headersTimeout !== undefined) headersTimeout = o.headersTimeout;
629
+ if (o.keepAliveTimeout !== undefined) keepAliveTimeout = o.keepAliveTimeout;
630
+ if (o.requestTimeout !== undefined) requestTimeout = o.requestTimeout;
631
+ if (o.maxHeaderCount !== undefined) maxHeaderCount = o.maxHeaderCount;
632
+ if (o.maxRequestsPerSocket !== undefined) maxRequests = maxRequestsPerSocket(o.maxRequestsPerSocket);
633
+ }
634
+
635
+ function address() {
636
+ return tcpServer && tcpServer.address ? tcpServer.address() : null;
637
+ }
638
+
639
+ var server = {
640
+ listen: listen,
641
+ close: close,
642
+ address: address,
643
+ on: function (n, f) { ev.on(n, f); return server; },
644
+ once: function (n, f) { ev.once(n, f); return server; },
645
+ off: function (n, f) { ev.off(n, f); return server; },
646
+ removeListener: function (n, f) { ev.removeListener(n, f); return server; },
647
+ emit: function () { ev.emit.apply(ev, arguments); return server; },
648
+ listenerCount: function (n) { return ev.listenerCount(n); },
649
+ setTimeout: function (ms) { headersTimeout = ms; return server; },
650
+ closeIdleConnections: closeIdleConnections,
651
+ closeAllConnections: closeAllConnections,
652
+ beginClose: beginClose,
653
+ setLimits: setLimits,
654
+ get connectionCount() { return connections.size; },
655
+
656
+ /** Attach an already-connected Duplex, bypassing the listener. Lets the
657
+ * unified server share one TCP port, and makes the whole stack testable
658
+ * without binding anything. */
659
+ handleConnection: handleConnection
660
+ };
661
+
662
+ return server;
663
+ }
664
+
665
+
666
+ export { createServer };