webhoster 0.1.1 → 0.3.1

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.
Files changed (87) hide show
  1. package/.eslintrc.json +74 -58
  2. package/.github/copilot-instructions.md +100 -0
  3. package/.github/workflows/test-matrix.yml +37 -0
  4. package/.test/benchmark.js +28 -0
  5. package/.test/constants.js +4 -0
  6. package/{test → .test}/http2server.js +1 -1
  7. package/{test → .test}/httpserver.js +1 -1
  8. package/{test → .test}/index.js +178 -192
  9. package/.test/multipromise.js +32 -0
  10. package/{test → .test}/tls.js +3 -3
  11. package/{test → .test}/urlencoded.js +3 -0
  12. package/.vscode/launch.json +24 -3
  13. package/README.md +116 -90
  14. package/data/CookieObject.js +14 -14
  15. package/errata/socketio.js +6 -11
  16. package/examples/starter.js +11 -0
  17. package/helpers/HeadersParser.js +7 -8
  18. package/helpers/HttpListener.js +387 -42
  19. package/helpers/RequestHeaders.js +43 -36
  20. package/helpers/RequestReader.js +27 -26
  21. package/helpers/ResponseHeaders.js +47 -36
  22. package/jsconfig.json +1 -1
  23. package/lib/HttpHandler.js +447 -277
  24. package/lib/HttpRequest.js +383 -39
  25. package/lib/HttpResponse.js +316 -52
  26. package/lib/HttpTransaction.js +146 -0
  27. package/middleware/AutoHeadersMiddleware.js +73 -0
  28. package/middleware/CORSMiddleware.js +45 -47
  29. package/middleware/CaseInsensitiveHeadersMiddleware.js +5 -11
  30. package/middleware/ContentDecoderMiddleware.js +81 -35
  31. package/middleware/ContentEncoderMiddleware.js +179 -132
  32. package/middleware/ContentLengthMiddleware.js +66 -41
  33. package/middleware/ContentWriterMiddleware.js +5 -5
  34. package/middleware/HashMiddleware.js +68 -40
  35. package/middleware/HeadMethodMiddleware.js +24 -21
  36. package/middleware/MethodMiddleware.js +29 -36
  37. package/middleware/PathMiddleware.js +49 -66
  38. package/middleware/ReadFormData.js +99 -0
  39. package/middleware/SendJsonMiddleware.js +131 -0
  40. package/middleware/SendStringMiddleware.js +87 -0
  41. package/package.json +38 -29
  42. package/polyfill/FormData.js +164 -0
  43. package/rollup.config.js +0 -1
  44. package/scripts/check-teapot.mjs +40 -0
  45. package/scripts/test-all-sync.sh +6 -0
  46. package/scripts/test-all.sh +7 -0
  47. package/templates/starter.js +55 -0
  48. package/test/fixtures/stream.js +68 -0
  49. package/test/helpers/HttpListener/construct.js +18 -0
  50. package/test/helpers/HttpListener/customOptions.js +22 -0
  51. package/test/helpers/HttpListener/doubleCreate.js +40 -0
  52. package/test/helpers/HttpListener/events.js +77 -0
  53. package/test/helpers/HttpListener/http.js +31 -0
  54. package/test/helpers/HttpListener/http2.js +41 -0
  55. package/test/helpers/HttpListener/https.js +38 -0
  56. package/test/helpers/HttpListener/startAll.js +31 -0
  57. package/test/helpers/HttpListener/stopNotStarted.js +23 -0
  58. package/test/lib/HttpHandler/class.js +8 -0
  59. package/test/lib/HttpHandler/handleRequest.js +11 -0
  60. package/test/lib/HttpHandler/middleware.js +941 -0
  61. package/test/lib/HttpHandler/parse.js +41 -0
  62. package/test/lib/HttpRequest/class.js +8 -0
  63. package/test/lib/HttpRequest/downstream.js +171 -0
  64. package/test/lib/HttpRequest/properties.js +101 -0
  65. package/test/lib/HttpRequest/read.js +518 -0
  66. package/test/lib/HttpResponse/class.js +8 -0
  67. package/test/lib/HttpResponse/properties.js +59 -0
  68. package/test/lib/HttpResponse/send.js +275 -0
  69. package/test/lib/HttpTransaction/class.js +8 -0
  70. package/test/lib/HttpTransaction/ping.js +50 -0
  71. package/test/lib/HttpTransaction/push.js +89 -0
  72. package/test/middleware/SendJsonMiddleware.js +222 -0
  73. package/test/sanity.js +10 -0
  74. package/test/templates/error-teapot.js +47 -0
  75. package/test/templates/starter.js +93 -0
  76. package/tsconfig.json +12 -0
  77. package/types/index.js +61 -34
  78. package/types/typings.d.ts +8 -9
  79. package/utils/AsyncObject.js +6 -3
  80. package/utils/CaseInsensitiveObject.js +2 -3
  81. package/utils/function.js +1 -7
  82. package/utils/headers.js +42 -0
  83. package/utils/qualityValues.js +1 -1
  84. package/utils/stream.js +4 -20
  85. package/index.cjs +0 -3190
  86. package/test/constants.js +0 -4
  87. /package/{test → .test}/cookietester.js +0 -0
@@ -1,11 +1,15 @@
1
- import http from 'http';
2
- import https from 'https';
3
- import http2 from 'http2';
1
+ import { createServer as createHttpServer } from 'node:http';
2
+ import { createSecureServer as createHttp2Server } from 'node:http2';
3
+ import { createServer as createHttpsServer } from 'node:https';
4
+ import { Server, Socket } from 'node:net';
5
+
4
6
  import HttpHandler from '../lib/HttpHandler.js';
5
7
 
6
8
  export const SERVER_ALREADY_CREATED = 'SERVER_ALREADY_CREATED';
7
9
 
8
10
  /** @typedef {import('tls').TlsOptions} TlsOptions */
11
+ /** @typedef {import('http2').Http2Session} Http2Session */
12
+ /** @typedef {import('http2').Http2Stream} Http2Stream */
9
13
 
10
14
  /**
11
15
  * @typedef {Object} HttpListenerOptions
@@ -24,8 +28,57 @@ export const SERVER_ALREADY_CREATED = 'SERVER_ALREADY_CREATED';
24
28
  let defaultInstance;
25
29
 
26
30
  export default class HttpListener {
31
+ /** @return {HttpListener} */
32
+ static get defaultInstance() {
33
+ if (!defaultInstance) {
34
+ defaultInstance = new HttpListener();
35
+ }
36
+ return defaultInstance;
37
+ }
38
+
39
+ /** @type {(err: Error) => void} */
40
+ #httpErrorListener;
41
+
42
+ /** @type {(err: Error, socket: import('node:net').Socket) => void} */
43
+ #httpClientErrorListener;
44
+
45
+ /** @type {(req: import('node:http').IncomingMessage, res: import('node:http').ServerResponse) => void} */
46
+ #httpRequestListener;
47
+
48
+ /** @type {(err: Error) => void} */
49
+ #httpsErrorListener;
50
+
51
+ /** @type {(err: Error, socket: import('node:net').Socket) => void} */
52
+ #httpsClientErrorListener;
53
+
54
+ /** @type {(req: import('node:http').IncomingMessage, res: import('node:http').ServerResponse) => void} */
55
+ #httpsRequestListener;
56
+
57
+ /** @type {(err: Error) => void} */
58
+ #http2ErrorListener;
59
+
60
+ /** @type {(err: Error, socket: import('node:net').Socket) => void} */
61
+ #http2ClientErrorListener;
62
+
63
+ /** @type {(err: Error, session: import('node:http2').Http2Session) => void} */
64
+ #http2SessionErrorListener;
65
+
66
+ /** @type {(session: import('node:http2').Http2Session) => void} */
67
+ #http2SessionListener;
68
+
69
+ /** @type {(stream: import('node:http2').Http2Stream, headers: import('node:http2').IncomingHttpHeaders) => void} */
70
+ #http2StreamListener;
71
+
72
+ /** @type {(req: import('node:http').IncomingMessage, res: import('node:http').ServerResponse) => void} */
73
+ #http2RequestListener;
74
+
27
75
  /** @param {HttpListenerOptions} options */
28
76
  constructor(options = {}) {
77
+ this.configure(options);
78
+ }
79
+
80
+ /** @param {HttpListenerOptions} options */
81
+ configure(options = {}) {
29
82
  this.httpHandler = options.httpHandler ?? HttpHandler.defaultInstance;
30
83
  this.insecurePort = options.insecurePort ?? 8080;
31
84
  this.insecureHost = options.insecureHost;
@@ -37,21 +90,13 @@ export default class HttpListener {
37
90
  this.tlsOptions = options.tlsOptions ?? {};
38
91
  }
39
92
 
40
- /** @return {HttpListener} */
41
- static get defaultInstance() {
42
- if (!defaultInstance) {
43
- defaultInstance = new HttpListener();
44
- }
45
- return defaultInstance;
46
- }
47
-
48
93
  /**
49
- * @param {http.ServerOptions} [options]
50
- * @return {http.Server}
94
+ * @param {import('node:http').ServerOptions} [options]
95
+ * @return {import('node:http').Server}
51
96
  */
52
97
  createHttpServer(options = {}) {
53
98
  if (!this.httpServer) {
54
- this.httpServer = http.createServer(options);
99
+ this.httpServer = createHttpServer(options);
55
100
  } else if (Object.keys(options).length) {
56
101
  throw new Error(SERVER_ALREADY_CREATED);
57
102
  }
@@ -59,12 +104,12 @@ export default class HttpListener {
59
104
  }
60
105
 
61
106
  /**
62
- * @param {https.ServerOptions} [options]
63
- * @return {https.Server}
107
+ * @param {import('node:https').ServerOptions} [options]
108
+ * @return {import('node:https').Server}
64
109
  */
65
110
  createHttpsServer(options = {}) {
66
111
  if (!this.httpsServer) {
67
- this.httpsServer = https.createServer({
112
+ this.httpsServer = createHttpsServer({
68
113
  ...this.tlsOptions,
69
114
  ...options,
70
115
  });
@@ -75,12 +120,12 @@ export default class HttpListener {
75
120
  }
76
121
 
77
122
  /**
78
- * @param {http2.ServerOptions} [options]
79
- * @return {http2.Http2SecureServer}
123
+ * @param {import('node:http2').ServerOptions} [options]
124
+ * @return {import('node:http2').Http2SecureServer}
80
125
  */
81
126
  createHttp2Server(options = {}) {
82
127
  if (!this.http2Server) {
83
- this.http2Server = http2.createSecureServer({
128
+ this.http2Server = createHttp2Server({
84
129
  allowHTTP1: true,
85
130
  ...this.tlsOptions,
86
131
  ...options,
@@ -91,7 +136,7 @@ export default class HttpListener {
91
136
  return this.http2Server;
92
137
  }
93
138
 
94
- /** @return {Promise<http.Server>} */
139
+ /** @return {Promise<import('node:http').Server>} */
95
140
  startHttpServer() {
96
141
  return new Promise((resolve, reject) => {
97
142
  this.createHttpServer();
@@ -100,14 +145,44 @@ export default class HttpListener {
100
145
  host: this.insecureHost,
101
146
  }, () => {
102
147
  this.httpServer.removeListener('error', reject);
103
- this.httpServer.addListener('request', this.httpHandler.handleHttp1Request);
148
+
149
+ this.httpServer.keepAliveTimeout = 5000;
150
+ this.httpServer.requestTimeout = 300_000;
151
+ this.httpServer.setTimeout(120_000, (socket) => {
152
+ if (!socket) {
153
+ console.warn('HTTP socket (unknown) timed out.');
154
+ return;
155
+ }
156
+ // const identity = `${socket.remoteFamily}:${socket.remoteAddress}:${socket.remotePort}`;
157
+ // console.warn(`HTTP socket ${identity} timed out.`);
158
+ socket.destroy(new Error('SOCKET_TIMEOUT'));
159
+ });
160
+
161
+ // Save listeners as private members for later removal
162
+ this.httpServer.addListener('error', this.#httpErrorListener = (error) => {
163
+ // console.error('HTTP server error', err);
164
+ });
165
+ this.httpServer.addListener('clientError', this.#httpClientErrorListener = (error, socket) => {
166
+ if (error?.code === 'ECONNRESET') {
167
+ // console.warn('HTTP client connection reset.');
168
+ return;
169
+ }
170
+ // console.error('HTTP client error', err);
171
+ if (socket.destroyed || socket.writableEnded) return;
172
+ socket.end('HTTP/1.1 400 Bad Request\r\n\r\n');
173
+ });
174
+ this.httpServer.addListener('request', this.#httpRequestListener = (request, res) => {
175
+ this.httpHandler.handleHttp1Request(request, res).catch((error) => {
176
+ // console.error('HTTP1 handler failed', err);
177
+ });
178
+ });
104
179
  resolve(this.httpServer);
105
180
  });
106
181
  this.httpServer.addListener('error', reject);
107
182
  });
108
183
  }
109
184
 
110
- /** @return {Promise<https.Server>} */
185
+ /** @return {Promise<import('node:https').Server>} */
111
186
  startHttpsServer() {
112
187
  return new Promise((resolve, reject) => {
113
188
  this.createHttpsServer();
@@ -116,14 +191,44 @@ export default class HttpListener {
116
191
  host: this.secureHost,
117
192
  }, () => {
118
193
  this.httpsServer.removeListener('error', reject);
119
- this.httpsServer.addListener('request', this.httpHandler.handleHttp1Request);
194
+
195
+ this.httpsServer.keepAliveTimeout = 5000;
196
+ this.httpsServer.requestTimeout = 300_000;
197
+ this.httpsServer.setTimeout(120_000, (socket) => {
198
+ if (!socket) {
199
+ // console.warn('HTTPS socket (unknown) timed out.');
200
+ return;
201
+ }
202
+ // const identity = `${socket.remoteFamily}:${socket.remoteAddress}:${socket.remotePort}`;
203
+ // console.error(`HTTPS socket ${identity} timed out.`);
204
+ socket.destroy(new Error('SOCKET_TIMEOUT'));
205
+ });
206
+
207
+ this.httpsServer.addListener('error', this.#httpsErrorListener = (error) => {
208
+ // console.error('HTTPS server error', err);
209
+ });
210
+ this.httpsServer.addListener('clientError', this.#httpsClientErrorListener = (error, socket) => {
211
+ if (error?.code === 'ECONNRESET') {
212
+ console.warn('HTTPS client connection reset.');
213
+ return;
214
+ }
215
+ // console.error('HTTPS client error', err);
216
+ if (socket.destroyed) return;
217
+ socket.end('HTTP/1.1 400 Bad Request\r\n\r\n');
218
+ });
219
+
220
+ this.httpsServer.addListener('request', this.#httpsRequestListener = (request, res) => {
221
+ this.httpHandler.handleHttp1Request(request, res).catch((error) => {
222
+ // console.error('HTTPS handler failed', err);
223
+ });
224
+ });
120
225
  resolve(this.httpsServer);
121
226
  });
122
227
  this.httpsServer.addListener('error', reject);
123
228
  });
124
229
  }
125
230
 
126
- /** @return {Promise<http2.Http2SecureServer>} */
231
+ /** @return {Promise<import('node:http2').Http2SecureServer>} */
127
232
  startHttp2Server() {
128
233
  return new Promise((resolve, reject) => {
129
234
  this.createHttp2Server();
@@ -132,12 +237,209 @@ export default class HttpListener {
132
237
  host: this.secureHost,
133
238
  }, () => {
134
239
  this.http2Server.removeListener('error', reject);
135
- this.http2Server.addListener('stream', this.httpHandler.handleHttp2Stream);
136
- this.http2Server.addListener('request', (req, res) => {
137
- if (req.httpVersionMajor >= 2) return;
240
+
241
+ /** @type {Set<WeakRef<Http2Session>>} */
242
+ const sessions = new Set();
243
+ /** @type {WeakMap<Http2Session, {timestamp:number, identity:string}>} */
244
+ const sessionMetadata = new WeakMap();
245
+ /** @type {Set<WeakRef<Http2Stream>>} */
246
+ const streams = new Set();
247
+ /** @type {WeakMap<Http2Stream, {timestamp:number, identity:string, path:string}>} */
248
+ const streamMetadata = new WeakMap();
249
+ /** @return {void} */
250
+ function logUsage() {
251
+ if (global.gc) {
252
+ console.debug('Perfoming garbage collection.');
253
+ global.gc();
254
+ }
255
+ for (const reference of sessions) {
256
+ const session = reference.deref();
257
+ if (!session) {
258
+ sessions.delete(reference);
259
+ continue;
260
+ }
261
+ const metadata = sessionMetadata.get(session);
262
+ if (session.destroyed) {
263
+ console.warn('SESSION destroyed from', metadata.identity, 'since', metadata.timestamp);
264
+ } else {
265
+ console.debug('SESSION alive from', metadata.identity, 'since', metadata.timestamp);
266
+ }
267
+ }
268
+ for (const reference of streams) {
269
+ const stream = reference.deref();
270
+ if (!stream) {
271
+ streams.delete(reference);
272
+ continue;
273
+ }
274
+ const metadata = streamMetadata.get(stream);
275
+ if (stream.destroyed) {
276
+ console.warn('STREAM destroyed from', metadata.identity, 'since', metadata.timestamp, 'for', metadata.path);
277
+ } else {
278
+ console.debug('STREAM alive from', metadata.identity, 'since', metadata.timestamp, 'for', metadata.path);
279
+ }
280
+ }
281
+ if (sessions.size) {
282
+ console.debug('Active sessions:', sessions.size);
283
+ }
284
+ if (streams.size) {
285
+ console.debug('Active streams:', streams.size);
286
+ }
287
+ if ('process' in globalThis) {
288
+ if ('getActiveResourcesInfo' in globalThis.process) {
289
+ console.dir('Active Resources', globalThis.process.getActiveResourcesInfo());
290
+ }
291
+ if ('_getActiveRequests' in globalThis.process) {
292
+ console.dir('Active Requests', globalThis.process._getActiveRequests());
293
+ }
294
+ if ('_getActiveHandles' in globalThis.process) {
295
+ const handles = globalThis.process._getActiveHandles();
296
+ for (const handle of handles) {
297
+ if (handle instanceof Socket) {
298
+ console.debug('Active Handle (Socket)', `${handle.localAddress}:${handle.localPort} <=> ${handle.remoteAddress}:${handle.remotePort}`);
299
+ } else if (handle instanceof Server) {
300
+ console.debug('Active Handle (Server)', `${handle.address()?.port}`);
301
+ } else {
302
+ console.debug('Active Handle (unknown)', handle);
303
+ }
304
+ }
305
+ }
306
+ }
307
+ }
308
+ // setInterval(logUsage, 300_000);
309
+ // logUsage();
310
+
311
+ this.http2Server.setTimeout(120_000, (socket) => {
312
+ if (!socket) {
313
+ console.warn('HTTP2 socket (unknown) timed out.');
314
+ return;
315
+ }
316
+ // const identity = `${socket.remoteFamily}:${socket.remoteAddress}:${socket.remotePort}`;
317
+ // console.warn(`HTTP2 socket ${identity} timed out.`);
318
+ socket.destroy(new Error('SOCKET_TIMEOUT'));
319
+ });
320
+
321
+ // Error Handlers
322
+ this.http2Server.addListener('error', this.#http2ErrorListener = (error) => {
323
+ console.error('HTTP/2 server error', error);
324
+ });
325
+ this.http2Server.addListener('clientError', this.#http2ClientErrorListener = (error, socket) => {
326
+ // No clear way to signal back to client why a socket has failed because
327
+ // it's unsure if it's HTTP1 or HTTP2. Just destroy and log server-side.
328
+ if (error?.code === 'ECONNRESET') {
329
+ // console.warn('HTTP/2 client connection reset.');
330
+ } else {
331
+ console.error('HTTP/2 client error', error);
332
+ }
333
+ if (!socket.destroyed) socket.destroy(error);
334
+ });
335
+ this.http2Server.addListener('sessionError', this.#http2SessionErrorListener = (error, session) => {
336
+ if (error?.code === 'ECONNRESET') {
337
+ // console.warn('HTTP/2 client connection reset.');
338
+ } else if (error?.message === 'SOCKET_TIMEOUT') {
339
+ // Server generated error
340
+ } else {
341
+ console.error('HTTP/2 sessionError error', error);
342
+ }
343
+ if (!session.destroyed) session.destroy(error);
344
+ });
345
+
346
+ this.http2Server.addListener('session', this.#http2SessionListener = (session) => {
347
+ sessions.add(new WeakRef(session));
348
+ const identity = `${session.socket.remoteFamily}:${session.socket.remoteAddress}:${session.socket.remotePort}`;
349
+ sessionMetadata.set(session, {
350
+ timestamp: performance.now(),
351
+ identity,
352
+ });
353
+ session.setTimeout(60_000, () => {
354
+ // console.warn(`HTTP/2 session ${identity} timed out.`);
355
+ session.destroy(new Error('SESSION_TIMEOUT'));
356
+ });
357
+ const pingInterval = setInterval(() => {
358
+ if (session.destroyed) {
359
+ clearInterval(pingInterval);
360
+ } else {
361
+ session.ping((error) => {
362
+ if (!error) return;
363
+ if (session.destroyed) return;
364
+ if (error.code === 'ERR_HTTP2_PING_CANCEL') return;
365
+ console.error(`Ping to ${identity} failed.`, error);
366
+ });
367
+ }
368
+ }, 15_000);
369
+ session.addListener('close', () => {
370
+ clearInterval(pingInterval);
371
+ });
372
+ });
373
+
374
+ // Logic handlers
375
+ this.http2Server.addListener('stream', this.#http2StreamListener = (stream, headers) => {
376
+ streams.add(new WeakRef(stream));
377
+ streamMetadata.set(stream, {
378
+ timestamp: performance.now(),
379
+ identity: `${stream.session?.socket.remoteFamily}:${stream.session?.socket.remoteAddress}:${stream.session?.socket.remotePort}`,
380
+ path: headers[':path'],
381
+ });
382
+
383
+ stream.setTimeout(300_000, () => {
384
+ stream.destroy(new Error('SOCKET_TIMEOUT'));
385
+ });
386
+ stream.addListener('error', (error) => {
387
+ if (error?.code === 'ECONNRESET') {
388
+ // console.warn('HTTP/2 stream connection reset.', headers[':path']);
389
+ } else {
390
+ console.error('HTTP/2 stream error', headers, error);
391
+ }
392
+ });
393
+ this.httpHandler.handleHttp2Stream(stream, headers).catch((error) => {
394
+ console.error('HTTP2 handler failed.', error);
395
+ });
396
+ });
397
+ this.http2Server.addListener('request', this.#http2RequestListener = (request, res) => {
398
+ if (request.httpVersionMajor >= 2) return;
138
399
  // @ts-ignore Ignore typings
139
- this.httpHandler.handleHttp1Request(req, res);
400
+ request.setTimeout(300_000, (socket) => {
401
+ if (!socket) {
402
+ // console.warn('HTTP1 in HTTP2 request (unknown) timed out.');
403
+ return;
404
+ }
405
+ // const identity = `${socket.remoteFamily}:${socket.remoteAddress}:${socket.remotePort}`;
406
+ // console.warn(`HTTP1 in HTTP2 request ${identity} timed out.`);
407
+ socket.destroy(new Error('SOCKET_TIMEOUT'));
408
+ });
409
+ res.setTimeout(300_000, (socket) => {
410
+ if (!socket) {
411
+ // console.warn('HTTP1 in HTTP2 response (unknown) timed out.');
412
+ return;
413
+ }
414
+ const identity = `${socket.remoteFamily}:${socket.remoteAddress}:${socket.remotePort}`;
415
+ // console.warn(`HTTP1 in HTTP2 response ${identity} timed out.`);
416
+ socket.destroy(new Error('SOCKET_TIMEOUT'));
417
+ });
418
+ request.addListener('error', (error) => {
419
+ if (error?.code === 'ECONNRESET') {
420
+ // console.warn('Request stream connection reset.', req.url);
421
+ } else {
422
+ console.error('Request stream error.', request.url, request.headers, error);
423
+ }
424
+ if (!request.destroyed) {
425
+ request.destroy(error);
426
+ }
427
+ });
428
+ res.addListener('error', (error) => {
429
+ if (error?.code === 'ECONNRESET') {
430
+ // console.warn('Response stream connection reset.', req.url);
431
+ } else {
432
+ console.error('Response stream error', request.url, request.headers, error);
433
+ }
434
+ if (!res.destroyed) {
435
+ res.destroy(error);
436
+ }
437
+ });
438
+ this.httpHandler.handleHttp1Request(request, res).catch((error) => {
439
+ console.error('HTTP1 in HTTP2 handler failed.', error);
440
+ });
140
441
  });
442
+
141
443
  resolve(this.http2Server);
142
444
  });
143
445
  this.http2Server.addListener('error', reject);
@@ -151,11 +453,20 @@ export default class HttpListener {
151
453
  resolve();
152
454
  return;
153
455
  }
154
- this.httpServer.close((err) => {
155
- if (err) {
156
- reject(err);
456
+ this.httpServer.close((error) => {
457
+ if (error) {
458
+ reject(error);
157
459
  return;
158
460
  }
461
+ if (this.#httpRequestListener) {
462
+ this.httpServer.removeListener('request', this.#httpRequestListener);
463
+ }
464
+ if (this.#httpErrorListener) {
465
+ this.httpServer.removeListener('error', this.#httpErrorListener);
466
+ }
467
+ if (this.#httpClientErrorListener) {
468
+ this.httpServer.removeListener('clientError', this.#httpClientErrorListener);
469
+ }
159
470
  resolve();
160
471
  });
161
472
  });
@@ -168,11 +479,20 @@ export default class HttpListener {
168
479
  resolve();
169
480
  return;
170
481
  }
171
- this.httpsServer.close((err) => {
172
- if (err) {
173
- reject(err);
482
+ this.httpsServer.close((error) => {
483
+ if (error) {
484
+ reject(error);
174
485
  return;
175
486
  }
487
+ if (this.#httpsRequestListener) {
488
+ this.httpsServer.removeListener('request', this.#httpsRequestListener);
489
+ }
490
+ if (this.#httpsErrorListener) {
491
+ this.httpsServer.removeListener('error', this.#httpsErrorListener);
492
+ }
493
+ if (this.#httpsClientErrorListener) {
494
+ this.httpsServer.removeListener('clientError', this.#httpsClientErrorListener);
495
+ }
176
496
  resolve();
177
497
  });
178
498
  });
@@ -185,18 +505,43 @@ export default class HttpListener {
185
505
  resolve();
186
506
  return;
187
507
  }
188
- this.http2Server.close((err) => {
189
- if (err) {
190
- reject(err);
508
+ this.http2Server.close((error) => {
509
+ if (error) {
510
+ reject(error);
191
511
  return;
192
512
  }
513
+
514
+ if (this.#http2ErrorListener) {
515
+ this.http2Server.removeListener('error', this.#http2ErrorListener);
516
+ }
517
+ if (this.#http2ClientErrorListener) {
518
+ this.http2Server.removeListener('clientError', this.#http2ClientErrorListener);
519
+ }
520
+ if (this.#http2SessionErrorListener) {
521
+ this.http2Server.removeListener('sessionError', this.#http2SessionErrorListener);
522
+ }
523
+ if (this.#http2SessionListener) {
524
+ this.http2Server.removeListener('session', this.#http2SessionListener);
525
+ }
526
+ if (this.#http2StreamListener) {
527
+ this.http2Server.removeListener('stream', this.#http2StreamListener);
528
+ }
529
+ if (this.#http2RequestListener) {
530
+ this.http2Server.removeListener('request', this.#http2RequestListener);
531
+ }
532
+
193
533
  resolve();
194
534
  });
195
535
  });
196
536
  }
197
537
 
198
538
  /**
199
- * @return {Promise<[http.Server, https.Server, http2.Http2SecureServer]>}
539
+ * @return {Promise<[
540
+ * import('node:http').Server,
541
+ * import('node:https').Server,
542
+ * import('node:http2').Http2SecureServer
543
+ * ]>
544
+ * }
200
545
  */
201
546
  startAll() {
202
547
  return Promise.all([
@@ -209,11 +554,11 @@ export default class HttpListener {
209
554
  /**
210
555
  * @return {Promise<void>}
211
556
  */
212
- stopAll() {
213
- return Promise.all([
557
+ async stopAll() {
558
+ await Promise.all([
214
559
  this.useHttp ? this.stopHttpServer() : Promise.resolve(null),
215
560
  this.useHttps ? this.stopHttpsServer() : Promise.resolve(null),
216
561
  this.useHttp2 ? this.stopHttp2Server() : Promise.resolve(null),
217
- ]).then(() => null);
562
+ ]);
218
563
  }
219
564
  }