webhoster 0.3.2 → 0.3.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.github/workflows/publish.yml +29 -0
- package/helpers/HttpListener.js +20 -94
- package/helpers/RequestReader.js +0 -11
- package/lib/HttpHandler.js +13 -3
- package/lib/HttpRequest.js +5 -2
- package/lib/HttpResponse.js +9 -3
- package/middleware/ContentDecoderMiddleware.js +11 -7
- package/middleware/ContentEncoderMiddleware.js +4 -3
- package/package.json +14 -3
- package/tsconfig.json +19 -1
- package/types/index.js +1 -1
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
name: Publish Package
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
tags:
|
|
6
|
+
- 'v*'
|
|
7
|
+
|
|
8
|
+
permissions:
|
|
9
|
+
id-token: write # Required for OIDC
|
|
10
|
+
contents: read
|
|
11
|
+
|
|
12
|
+
jobs:
|
|
13
|
+
publish:
|
|
14
|
+
runs-on: ubuntu-latest
|
|
15
|
+
steps:
|
|
16
|
+
- uses: actions/checkout@v4
|
|
17
|
+
|
|
18
|
+
- uses: actions/setup-node@v4
|
|
19
|
+
with:
|
|
20
|
+
node-version: '20'
|
|
21
|
+
registry-url: 'https://registry.npmjs.org'
|
|
22
|
+
|
|
23
|
+
# Ensure npm 11.5.1 or later is installed
|
|
24
|
+
- name: Update npm
|
|
25
|
+
run: npm install -g npm@latest
|
|
26
|
+
- run: npm ci
|
|
27
|
+
- run: npm run build --if-present
|
|
28
|
+
- run: npm test
|
|
29
|
+
- run: npm publish
|
package/helpers/HttpListener.js
CHANGED
|
@@ -148,6 +148,7 @@ export default class HttpListener {
|
|
|
148
148
|
|
|
149
149
|
this.httpServer.keepAliveTimeout = 5000;
|
|
150
150
|
this.httpServer.requestTimeout = 300_000;
|
|
151
|
+
// @ts-expect-error Missing types
|
|
151
152
|
this.httpServer.setTimeout(120_000, (socket) => {
|
|
152
153
|
if (!socket) {
|
|
153
154
|
console.warn('HTTP socket (unknown) timed out.');
|
|
@@ -163,6 +164,7 @@ export default class HttpListener {
|
|
|
163
164
|
// console.error('HTTP server error', err);
|
|
164
165
|
});
|
|
165
166
|
this.httpServer.addListener('clientError', this.#httpClientErrorListener = (error, socket) => {
|
|
167
|
+
// @ts-expect-error Missing types
|
|
166
168
|
if (error?.code === 'ECONNRESET') {
|
|
167
169
|
// console.warn('HTTP client connection reset.');
|
|
168
170
|
return;
|
|
@@ -194,6 +196,7 @@ export default class HttpListener {
|
|
|
194
196
|
|
|
195
197
|
this.httpsServer.keepAliveTimeout = 5000;
|
|
196
198
|
this.httpsServer.requestTimeout = 300_000;
|
|
199
|
+
// @ts-expect-error Missing types
|
|
197
200
|
this.httpsServer.setTimeout(120_000, (socket) => {
|
|
198
201
|
if (!socket) {
|
|
199
202
|
// console.warn('HTTPS socket (unknown) timed out.');
|
|
@@ -208,6 +211,7 @@ export default class HttpListener {
|
|
|
208
211
|
// console.error('HTTPS server error', err);
|
|
209
212
|
});
|
|
210
213
|
this.httpsServer.addListener('clientError', this.#httpsClientErrorListener = (error, socket) => {
|
|
214
|
+
// @ts-expect-error Missing types
|
|
211
215
|
if (error?.code === 'ECONNRESET') {
|
|
212
216
|
console.warn('HTTPS client connection reset.');
|
|
213
217
|
return;
|
|
@@ -237,77 +241,7 @@ export default class HttpListener {
|
|
|
237
241
|
host: this.secureHost,
|
|
238
242
|
}, () => {
|
|
239
243
|
this.http2Server.removeListener('error', reject);
|
|
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
|
-
|
|
244
|
+
// @ts-expect-error Missing types
|
|
311
245
|
this.http2Server.setTimeout(120_000, (socket) => {
|
|
312
246
|
if (!socket) {
|
|
313
247
|
console.warn('HTTP2 socket (unknown) timed out.');
|
|
@@ -325,6 +259,7 @@ export default class HttpListener {
|
|
|
325
259
|
this.http2Server.addListener('clientError', this.#http2ClientErrorListener = (error, socket) => {
|
|
326
260
|
// No clear way to signal back to client why a socket has failed because
|
|
327
261
|
// it's unsure if it's HTTP1 or HTTP2. Just destroy and log server-side.
|
|
262
|
+
// @ts-expect-error Missing types
|
|
328
263
|
if (error?.code === 'ECONNRESET') {
|
|
329
264
|
// console.warn('HTTP/2 client connection reset.');
|
|
330
265
|
} else {
|
|
@@ -333,6 +268,7 @@ export default class HttpListener {
|
|
|
333
268
|
if (!socket.destroyed) socket.destroy(error);
|
|
334
269
|
});
|
|
335
270
|
this.http2Server.addListener('sessionError', this.#http2SessionErrorListener = (error, session) => {
|
|
271
|
+
// @ts-expect-error Missing types
|
|
336
272
|
if (error?.code === 'ECONNRESET') {
|
|
337
273
|
// console.warn('HTTP/2 client connection reset.');
|
|
338
274
|
} else if (error?.message === 'SOCKET_TIMEOUT') {
|
|
@@ -344,12 +280,7 @@ export default class HttpListener {
|
|
|
344
280
|
});
|
|
345
281
|
|
|
346
282
|
this.http2Server.addListener('session', this.#http2SessionListener = (session) => {
|
|
347
|
-
sessions.add(new WeakRef(session));
|
|
348
283
|
const identity = `${session.socket.remoteFamily}:${session.socket.remoteAddress}:${session.socket.remotePort}`;
|
|
349
|
-
sessionMetadata.set(session, {
|
|
350
|
-
timestamp: performance.now(),
|
|
351
|
-
identity,
|
|
352
|
-
});
|
|
353
284
|
session.setTimeout(60_000, () => {
|
|
354
285
|
// console.warn(`HTTP/2 session ${identity} timed out.`);
|
|
355
286
|
session.destroy(new Error('SESSION_TIMEOUT'));
|
|
@@ -361,6 +292,7 @@ export default class HttpListener {
|
|
|
361
292
|
session.ping((error) => {
|
|
362
293
|
if (!error) return;
|
|
363
294
|
if (session.destroyed) return;
|
|
295
|
+
// @ts-expect-error Missing types
|
|
364
296
|
if (error.code === 'ERR_HTTP2_PING_CANCEL') return;
|
|
365
297
|
console.error(`Ping to ${identity} failed.`, error);
|
|
366
298
|
});
|
|
@@ -373,49 +305,42 @@ export default class HttpListener {
|
|
|
373
305
|
|
|
374
306
|
// Logic handlers
|
|
375
307
|
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
308
|
stream.setTimeout(300_000, () => {
|
|
384
309
|
stream.destroy(new Error('SOCKET_TIMEOUT'));
|
|
385
310
|
});
|
|
386
311
|
stream.addListener('error', (error) => {
|
|
312
|
+
// @ts-expect-error Missing types
|
|
387
313
|
if (error?.code === 'ECONNRESET') {
|
|
388
314
|
// console.warn('HTTP/2 stream connection reset.', headers[':path']);
|
|
389
315
|
} else {
|
|
390
316
|
console.error('HTTP/2 stream error', headers, error);
|
|
391
317
|
}
|
|
392
318
|
});
|
|
319
|
+
// @ts-expect-error Missing types
|
|
393
320
|
this.httpHandler.handleHttp2Stream(stream, headers).catch((error) => {
|
|
394
321
|
console.error('HTTP2 handler failed.', error);
|
|
395
322
|
});
|
|
396
323
|
});
|
|
397
|
-
this.http2Server.addListener('request', this.#http2RequestListener = (request,
|
|
324
|
+
this.http2Server.addListener('request', this.#http2RequestListener = (request, response) => {
|
|
398
325
|
if (request.httpVersionMajor >= 2) return;
|
|
399
|
-
// @ts-
|
|
326
|
+
// @ts-expect-error Missing types
|
|
400
327
|
request.setTimeout(300_000, (socket) => {
|
|
401
328
|
if (!socket) {
|
|
402
329
|
// console.warn('HTTP1 in HTTP2 request (unknown) timed out.');
|
|
403
330
|
return;
|
|
404
331
|
}
|
|
405
|
-
// const identity = `${socket.remoteFamily}:${socket.remoteAddress}:${socket.remotePort}`;
|
|
406
|
-
// console.warn(`HTTP1 in HTTP2 request ${identity} timed out.`);
|
|
407
332
|
socket.destroy(new Error('SOCKET_TIMEOUT'));
|
|
408
333
|
});
|
|
409
|
-
|
|
334
|
+
// @ts-expect-error Missing types
|
|
335
|
+
response.setTimeout(300_000, (socket) => {
|
|
410
336
|
if (!socket) {
|
|
411
337
|
// console.warn('HTTP1 in HTTP2 response (unknown) timed out.');
|
|
412
338
|
return;
|
|
413
339
|
}
|
|
414
|
-
const identity = `${socket.remoteFamily}:${socket.remoteAddress}:${socket.remotePort}`;
|
|
415
|
-
// console.warn(`HTTP1 in HTTP2 response ${identity} timed out.`);
|
|
416
340
|
socket.destroy(new Error('SOCKET_TIMEOUT'));
|
|
417
341
|
});
|
|
418
342
|
request.addListener('error', (error) => {
|
|
343
|
+
// @ts-expect-error Missing types
|
|
419
344
|
if (error?.code === 'ECONNRESET') {
|
|
420
345
|
// console.warn('Request stream connection reset.', req.url);
|
|
421
346
|
} else {
|
|
@@ -425,17 +350,18 @@ export default class HttpListener {
|
|
|
425
350
|
request.destroy(error);
|
|
426
351
|
}
|
|
427
352
|
});
|
|
428
|
-
|
|
353
|
+
response.addListener('error', (error) => {
|
|
354
|
+
// @ts-expect-error Missing types
|
|
429
355
|
if (error?.code === 'ECONNRESET') {
|
|
430
356
|
// console.warn('Response stream connection reset.', req.url);
|
|
431
357
|
} else {
|
|
432
358
|
console.error('Response stream error', request.url, request.headers, error);
|
|
433
359
|
}
|
|
434
|
-
if (!
|
|
435
|
-
|
|
360
|
+
if (!response.destroyed) {
|
|
361
|
+
response.destroy(error);
|
|
436
362
|
}
|
|
437
363
|
});
|
|
438
|
-
this.httpHandler.handleHttp1Request(request,
|
|
364
|
+
this.httpHandler.handleHttp1Request(request, response).catch((error) => {
|
|
439
365
|
console.error('HTTP1 in HTTP2 handler failed.', error);
|
|
440
366
|
});
|
|
441
367
|
});
|
package/helpers/RequestReader.js
CHANGED
|
@@ -2,7 +2,6 @@
|
|
|
2
2
|
|
|
3
3
|
import { TextDecoder } from 'node:util';
|
|
4
4
|
import AsyncObject from '../utils/AsyncObject.js';
|
|
5
|
-
import { noop } from '../utils/function.js';
|
|
6
5
|
import RequestHeaders from './RequestHeaders.js';
|
|
7
6
|
|
|
8
7
|
/**
|
|
@@ -11,7 +10,6 @@ import RequestHeaders from './RequestHeaders.js';
|
|
|
11
10
|
*/
|
|
12
11
|
|
|
13
12
|
const BUFFER_SIZE = 4096;
|
|
14
|
-
const STREAM_WAIT_MS = 0;
|
|
15
13
|
|
|
16
14
|
/** @type {WeakMap<HttpRequest, RequestReader>} */
|
|
17
15
|
const cache = new WeakMap();
|
|
@@ -45,8 +43,6 @@ export default class RequestReader {
|
|
|
45
43
|
const hp = new RequestHeaders(this.request);
|
|
46
44
|
let data = Buffer.allocUnsafe(Math.min(BUFFER_SIZE, hp.contentLength || BUFFER_SIZE));
|
|
47
45
|
let bytesWritten = 0;
|
|
48
|
-
/** @type {NodeJS.Timeout} */
|
|
49
|
-
let sendPingTimeout = null;
|
|
50
46
|
this.request.stream.on('readable', () => {
|
|
51
47
|
let chunk;
|
|
52
48
|
// eslint-disable-next-line no-cond-assign
|
|
@@ -70,15 +66,8 @@ export default class RequestReader {
|
|
|
70
66
|
}
|
|
71
67
|
bytesWritten += buffer.copy(data, bytesWritten);
|
|
72
68
|
}
|
|
73
|
-
clearTimeout(sendPingTimeout);
|
|
74
|
-
if (this.request.canPing) {
|
|
75
|
-
sendPingTimeout = setTimeout(() => {
|
|
76
|
-
this.request.ping().catch(noop);
|
|
77
|
-
}, STREAM_WAIT_MS);
|
|
78
|
-
}
|
|
79
69
|
});
|
|
80
70
|
this.request.stream.on('end', () => {
|
|
81
|
-
clearTimeout(sendPingTimeout);
|
|
82
71
|
if (data.length > bytesWritten) {
|
|
83
72
|
// Must partition to clear unsafe allocation
|
|
84
73
|
data = data.subarray(0, bytesWritten);
|
package/lib/HttpHandler.js
CHANGED
|
@@ -145,6 +145,7 @@ export default class HttpHandler {
|
|
|
145
145
|
|
|
146
146
|
// Check if error handler
|
|
147
147
|
const isErrorHandler = (typeof middleware === 'object'
|
|
148
|
+
&& 'onError' in middleware
|
|
148
149
|
&& typeof middleware.onError === 'function');
|
|
149
150
|
|
|
150
151
|
let value = middleware;
|
|
@@ -186,6 +187,7 @@ export default class HttpHandler {
|
|
|
186
187
|
}
|
|
187
188
|
|
|
188
189
|
// Sync operation returned Promise
|
|
190
|
+
// @ts-expect-error TS improperly thinks result can be null
|
|
189
191
|
if (typeof result === 'object' && typeof result.then === 'function') {
|
|
190
192
|
if (isErrorHandler) transaction.error = null;
|
|
191
193
|
result = await result;
|
|
@@ -216,6 +218,7 @@ export default class HttpHandler {
|
|
|
216
218
|
const { treeIndex } = transaction.state;
|
|
217
219
|
treeIndex.push(-1);
|
|
218
220
|
const { length } = value;
|
|
221
|
+
// eslint-disable-next-line no-plusplus
|
|
219
222
|
for (let index = 0; index < length; index++) {
|
|
220
223
|
const innerMiddleware = value[index];
|
|
221
224
|
treeIndex[treeIndex.length - 1] += 1;
|
|
@@ -333,6 +336,7 @@ export default class HttpHandler {
|
|
|
333
336
|
|
|
334
337
|
const request = new HttpRequest({
|
|
335
338
|
headers,
|
|
339
|
+
// @ts-expect-error JSDoc syntax limitation
|
|
336
340
|
method,
|
|
337
341
|
stream: incomingMessage,
|
|
338
342
|
|
|
@@ -428,7 +432,7 @@ export default class HttpHandler {
|
|
|
428
432
|
}
|
|
429
433
|
}
|
|
430
434
|
|
|
431
|
-
// eslint-disable-next-line @typescript-eslint/no-this-alias
|
|
435
|
+
// eslint-disable-next-line @typescript-eslint/no-this-alias, unicorn/no-this-assignment
|
|
432
436
|
const context = this;
|
|
433
437
|
|
|
434
438
|
/** @type {Promise<any>[]} */
|
|
@@ -436,6 +440,7 @@ export default class HttpHandler {
|
|
|
436
440
|
|
|
437
441
|
const request = new HttpRequest({
|
|
438
442
|
headers,
|
|
443
|
+
// @ts-expect-error JSDoc syntax limitation
|
|
439
444
|
method,
|
|
440
445
|
stream,
|
|
441
446
|
|
|
@@ -492,7 +497,7 @@ export default class HttpHandler {
|
|
|
492
497
|
'cache-control',
|
|
493
498
|
]) {
|
|
494
499
|
if (passedHeader in headers) {
|
|
495
|
-
// @ts-
|
|
500
|
+
// @ts-expect-error Coerce
|
|
496
501
|
newHeaders[passedHeader] = headers[passedHeader];
|
|
497
502
|
}
|
|
498
503
|
}
|
|
@@ -500,10 +505,15 @@ export default class HttpHandler {
|
|
|
500
505
|
// Build promise function
|
|
501
506
|
const promiseFunction = async () => {
|
|
502
507
|
try {
|
|
508
|
+
/** @type {import('node:http2').ServerHttp2Stream} */
|
|
503
509
|
const pushStream = await new Promise((resolve, reject) => {
|
|
504
|
-
stream.pushStream(
|
|
510
|
+
stream.pushStream(
|
|
511
|
+
newHeaders,
|
|
512
|
+
((error, newStream) => (error ? reject(error) : resolve(newStream))),
|
|
513
|
+
);
|
|
505
514
|
});
|
|
506
515
|
pushStream.addListener('error', (error) => {
|
|
516
|
+
// @ts-expect-error Missing types
|
|
507
517
|
if (error?.code === 'ECONNRESET') {
|
|
508
518
|
console.warn('HTTP/2 stream connection reset.', headers[':path']);
|
|
509
519
|
} else {
|
package/lib/HttpRequest.js
CHANGED
|
@@ -140,7 +140,9 @@ export default class HttpRequest {
|
|
|
140
140
|
if (this.#readable === undefined) {
|
|
141
141
|
if (this.method === 'GET' || this.method === 'HEAD') {
|
|
142
142
|
this.#readable = null;
|
|
143
|
+
// @ts-expect-error Missing types
|
|
143
144
|
} else if (Readable.toWeb) {
|
|
145
|
+
// @ts-expect-error Missing types
|
|
144
146
|
this.#readable = Readable.toWeb(this.stream);
|
|
145
147
|
this.#bodyUsed = true;
|
|
146
148
|
} else {
|
|
@@ -226,7 +228,7 @@ export default class HttpRequest {
|
|
|
226
228
|
}
|
|
227
229
|
|
|
228
230
|
/**
|
|
229
|
-
* @return {Promise<
|
|
231
|
+
* @return {Promise<ArrayBufferLike>}
|
|
230
232
|
*/
|
|
231
233
|
async arrayBuffer() {
|
|
232
234
|
try {
|
|
@@ -264,7 +266,7 @@ export default class HttpRequest {
|
|
|
264
266
|
try {
|
|
265
267
|
const module = await import('node:buffer');
|
|
266
268
|
if ('Blob' in module === false) throw new Error('NOT_SUPPORTED');
|
|
267
|
-
BlobClass = module.Blob;
|
|
269
|
+
BlobClass = /** @type {typeof Blob} */ (module.Blob);
|
|
268
270
|
} catch {
|
|
269
271
|
BlobClass = null;
|
|
270
272
|
}
|
|
@@ -389,6 +391,7 @@ export default class HttpRequest {
|
|
|
389
391
|
}
|
|
390
392
|
}
|
|
391
393
|
|
|
394
|
+
// eslint-disable-next-line class-methods-use-this
|
|
392
395
|
async formData() {
|
|
393
396
|
throw new Error('UNSUPPORTED_MEDIA_TYPE');
|
|
394
397
|
}
|
package/lib/HttpResponse.js
CHANGED
|
@@ -124,6 +124,7 @@ export default class HttpResponse {
|
|
|
124
124
|
async sendRaw(body) {
|
|
125
125
|
this.body = body;
|
|
126
126
|
await new Promise((resolve, reject) => {
|
|
127
|
+
// @ts-expect-error Error is any
|
|
127
128
|
this.stream.end(body, (error) => (error ? reject(error) : resolve()));
|
|
128
129
|
});
|
|
129
130
|
return 0;
|
|
@@ -135,7 +136,7 @@ export default class HttpResponse {
|
|
|
135
136
|
}
|
|
136
137
|
|
|
137
138
|
/**
|
|
138
|
-
* @param {Transform} stream
|
|
139
|
+
* @param {import('stream').Transform} stream
|
|
139
140
|
* @return {this}
|
|
140
141
|
*/
|
|
141
142
|
addUpstream(stream) {
|
|
@@ -162,6 +163,7 @@ export default class HttpResponse {
|
|
|
162
163
|
// Called directly by user and needs finalizer calls
|
|
163
164
|
|
|
164
165
|
this.isStreaming = true;
|
|
166
|
+
// eslint-disable-next-line no-plusplus
|
|
165
167
|
for (let index = 0; index < this.finalizers.length; index++) {
|
|
166
168
|
const process = this.finalizers[index];
|
|
167
169
|
const result = process(this);
|
|
@@ -183,11 +185,13 @@ export default class HttpResponse {
|
|
|
183
185
|
];
|
|
184
186
|
}
|
|
185
187
|
|
|
186
|
-
|
|
187
|
-
|
|
188
|
+
// eslint-disable-next-line prefer-destructuring
|
|
189
|
+
this.#pipeline = /** @type {Writable} */ (array[0]);
|
|
190
|
+
// @ts-expect-error Bad typings
|
|
188
191
|
pipeline(array, (error) => {
|
|
189
192
|
this.#pipelineComplete = true;
|
|
190
193
|
let nextCallback;
|
|
194
|
+
// eslint-disable-next-line no-cond-assign
|
|
191
195
|
while ((nextCallback = this.#pipelineCallbacks.shift()) != null) {
|
|
192
196
|
nextCallback(error);
|
|
193
197
|
}
|
|
@@ -282,6 +286,7 @@ export default class HttpResponse {
|
|
|
282
286
|
/** @type {void|Promise<boolean|void>} */
|
|
283
287
|
let pendingPromise;
|
|
284
288
|
let index;
|
|
289
|
+
// eslint-disable-next-line no-plusplus
|
|
285
290
|
for (index = 0; index < this.finalizers.length; index++) {
|
|
286
291
|
const process = this.finalizers[index];
|
|
287
292
|
if (needsAsync) {
|
|
@@ -303,6 +308,7 @@ export default class HttpResponse {
|
|
|
303
308
|
if (pendingPromise) {
|
|
304
309
|
pendingPromise.then(async (initialResult) => {
|
|
305
310
|
if (initialResult !== false) {
|
|
311
|
+
// eslint-disable-next-line no-plusplus
|
|
306
312
|
for (index = 0; index < pendingProcessors.length; index++) {
|
|
307
313
|
const process = pendingProcessors[index];
|
|
308
314
|
const result = process(this);
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { Transform } from 'node:stream';
|
|
2
2
|
import {
|
|
3
|
+
// @ts-expect-error Bad typings
|
|
3
4
|
BrotliDecompress, Gunzip, Inflate,
|
|
4
5
|
} from 'node:zlib';
|
|
5
6
|
|
|
@@ -69,17 +70,20 @@ export default class ContentDecoderMiddleware {
|
|
|
69
70
|
const gzipOptions = { chunkSize: this.chunkSize };
|
|
70
71
|
const newDownstream = new Transform({
|
|
71
72
|
|
|
72
|
-
read: (...
|
|
73
|
+
read: (...arguments_) => {
|
|
73
74
|
if (!initialized) {
|
|
74
75
|
/** @type {import("zlib").Gzip} */
|
|
75
76
|
switch (contentEncoding) {
|
|
76
77
|
case 'deflate':
|
|
78
|
+
// @ts-expect-error Bad typings
|
|
77
79
|
gzipStream = new Inflate(gzipOptions);
|
|
78
80
|
break;
|
|
79
81
|
case 'gzip':
|
|
82
|
+
// @ts-expect-error Bad typings
|
|
80
83
|
gzipStream = new Gunzip(gzipOptions);
|
|
81
84
|
break;
|
|
82
85
|
case 'br':
|
|
86
|
+
// @ts-expect-error Bad typings
|
|
83
87
|
gzipStream = new BrotliDecompress(gzipOptions);
|
|
84
88
|
break;
|
|
85
89
|
default:
|
|
@@ -89,7 +93,7 @@ export default class ContentDecoderMiddleware {
|
|
|
89
93
|
// To newDownstream <= gzipStream < =inputStream
|
|
90
94
|
|
|
91
95
|
// Forward errors
|
|
92
|
-
gzipStream.on('error', (
|
|
96
|
+
gzipStream.on('error', (error) => inputStream.emit('error', error));
|
|
93
97
|
gzipStream.on('data', (chunk) => newDownstream.push(chunk));
|
|
94
98
|
|
|
95
99
|
inputStream.on('end', () => gzipStream.end());
|
|
@@ -104,12 +108,13 @@ export default class ContentDecoderMiddleware {
|
|
|
104
108
|
initialized = true;
|
|
105
109
|
}
|
|
106
110
|
|
|
107
|
-
|
|
111
|
+
// eslint-disable-next-line no-underscore-dangle
|
|
112
|
+
Transform.prototype._read.call(this, ...arguments_);
|
|
108
113
|
},
|
|
109
114
|
transform: (chunk, chunkEncoding, callback) => {
|
|
110
|
-
gzipStream.write(chunk, (
|
|
111
|
-
if (
|
|
112
|
-
callback(
|
|
115
|
+
gzipStream.write(chunk, (error) => {
|
|
116
|
+
if (error) console.error(error);
|
|
117
|
+
callback(error);
|
|
113
118
|
});
|
|
114
119
|
},
|
|
115
120
|
flush: (callback) => {
|
|
@@ -129,7 +134,6 @@ export default class ContentDecoderMiddleware {
|
|
|
129
134
|
},
|
|
130
135
|
});
|
|
131
136
|
|
|
132
|
-
newDownstream.tag = 'ContentDecoder';
|
|
133
137
|
inputStream = request.addDownstream(newDownstream, { autoPause: true });
|
|
134
138
|
|
|
135
139
|
return CONTINUE;
|
|
@@ -75,8 +75,9 @@ export default class ContentEncoderMiddleware {
|
|
|
75
75
|
let encoding = COMPATIBLE_ENCODINGS[0];
|
|
76
76
|
const allowWildcards = (encodings.get('*')?.q !== 0);
|
|
77
77
|
const encodingEntries = [...encodings.entries()];
|
|
78
|
-
|
|
79
|
-
|
|
78
|
+
encoding = /** @type {COMPATIBLE_ENCODING} */ (encodingEntries
|
|
79
|
+
.find(([value, spec]) => spec.q !== 0
|
|
80
|
+
&& COMPATIBLE_ENCODINGS.includes(/** @type {COMPATIBLE_ENCODING} */ (value)))?.[0]);
|
|
80
81
|
if (allowWildcards && (encoding === '*' || !encoding)) {
|
|
81
82
|
// Server preference
|
|
82
83
|
// Get first compatible encoding not specified
|
|
@@ -154,7 +155,7 @@ export default class ContentEncoderMiddleware {
|
|
|
154
155
|
encoding = getContentEncoding().toLowerCase?.();
|
|
155
156
|
}
|
|
156
157
|
|
|
157
|
-
const isEventStream = response.headers['content-type']?.includes('text/event-stream');
|
|
158
|
+
const isEventStream = /** @type {string|null} */ (response.headers['content-type'])?.includes('text/event-stream');
|
|
158
159
|
|
|
159
160
|
let newStream;
|
|
160
161
|
switch (encoding) {
|
package/package.json
CHANGED
|
@@ -24,7 +24,16 @@
|
|
|
24
24
|
"node": ">v16.13"
|
|
25
25
|
},
|
|
26
26
|
"exports": {
|
|
27
|
-
"
|
|
27
|
+
"./data/*": "./data/*",
|
|
28
|
+
"./errata/*": "./errata/*",
|
|
29
|
+
"./examples/*": "./examples/*",
|
|
30
|
+
"./helpers/*": "./helpers/*",
|
|
31
|
+
"./lib/*": "./lib/*",
|
|
32
|
+
"./middlewares/*": "./middlewares/*",
|
|
33
|
+
"./polyfills/*": "./polyfills/*",
|
|
34
|
+
"./templates/*": "./templates/*",
|
|
35
|
+
"./types/*": "./types/*",
|
|
36
|
+
"./utils/*": "./utils/*"
|
|
28
37
|
},
|
|
29
38
|
"keywords": [
|
|
30
39
|
"http",
|
|
@@ -42,6 +51,7 @@
|
|
|
42
51
|
"scripts": {
|
|
43
52
|
"debug-test": "ava --serial",
|
|
44
53
|
"test": "c8 ava",
|
|
54
|
+
"prepublishOnly": "rm -Rf typings && tsc --emitDeclarationOnly",
|
|
45
55
|
"pretestallsync": "rimraf coverage",
|
|
46
56
|
"testallsync": "scripts/test-all-sync.sh",
|
|
47
57
|
"posttestallsync": "c8 report",
|
|
@@ -50,5 +60,6 @@
|
|
|
50
60
|
"posttestall": "c8 report"
|
|
51
61
|
},
|
|
52
62
|
"type": "module",
|
|
53
|
-
"
|
|
54
|
-
|
|
63
|
+
"types": "typings/index.d.ts",
|
|
64
|
+
"version": "0.3.3"
|
|
65
|
+
}
|
package/tsconfig.json
CHANGED
|
@@ -1,12 +1,30 @@
|
|
|
1
1
|
{
|
|
2
2
|
"compilerOptions": {
|
|
3
|
+
"allowJs": true,
|
|
3
4
|
"checkJs": true,
|
|
4
5
|
"noImplicitAny": true,
|
|
6
|
+
"declaration": true,
|
|
7
|
+
"declarationMap": true,
|
|
8
|
+
"declarationDir": "typings",
|
|
9
|
+
"outDir": "/dev/null",
|
|
5
10
|
"target": "es2022",
|
|
6
11
|
"module": "esnext",
|
|
7
12
|
"moduleResolution": "node"
|
|
8
13
|
},
|
|
14
|
+
"include": [
|
|
15
|
+
"./data/*",
|
|
16
|
+
"./errata/*",
|
|
17
|
+
"./examples/*",
|
|
18
|
+
"./helpers/*",
|
|
19
|
+
"./lib/*",
|
|
20
|
+
"./middlewares/*",
|
|
21
|
+
"./polyfills/*",
|
|
22
|
+
"./templates/*",
|
|
23
|
+
"./types/*",
|
|
24
|
+
"./utils/*"
|
|
25
|
+
],
|
|
9
26
|
"exclude": [
|
|
10
|
-
"**/node_modules/*"
|
|
27
|
+
"**/node_modules/*",
|
|
28
|
+
"**/test/*"
|
|
11
29
|
]
|
|
12
30
|
}
|
package/types/index.js
CHANGED