quico 0.4.0 → 0.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,624 @@
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
+
159
+ try {
160
+ parser.execute(chunk);
161
+ } catch (err) {
162
+ // h1.js reports parse failures via 'error'. A throw from here is a bug
163
+ // in a listener (a handler that threw synchronously), not bad input.
164
+ ev.emit('clientError', err, socket);
165
+ destroy(err);
166
+ }
167
+ });
168
+
169
+ function resumeParsing() {
170
+ if (destroyed) return;
171
+ holdForResponse = false;
172
+ updateReading();
173
+ try { parser.resume(); }
174
+ catch (err) { ev.emit('clientError', err, socket); destroy(err); }
175
+ }
176
+
177
+ // Reading from the socket stops while nothing would take what it brings,
178
+ // as node:http does it — the kernel's buffer fills, and TCP slows the
179
+ // client down:
180
+ // holdForReader the request body is not being read: the
181
+ // IncomingMessage's buffer is full (push() said so),
182
+ // until it asks for more (_read).
183
+ // holdForResponse the request is complete and its response is not:
184
+ // what follows is the next request, not looked at
185
+ // until then (pipelining, RFC 9112 §9.3).
186
+ // Neither stopped the socket. The parser took everything in — a body
187
+ // the handler had not started reading (it was waiting on a lookup, say)
188
+ // was held in memory as fast as the client could send it, hundreds of MB
189
+ // in seconds; so were pipelined bytes behind a slow response.
190
+ var holdForReader = false, holdForResponse = false;
191
+ function updateReading() {
192
+ if (destroyed) return;
193
+ if (holdForReader || holdForResponse) { if (!socket.isPaused()) socket.pause(); }
194
+ else if (socket.isPaused()) socket.resume();
195
+ }
196
+
197
+ socket.on('error', function (err) {
198
+ // A peer that vanishes mid-request is ordinary, not exceptional; surface
199
+ // it for logging but never let it reach an uncaught handler.
200
+ ev.emit('clientError', err, socket);
201
+ destroy(err);
202
+ });
203
+
204
+ socket.on('close', function () { destroy(null); });
205
+
206
+ socket.on('end', function () {
207
+ // The peer half-closed. Anything mid-message is truncated; the parser
208
+ // decides which.
209
+ try { parser.eof(); } catch (e) {}
210
+ });
211
+
212
+
213
+ // ---- Parser events ----
214
+
215
+ parser.on('error', function (err) {
216
+ ev.emit('clientError', err, socket);
217
+
218
+ // Answer if we still can. A framing failure means we no longer know where
219
+ // the next message starts, so the connection always closes after this.
220
+ if (!destroyed && socket.writable) {
221
+ var code = err.statusCode || 400;
222
+ if (options.stats) options.stats.status(options.stats.closes.http1.sent, code);
223
+ try {
224
+ socket.end(buildResponseHead(code, null, {
225
+ 'Connection': 'close',
226
+ 'Content-Length': '0'
227
+ }));
228
+ } catch (e) {}
229
+ }
230
+ destroy(err);
231
+ });
232
+
233
+ parser.on('headers', function (info) {
234
+ clearTimer();
235
+ setTimer(requestTimeout, 'request');
236
+ served++;
237
+ if (maxRequests > 0 && served > maxRequests) {
238
+ // Past the limit (node:http: "subsequent requests ... will get 503").
239
+ try {
240
+ socket.end(buildResponseHead(503, null, { 'Connection': 'close', 'Content-Length': '0' }));
241
+ } catch (e) {}
242
+ destroy(null);
243
+ return;
244
+ }
245
+
246
+ var req = new IncomingMessage({
247
+ httpVersion: info.httpVersion,
248
+ method: info.method,
249
+ url: info.url,
250
+ headers: info.headers,
251
+ socket: socket
252
+ });
253
+ req.rawHeaders = info.rawHeaders;
254
+ req.complete = false;
255
+ // Read whole from 0-RTT data (TLS early data): it could be a replay
256
+ // (server.js holds it to earlyDataFilter). A request completed by
257
+ // bytes after the handshake cannot be replayed, and is not early.
258
+ req.earlyData = socket.isEarlyData === true;
259
+
260
+ var res = new ServerResponse({ socket: socket });
261
+ res.req = req;
262
+ // res.destroy([err]) / req.destroy([err]) by the application, the
263
+ // response unfinished: the socket goes, as on node:http — the only way
264
+ // HTTP/1.1 has to say the response will not be completed. It stayed
265
+ // open, and the client waited for the rest.
266
+ res._onDestroy(function () { destroy(null); });
267
+ req._onDestroy(function (err) { if (!res.writableFinished && !res.destroyed) res.destroy(err); });
268
+ // The reader wants more: reading from the socket goes on (see
269
+ // holdForReader).
270
+ req._read = function () {
271
+ if (holdForReader && current && current.req === req) { holdForReader = false; updateReading(); }
272
+ };
273
+
274
+ current = { req: req, res: res, info: info };
275
+ wireResponse(info, req, res, maxRequests > 0 && served >= maxRequests);
276
+
277
+ // Expect: 100-continue (RFC 9110 §10.1.1). The client is waiting for
278
+ // permission before sending the body; if we never answer, it stalls for
279
+ // its own timeout and then sends anyway. Default to allowing it, and let
280
+ // a 'checkContinue' listener take over when one is registered — the same
281
+ // split node:http makes.
282
+ var expect = String(info.headers['expect'] || '').toLowerCase();
283
+ if (expect === '100-continue') {
284
+ if (ev.listenerCount('checkContinue') > 0) {
285
+ runRequestHandler(function () { ev.emit('checkContinue', req, res); }, handlerFailed);
286
+ return; // the listener owns the exchange from here
287
+ }
288
+ try { socket.write('HTTP/1.1 100 Continue\r\n\r\n'); } catch (e) {}
289
+ }
290
+
291
+ // No special case for info.upgrade here any more. A request that will
292
+ // actually switch protocols never reaches this handler — h1.js emits
293
+ // 'upgrade' instead of 'headers' for those. What DOES reach it is an
294
+ // Upgrade request that cannot switch, such as one carrying a body: that
295
+ // is an ordinary request the application should answer (and probably
296
+ // ignore the Upgrade on), not one to drop on the floor. The early return
297
+ // that used to sit here hung exactly those.
298
+ runRequestHandler(function () {
299
+ if (typeof handler === 'function') handler(req, res);
300
+ ev.emit('request', req, res);
301
+ }, handlerFailed);
302
+
303
+ // The handler threw (see runRequestHandler): a 500 if nothing has been
304
+ // sent yet, and the connection closed — the response may be half
305
+ // written, and nothing after it on this connection can be trusted to
306
+ // line up. A response it had already ended is left to finish.
307
+ function handlerFailed() {
308
+ if (destroyed || res.writableEnded) return;
309
+ if (!res.headersSent && socket.writable) {
310
+ try {
311
+ socket.end(buildResponseHead(500, null, { 'Connection': 'close', 'Content-Length': '0' }));
312
+ } catch (e) {}
313
+ }
314
+ destroy(null);
315
+ }
316
+ });
317
+
318
+ parser.on('body', function (chunk) {
319
+ var req = current && current.req;
320
+ if (!req) return;
321
+ // Destroyed by the application: it will read no more. Dropped, and
322
+ // reading goes on — a body nobody reads must not stop the connection.
323
+ if (req.destroyed) return;
324
+ if (req._pushData(chunk) === false && !holdForReader) {
325
+ holdForReader = true;
326
+ updateReading();
327
+ }
328
+ });
329
+
330
+ parser.on('trailers', function (trailers) {
331
+ if (current && current.req) current.req.trailers = trailers;
332
+ });
333
+
334
+ parser.on('end', function () {
335
+ // requestTimeout bounds the request's arrival, as node:http's does —
336
+ // not the response: a long download or an event stream runs as long as
337
+ // the application keeps writing. It ran until the response was done,
338
+ // harmless while the default was 0 (none), not with node's 300 s.
339
+ clearTimer();
340
+
341
+ // The request is complete. Stop looking at anything after it until its
342
+ // response is on the wire — pipelined request N+1 must not be handled
343
+ // before response N is sent. Pausing here rather than at 'headers' is
344
+ // the difference between "do not read ahead" and "do not read the body
345
+ // of the request we are answering", and the latter deadlocks any client
346
+ // that waits for 100-continue.
347
+ if (current && !current.res.finished) { parser.pause(); holdForResponse = true; updateReading(); }
348
+ if (current && current.req) current.req._endData();
349
+ });
350
+
351
+ parser.on('upgrade', function (info, head) {
352
+ clearTimer();
353
+
354
+ var req = new IncomingMessage({
355
+ httpVersion: info.httpVersion,
356
+ method: info.method,
357
+ url: info.url,
358
+ headers: info.headers,
359
+ socket: socket
360
+ });
361
+ req.rawHeaders = info.rawHeaders;
362
+ req._endData();
363
+
364
+ // Hand the raw socket over, exactly as node:http does. `head` is the
365
+ // bytes that arrived after the request head and already belong to the
366
+ // new protocol — a WebSocket client may put its first frame in the same
367
+ // TCP segment as the handshake, and dropping it loses that frame.
368
+ //
369
+ // From here we own nothing: no parser, no timers, no keep-alive.
370
+ connections.delete(socket);
371
+ destroyed = true; // stop our own handlers touching the socket
372
+ clearTimer();
373
+ socket.removeAllListeners('data');
374
+ socket.removeAllListeners('end');
375
+
376
+ if (ev.listenerCount('upgrade') === 0) {
377
+ // Accepted by shouldUpgradeCallback, yet nobody listens: as node:http
378
+ // does, the socket is destroyed — the request is half-switched, and
379
+ // there is no one to finish the job.
380
+ try { socket.destroy(); } catch (e) {}
381
+ return;
382
+ }
383
+
384
+ ev.emit('upgrade', req, socket, head);
385
+ });
386
+
387
+
388
+ // ============================================================
389
+ // Response wiring
390
+ //
391
+ // ServerResponse is transport-agnostic (h3_server overrides the same
392
+ // three hooks to write H3 frames). Here they become HTTP/1.1 bytes.
393
+ // ============================================================
394
+
395
+ function wireResponse(info, req, res, lastOnSocket) {
396
+ var chunked = false;
397
+ var bodyAllowed = true;
398
+ var willClose = !!lastOnSocket; // maxRequestsPerSocket reached: this one closes it
399
+ var finished = false;
400
+
401
+ res._flushHeaders = function () {
402
+ if (res.headersSent) return;
403
+
404
+ var headers = res.getHeaders();
405
+ var code = res.statusCode;
406
+
407
+ // Status codes that define themselves as bodiless, plus HEAD, whose
408
+ // response carries the headers of the body it is not sending.
409
+ var bodiless = statusHasNoBody(code);
410
+ bodyAllowed = !bodiless && info.method !== 'HEAD';
411
+
412
+ // A bodiless status must not carry framing headers at all: a
413
+ // Content-Length on a 204 makes some clients wait for a body that is
414
+ // never coming.
415
+ if (bodiless) {
416
+ delete headers['content-length'];
417
+ delete headers['transfer-encoding'];
418
+ }
419
+
420
+ // Framing, in the order RFC 9112 §6 resolves it.
421
+ if (!bodiless) {
422
+ if ('transfer-encoding' in headers) {
423
+ chunked = String(headers['transfer-encoding']).toLowerCase().indexOf('chunked') !== -1;
424
+ } else if ('content-length' in headers) {
425
+ chunked = false;
426
+ } else if (res._endLength !== undefined) {
427
+ // The whole body was given to end() with nothing written before
428
+ // it: its length is known, and sent — as node:http does. It was
429
+ // chunked: res.end('hello') cost a chunk header and a terminating
430
+ // chunk, and a client could not tell the size before it arrived.
431
+ headers['content-length'] = String(res._endLength);
432
+ chunked = false;
433
+ } else if (info.httpVersion === '1.1' && info.keepAlive) {
434
+ // Length unknown and the connection is reusable, so chunk it —
435
+ // otherwise the only way to delimit the body is to close, and
436
+ // keep-alive is lost for no reason.
437
+ headers['transfer-encoding'] = 'chunked';
438
+ chunked = true;
439
+ } else {
440
+ // HTTP/1.0, or the client asked to close: the body ends when the
441
+ // connection does. (1.0 has no chunked encoding.)
442
+ willClose = true;
443
+ }
444
+ }
445
+
446
+ // Connection header. `keepAlive` already accounts for the client's
447
+ // Connection: close and for HTTP/1.0 semantics.
448
+ var keepAlive = info.keepAlive && !willClose && !closing;
449
+ if (String(headers['connection'] || '').toLowerCase() === 'close') keepAlive = false;
450
+
451
+ headers['connection'] = keepAlive ? 'keep-alive' : 'close';
452
+ willClose = !keepAlive;
453
+
454
+ if (keepAlive && info.httpVersion === '1.0') headers['keep-alive'] = 'timeout=5';
455
+ if (!('date' in headers)) headers['date'] = httpDate();
456
+ // A function when the server was built before its port was known —
457
+ // the unified server builds its layers at creation and learns the
458
+ // port from listen(), or from options.port when fed from outside.
459
+ var altSvcNow = typeof altSvc === 'function' ? altSvc() : altSvc;
460
+ if (altSvcNow && !('alt-svc' in headers)) headers['alt-svc'] = altSvcNow;
461
+
462
+ // The application's reason phrase, else the standard one for the code
463
+ // — and statusMessage then says what was sent, as node:http does.
464
+ var reason = res.statusMessage || STATUS_TEXT[code] || '';
465
+ res.statusMessage = reason;
466
+ var head = buildResponseHead(code, reason, headers);
467
+ // Corked: the head and the body written after it in this tick — all
468
+ // of res.end(body) — leave as one segment, not one each.
469
+ corkForTick(socket);
470
+ try { socket.write(head); } catch (e) { destroy(e); return; }
471
+ res.headersSent = true;
472
+ };
473
+
474
+ // 103 Early Hints before the response (RFC 8297): an interim response,
475
+ // written whole before the final head. HTTP/1.0 has no interim
476
+ // responses, and after the head it is too late — both send nothing.
477
+ // node:http's signature: (hints, callback).
478
+ res.writeEarlyHints = function (hints, callback) {
479
+ var ok = !res.headersSent && info.httpVersion !== '1.0' && !destroyed;
480
+ if (ok) {
481
+ try { socket.write(buildResponseHead(103, null, earlyHintHeaders(hints))); }
482
+ catch (e) { ok = false; }
483
+ }
484
+ if (typeof callback === 'function') process.nextTick(callback);
485
+ };
486
+
487
+ res._write = function (chunk, encoding, callback) {
488
+ if (!res.headersSent) res._flushHeaders();
489
+ if (typeof chunk === 'string') chunk = Buffer.from(chunk, encoding);
490
+
491
+ // A HEAD response, or a bodiless status, must send no body — but the
492
+ // application still wrote one, and that is fine: it wrote the body it
493
+ // WOULD have sent. Swallow the bytes; the headers already described
494
+ // them.
495
+ if (!bodyAllowed) { callback(); return; }
496
+
497
+ var ok = true;
498
+ try {
499
+ if (!chunked) ok = socket.write(chunk);
500
+ else {
501
+ var parts = encodeChunk(chunk);
502
+ if (parts.length) corkForTick(socket);
503
+ for (var i = 0; i < parts.length; i++) ok = socket.write(parts[i]);
504
+ }
505
+ } catch (e) { callback(e); return; }
506
+
507
+ // Respect back-pressure: without this a fast producer piped into a slow
508
+ // client buffers the whole body in memory.
509
+ if (ok) callback();
510
+ else socket.once('drain', callback);
511
+ };
512
+
513
+ res._final = function (callback) {
514
+ if (finished) { callback(); return; }
515
+ finished = true;
516
+ if (!res.headersSent) res._flushHeaders();
517
+
518
+ try {
519
+ if (bodyAllowed && chunked) socket.write(encodeLastChunk(trailerFields(res.trailers)));
520
+ } catch (e) { callback(e); return; }
521
+
522
+ callback();
523
+ onResponseComplete();
524
+ };
525
+
526
+ function onResponseComplete() {
527
+ clearTimer();
528
+ current = null;
529
+ // Whatever of the body is still to come is dropped from here on (the
530
+ // parser reads it, to find the next request): the reader no longer
531
+ // holds the socket back.
532
+ holdForReader = false;
533
+ updateReading();
534
+
535
+ if (willClose || closing) {
536
+ // Half-close: let the peer read what we wrote before the socket goes.
537
+ try { socket.end(); } catch (e) {}
538
+ return;
539
+ }
540
+
541
+ // Ready for the next request on this connection.
542
+ setTimer(keepAliveTimeout, 'keep-alive');
543
+ resumeParsing();
544
+ }
545
+ }
546
+ }
547
+
548
+
549
+ // ============================================================
550
+ // Listening
551
+ // ============================================================
552
+
553
+ // ============================================================
554
+ // Listening
555
+ //
556
+ // Sockets, TLS and ALPN belong to tcp_listener, which h2_server and
557
+ // ws_server share — a client picks between them with ALPN, and that choice
558
+ // has to be made in one place or the three ALPN lists drift apart. What
559
+ // remains here is a convenience: an h1-only server that owns its listener,
560
+ // so this file can still be used (and tested) on its own.
561
+ //
562
+ // The unified server does NOT call this. It builds one listener, registers
563
+ // handleConnection under 'http/1.1', and registers h2_server's under 'h2'.
564
+ // ============================================================
565
+
566
+ function listen(port, host, callback) {
567
+ if (typeof host === 'function') { callback = host; host = undefined; }
568
+
569
+ tcpServer = createTcpListener({
570
+ tls: options.tls,
571
+ key: options.key,
572
+ cert: options.cert,
573
+ ca: options.ca,
574
+ SNICallback: options.SNICallback
575
+ });
576
+ tcpServer.register('http/1.1', handleConnection);
577
+ tcpServer.on('error', function (err) { ev.emit('error', err); });
578
+ tcpServer.on('clientError', function (err, sock) { ev.emit('clientError', err, sock); });
579
+ tcpServer.on('tlsClientError', function (err, sock) { ev.emit('tlsClientError', err, sock); });
580
+ tcpServer.listen(port, host, function () {
581
+ ev.emit('listening');
582
+ if (typeof callback === 'function') callback();
583
+ });
584
+ return server;
585
+ }
586
+
587
+ function close(callback) {
588
+ closing = true;
589
+ if (!tcpServer) {
590
+ // Sockets we were handed rather than accepted: still ours to close.
591
+ for (var s of connections) { try { s.end(); } catch (e) {} }
592
+ if (callback) callback();
593
+ return;
594
+ }
595
+ tcpServer.close(callback);
596
+ }
597
+
598
+ function address() {
599
+ return tcpServer && tcpServer.address ? tcpServer.address() : null;
600
+ }
601
+
602
+ var server = {
603
+ listen: listen,
604
+ close: close,
605
+ address: address,
606
+ on: function (n, f) { ev.on(n, f); return server; },
607
+ once: function (n, f) { ev.once(n, f); return server; },
608
+ off: function (n, f) { ev.off(n, f); return server; },
609
+ removeListener: function (n, f) { ev.removeListener(n, f); return server; },
610
+ emit: function () { ev.emit.apply(ev, arguments); return server; },
611
+ listenerCount: function (n) { return ev.listenerCount(n); },
612
+ setTimeout: function (ms) { headersTimeout = ms; return server; },
613
+
614
+ /** Attach an already-connected Duplex, bypassing the listener. Lets the
615
+ * unified server share one TCP port, and makes the whole stack testable
616
+ * without binding anything. */
617
+ handleConnection: handleConnection
618
+ };
619
+
620
+ return server;
621
+ }
622
+
623
+
624
+ export { createServer };