@e-mc/request 0.7.16 → 0.7.18
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/LICENSE +1 -1
- package/http/adapter/index.js +561 -0
- package/index.js +21 -16
- package/package.json +3 -3
- package/util.d.ts +1 -0
- package/util.js +23 -5
package/LICENSE
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
Copyright
|
|
1
|
+
Copyright 2023 An Pham
|
|
2
2
|
|
|
3
3
|
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
|
4
4
|
|
|
@@ -0,0 +1,561 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
const fs = require("node:fs");
|
|
3
|
+
const http2 = require("node:http2");
|
|
4
|
+
const yaml = require("js-yaml");
|
|
5
|
+
const types_1 = require("@e-mc/types");
|
|
6
|
+
const module_1 = require("@e-mc/module");
|
|
7
|
+
const util_1 = require("@e-mc/request/util");
|
|
8
|
+
const kOutStream = Symbol('outStream');
|
|
9
|
+
const kOptions = Symbol('options');
|
|
10
|
+
let LOG_TIMEFORMAT = 'readable';
|
|
11
|
+
class HttpAdapter {
|
|
12
|
+
instance;
|
|
13
|
+
state;
|
|
14
|
+
uri;
|
|
15
|
+
static isUnsupported(value) {
|
|
16
|
+
return value === 421 || value === 505;
|
|
17
|
+
}
|
|
18
|
+
static isDowngrade(err) {
|
|
19
|
+
return err instanceof Error && (err.code === 'ERR_HTTP2_ERROR' || this.isUnsupported(Math.abs(err.errno)));
|
|
20
|
+
}
|
|
21
|
+
static wasAborted(err) {
|
|
22
|
+
return err instanceof Error && err.message.startsWith("Aborted");
|
|
23
|
+
}
|
|
24
|
+
static isConnectionError(err) {
|
|
25
|
+
switch (err instanceof Error && err.code) {
|
|
26
|
+
case 'ETIMEDOUT':
|
|
27
|
+
case 'ECONNRESET':
|
|
28
|
+
return true;
|
|
29
|
+
default:
|
|
30
|
+
return false;
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
static defineHostConfig({ settings }) {
|
|
34
|
+
const time_format = settings?.time_format;
|
|
35
|
+
switch (time_format) {
|
|
36
|
+
case 'readable':
|
|
37
|
+
case 'relative':
|
|
38
|
+
case 'none':
|
|
39
|
+
LOG_TIMEFORMAT = time_format;
|
|
40
|
+
break;
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
contentLength = 0;
|
|
44
|
+
retries = 0;
|
|
45
|
+
redirects = 0;
|
|
46
|
+
closed = false;
|
|
47
|
+
aborted = false;
|
|
48
|
+
timeout = null;
|
|
49
|
+
dataTime = null;
|
|
50
|
+
delayTime = undefined;
|
|
51
|
+
opts;
|
|
52
|
+
client;
|
|
53
|
+
resolve;
|
|
54
|
+
reject;
|
|
55
|
+
startTime;
|
|
56
|
+
[kOutStream] = null;
|
|
57
|
+
[kOptions];
|
|
58
|
+
constructor(instance, state, uri, options) {
|
|
59
|
+
this.instance = instance;
|
|
60
|
+
this.state = state;
|
|
61
|
+
this.uri = uri;
|
|
62
|
+
this[kOptions] = options;
|
|
63
|
+
this.startTime = state.log ? process.hrtime.bigint() : BigInt(0);
|
|
64
|
+
this.setOpts();
|
|
65
|
+
}
|
|
66
|
+
async start() {
|
|
67
|
+
return new Promise((resolve, reject) => {
|
|
68
|
+
this.resolve = resolve;
|
|
69
|
+
this.reject = reject;
|
|
70
|
+
this.init();
|
|
71
|
+
});
|
|
72
|
+
}
|
|
73
|
+
init() {
|
|
74
|
+
this.aborted = false;
|
|
75
|
+
this.setWriteStream();
|
|
76
|
+
this.client = this.instance.open(this.uri, this.opts);
|
|
77
|
+
if (this.opts.httpVersion === 2) {
|
|
78
|
+
this.client
|
|
79
|
+
.on('response', (headers, flags) => {
|
|
80
|
+
if (this.destroyed) {
|
|
81
|
+
return;
|
|
82
|
+
}
|
|
83
|
+
const statusCode = headers[':status'];
|
|
84
|
+
if (statusCode < 300) {
|
|
85
|
+
this.acceptResponse(headers);
|
|
86
|
+
}
|
|
87
|
+
else if (statusCode < 400) {
|
|
88
|
+
this.redirectResponse(statusCode, headers.location);
|
|
89
|
+
}
|
|
90
|
+
else if (statusCode === 401 ||
|
|
91
|
+
statusCode === 402 ||
|
|
92
|
+
statusCode === 403 ||
|
|
93
|
+
statusCode === 404 ||
|
|
94
|
+
statusCode === 407 ||
|
|
95
|
+
statusCode === 410) {
|
|
96
|
+
this.terminate(this.formatStatus(statusCode));
|
|
97
|
+
}
|
|
98
|
+
else if (this.isRetry(statusCode)) {
|
|
99
|
+
this.retryResponse(statusCode, headers['retry-after']);
|
|
100
|
+
}
|
|
101
|
+
else if (HttpAdapter.isUnsupported(statusCode)) {
|
|
102
|
+
this.retryDownload(true, this.formatNgFlags(http2.constants.NGHTTP2_PROTOCOL_ERROR, statusCode));
|
|
103
|
+
}
|
|
104
|
+
else {
|
|
105
|
+
switch (flags) {
|
|
106
|
+
case http2.constants.NGHTTP2_PROTOCOL_ERROR:
|
|
107
|
+
case http2.constants.NGHTTP2_INADEQUATE_SECURITY:
|
|
108
|
+
case http2.constants.NGHTTP2_HTTP_1_1_REQUIRED:
|
|
109
|
+
this.retryDownload(true, this.formatNgFlags(flags, statusCode, headers.location));
|
|
110
|
+
break;
|
|
111
|
+
default:
|
|
112
|
+
this.retryDownload(false, this.formatStatus(statusCode));
|
|
113
|
+
break;
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
})
|
|
117
|
+
.on('unknownProtocol', () => {
|
|
118
|
+
if (this.aborted) {
|
|
119
|
+
return;
|
|
120
|
+
}
|
|
121
|
+
this.retryDownload(true, 'Unknown protocol (HTTP/2)');
|
|
122
|
+
})
|
|
123
|
+
.on('aborted', () => {
|
|
124
|
+
this.aborted = true;
|
|
125
|
+
this.terminate((0, types_1.createAbortError)());
|
|
126
|
+
})
|
|
127
|
+
.on('error', async (err) => {
|
|
128
|
+
if (this.aborted) {
|
|
129
|
+
return;
|
|
130
|
+
}
|
|
131
|
+
if (HttpAdapter.wasAborted(err)) {
|
|
132
|
+
this.errorResponse(err);
|
|
133
|
+
}
|
|
134
|
+
else {
|
|
135
|
+
switch (!HttpAdapter.isDowngrade(err) && await this.host.hasProtocol(2)) {
|
|
136
|
+
case 1:
|
|
137
|
+
this.errorResponse(err);
|
|
138
|
+
break;
|
|
139
|
+
case 2:
|
|
140
|
+
this.retryDownload(false, err);
|
|
141
|
+
break;
|
|
142
|
+
default:
|
|
143
|
+
this.retryDownload(true, err);
|
|
144
|
+
break;
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
});
|
|
148
|
+
}
|
|
149
|
+
else {
|
|
150
|
+
this.client
|
|
151
|
+
.on('response', res => {
|
|
152
|
+
if (this.destroyed) {
|
|
153
|
+
return;
|
|
154
|
+
}
|
|
155
|
+
const statusCode = res.statusCode;
|
|
156
|
+
if (statusCode < 300) {
|
|
157
|
+
this.acceptResponse(res.headers);
|
|
158
|
+
}
|
|
159
|
+
else if (statusCode < 400) {
|
|
160
|
+
this.redirectResponse(statusCode, res.headers.location);
|
|
161
|
+
}
|
|
162
|
+
else if (this.isRetry(statusCode)) {
|
|
163
|
+
this.retryResponse(statusCode, res.headers['retry-after']);
|
|
164
|
+
}
|
|
165
|
+
else {
|
|
166
|
+
this.terminate(this.formatStatus(statusCode));
|
|
167
|
+
}
|
|
168
|
+
})
|
|
169
|
+
.on('abort', () => {
|
|
170
|
+
this.aborted = true;
|
|
171
|
+
this.terminate((0, types_1.createAbortError)());
|
|
172
|
+
})
|
|
173
|
+
.on('error', err => {
|
|
174
|
+
if (this.aborted) {
|
|
175
|
+
return;
|
|
176
|
+
}
|
|
177
|
+
this.errorResponse(err);
|
|
178
|
+
});
|
|
179
|
+
}
|
|
180
|
+
this.client.on('timeout', () => {
|
|
181
|
+
if (this.aborted) {
|
|
182
|
+
return;
|
|
183
|
+
}
|
|
184
|
+
if (++this.retries <= this.retryLimit) {
|
|
185
|
+
this.abortResponse();
|
|
186
|
+
this.retryTimeout();
|
|
187
|
+
}
|
|
188
|
+
else {
|
|
189
|
+
this.terminate(this.formatStatus(408));
|
|
190
|
+
}
|
|
191
|
+
});
|
|
192
|
+
}
|
|
193
|
+
setOpts(uri) {
|
|
194
|
+
if (uri) {
|
|
195
|
+
this.uri = uri;
|
|
196
|
+
}
|
|
197
|
+
this.opts = this.instance.opts(this.uri, this[kOptions]);
|
|
198
|
+
}
|
|
199
|
+
setWriteStream() {
|
|
200
|
+
const pipeTo = this.pipeTo;
|
|
201
|
+
if (typeof pipeTo === 'string') {
|
|
202
|
+
try {
|
|
203
|
+
this.outStream = fs.createWriteStream(pipeTo, { emitClose: false, highWaterMark: this.host.streamSize });
|
|
204
|
+
}
|
|
205
|
+
catch (err) {
|
|
206
|
+
this.terminate(err);
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
else if (pipeTo) {
|
|
210
|
+
this.outStream = pipeTo;
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
retryDownload(downgrade, message) {
|
|
214
|
+
if (this.aborted) {
|
|
215
|
+
return;
|
|
216
|
+
}
|
|
217
|
+
this.abortResponse();
|
|
218
|
+
if (downgrade) {
|
|
219
|
+
const host = this.host;
|
|
220
|
+
host.failed(2);
|
|
221
|
+
if (host.version > 1) {
|
|
222
|
+
if (this.state.verbose) {
|
|
223
|
+
this.instance.formatMessage(1024, 'HTTP2', ["Unsupported protocol", host.origin], message, { failed: true });
|
|
224
|
+
}
|
|
225
|
+
host.version = 1;
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
this.opts.httpVersion = 1;
|
|
229
|
+
this.init();
|
|
230
|
+
}
|
|
231
|
+
acceptResponse(headers) {
|
|
232
|
+
const opts = this.opts;
|
|
233
|
+
if ('outHeaders' in opts) {
|
|
234
|
+
opts.outHeaders = headers;
|
|
235
|
+
}
|
|
236
|
+
if ('outFilename' in opts) {
|
|
237
|
+
opts.outFilename = (0, util_1.parseHeader)(headers, 'content-disposition');
|
|
238
|
+
}
|
|
239
|
+
const pipeline = this.pipeTo ? !(0, types_1.isString)(this.pipeTo) : false;
|
|
240
|
+
const enabled = opts.connected?.call(this.client, headers) !== false && !pipeline;
|
|
241
|
+
const maxBufferSize = opts.maxBufferSize ? (0, types_1.alignSize)(opts.maxBufferSize) : 0;
|
|
242
|
+
this.contentLength = parseInt(headers['content-length'] || '0');
|
|
243
|
+
const updating = opts.progressId !== undefined && !!this.instance.host && this.contentLength > 0;
|
|
244
|
+
const readTimeout = this.instance.readTimeout;
|
|
245
|
+
let log = this.state.log, buffer = null, dataLength = 0;
|
|
246
|
+
this.client.once('readable', () => {
|
|
247
|
+
if (readTimeout > 0) {
|
|
248
|
+
this.timeout = setTimeout(() => {
|
|
249
|
+
this.terminate((0, types_1.errorValue)("Timeout was exceeded", this.uri.toString()));
|
|
250
|
+
}, readTimeout);
|
|
251
|
+
}
|
|
252
|
+
if (log) {
|
|
253
|
+
switch (this.instance.settings?.time_format || LOG_TIMEFORMAT) {
|
|
254
|
+
case 'readable':
|
|
255
|
+
this.delayTime = process.hrtime.bigint() - this.startTime;
|
|
256
|
+
break;
|
|
257
|
+
case 'relative':
|
|
258
|
+
this.delayTime = Date.now() - this.instance.startTime;
|
|
259
|
+
break;
|
|
260
|
+
case 'none':
|
|
261
|
+
if (!this.silent) {
|
|
262
|
+
log = false;
|
|
263
|
+
}
|
|
264
|
+
break;
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
this.dataTime = process.hrtime.bigint();
|
|
268
|
+
if (updating) {
|
|
269
|
+
this.updateProgress(0, 0);
|
|
270
|
+
}
|
|
271
|
+
});
|
|
272
|
+
if (enabled) {
|
|
273
|
+
const encoding = opts.encoding;
|
|
274
|
+
this.client.on('data', (chunk) => {
|
|
275
|
+
if (buffer) {
|
|
276
|
+
if (typeof buffer === 'string') {
|
|
277
|
+
buffer += typeof chunk === 'string' ? chunk : chunk.toString(encoding);
|
|
278
|
+
}
|
|
279
|
+
else if (Array.isArray(buffer)) {
|
|
280
|
+
buffer.push(typeof chunk === 'string' ? Buffer.from(chunk, encoding) : chunk);
|
|
281
|
+
}
|
|
282
|
+
else {
|
|
283
|
+
buffer = Buffer.concat([buffer, typeof chunk === 'string' ? Buffer.from(chunk, encoding) : chunk]);
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
else {
|
|
287
|
+
buffer = typeof chunk === 'string' ? chunk : [chunk];
|
|
288
|
+
}
|
|
289
|
+
if (updating) {
|
|
290
|
+
dataLength += Buffer.byteLength(chunk, encoding);
|
|
291
|
+
this.updateProgress(dataLength, this.contentLength);
|
|
292
|
+
}
|
|
293
|
+
if (maxBufferSize > 0) {
|
|
294
|
+
if (!updating) {
|
|
295
|
+
dataLength += Buffer.byteLength(chunk, encoding);
|
|
296
|
+
}
|
|
297
|
+
if (dataLength > maxBufferSize) {
|
|
298
|
+
this.terminate((0, types_1.errorValue)("Size limit was exceeded", this.uri.toString()));
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
});
|
|
302
|
+
}
|
|
303
|
+
this.client.once('end', () => {
|
|
304
|
+
if (this.closed || this.aborted) {
|
|
305
|
+
return;
|
|
306
|
+
}
|
|
307
|
+
this.close();
|
|
308
|
+
const encoding = opts.encoding;
|
|
309
|
+
let result;
|
|
310
|
+
if (buffer) {
|
|
311
|
+
if (Array.isArray(buffer)) {
|
|
312
|
+
buffer = Buffer.concat(buffer);
|
|
313
|
+
if (encoding) {
|
|
314
|
+
buffer = buffer.toString(encoding);
|
|
315
|
+
}
|
|
316
|
+
}
|
|
317
|
+
dataLength = Buffer.byteLength(buffer, encoding);
|
|
318
|
+
if (updating) {
|
|
319
|
+
this.updateProgress(dataLength, dataLength);
|
|
320
|
+
}
|
|
321
|
+
if (typeof buffer === 'string') {
|
|
322
|
+
if (buffer.startsWith('\uFEFF') && encoding !== 'utf16le' && encoding !== 'utf-16le') {
|
|
323
|
+
buffer = buffer.substring(1);
|
|
324
|
+
}
|
|
325
|
+
if (opts.outFormat) {
|
|
326
|
+
const { out: format, parser } = opts.outFormat;
|
|
327
|
+
let packageName;
|
|
328
|
+
try {
|
|
329
|
+
switch (format) {
|
|
330
|
+
case 'yaml':
|
|
331
|
+
result = yaml.load(buffer, parser);
|
|
332
|
+
break;
|
|
333
|
+
case 'json5':
|
|
334
|
+
result = require(packageName = 'json5').parse(buffer);
|
|
335
|
+
break;
|
|
336
|
+
case 'xml':
|
|
337
|
+
result = new (require(packageName = 'fast-xml-parser').XMLParser)(parser).parse(buffer);
|
|
338
|
+
break;
|
|
339
|
+
case 'toml':
|
|
340
|
+
result = require(packageName = 'toml').parse(buffer);
|
|
341
|
+
break;
|
|
342
|
+
default:
|
|
343
|
+
result = JSON.parse(buffer);
|
|
344
|
+
break;
|
|
345
|
+
}
|
|
346
|
+
if (!(0, types_1.isObject)(result)) {
|
|
347
|
+
result = null;
|
|
348
|
+
}
|
|
349
|
+
}
|
|
350
|
+
catch (err) {
|
|
351
|
+
if (this.state.verbose && !(packageName && this.instance.checkPackage(err, packageName))) {
|
|
352
|
+
this.instance.writeFail(["Unable to parse URI response", format], err, 1024);
|
|
353
|
+
}
|
|
354
|
+
result = null;
|
|
355
|
+
}
|
|
356
|
+
}
|
|
357
|
+
}
|
|
358
|
+
if (result === undefined) {
|
|
359
|
+
result = buffer;
|
|
360
|
+
}
|
|
361
|
+
}
|
|
362
|
+
else {
|
|
363
|
+
if (updating) {
|
|
364
|
+
this.updateProgress(0, this.contentLength);
|
|
365
|
+
}
|
|
366
|
+
if (enabled && this.instance.readExpect === 'always') {
|
|
367
|
+
this.terminate("No data received");
|
|
368
|
+
return;
|
|
369
|
+
}
|
|
370
|
+
result = encoding && !pipeline ? '' : null;
|
|
371
|
+
}
|
|
372
|
+
this.endResponse(result, dataLength, log);
|
|
373
|
+
});
|
|
374
|
+
this.host.success(this.httpVersion);
|
|
375
|
+
}
|
|
376
|
+
updateProgress(dataLength, contentLength) {
|
|
377
|
+
const { host, moduleName } = this.instance;
|
|
378
|
+
host.updateProgress(moduleName, this.opts.progressId, dataLength, contentLength, this.dataTime);
|
|
379
|
+
}
|
|
380
|
+
endResponse(result, dataLength = 0, logging = this.state.log) {
|
|
381
|
+
if (logging) {
|
|
382
|
+
const messageUnit = this.dataTime && dataLength > 0 ? (0, util_1.getTransferRate)(dataLength, (0, types_1.convertTime)(process.hrtime.bigint() - this.dataTime, false) * 1000) : undefined;
|
|
383
|
+
this.instance.writeTimeProcess('HTTP' + this.httpVersion, this.opts.statusMessage || this.uri.toString(), this.startTime, {
|
|
384
|
+
type: 1024,
|
|
385
|
+
queue: !!this.instance.host,
|
|
386
|
+
titleBgColor: !result ? 'bgBlue' : undefined,
|
|
387
|
+
messageUnit,
|
|
388
|
+
messageUnitMinWidth: 9,
|
|
389
|
+
delayTime: this.delayTime,
|
|
390
|
+
bypassLog: module_1.hasLogType(32768)
|
|
391
|
+
});
|
|
392
|
+
}
|
|
393
|
+
this.resolve(result);
|
|
394
|
+
}
|
|
395
|
+
redirectResponse(statusCode, location) {
|
|
396
|
+
if (location) {
|
|
397
|
+
if (this.opts.followRedirect === false) {
|
|
398
|
+
this.terminate(this.formatStatus(statusCode, "Follow redirect was disabled"));
|
|
399
|
+
}
|
|
400
|
+
else if (++this.redirects <= this.redirectLimit) {
|
|
401
|
+
this.abortResponse();
|
|
402
|
+
this.setOpts((0, util_1.fromURL)(this.opts.url, location));
|
|
403
|
+
this.init();
|
|
404
|
+
}
|
|
405
|
+
else {
|
|
406
|
+
this.terminate(this.formatStatus(statusCode, "Exceeded redirect limit"));
|
|
407
|
+
}
|
|
408
|
+
}
|
|
409
|
+
else {
|
|
410
|
+
this.terminate(this.formatStatus(statusCode, "Missing redirect location"));
|
|
411
|
+
}
|
|
412
|
+
}
|
|
413
|
+
abortResponse() {
|
|
414
|
+
if (this.closed) {
|
|
415
|
+
return;
|
|
416
|
+
}
|
|
417
|
+
this.aborted = true;
|
|
418
|
+
this.client.destroy();
|
|
419
|
+
this.instance.reset(this);
|
|
420
|
+
this.cleanup();
|
|
421
|
+
}
|
|
422
|
+
errorResponse(err) {
|
|
423
|
+
if (HttpAdapter.wasAborted(err)) {
|
|
424
|
+
this.terminate(err);
|
|
425
|
+
}
|
|
426
|
+
else if ((0, util_1.checkRetryable)(err) && ++this.retries <= this.retryLimit) {
|
|
427
|
+
this.abortResponse();
|
|
428
|
+
if (HttpAdapter.isConnectionError(err)) {
|
|
429
|
+
this.retryTimeout();
|
|
430
|
+
}
|
|
431
|
+
else {
|
|
432
|
+
setTimeout(() => {
|
|
433
|
+
this.init();
|
|
434
|
+
}, this.retryWait);
|
|
435
|
+
}
|
|
436
|
+
}
|
|
437
|
+
else {
|
|
438
|
+
this.host.error(this.httpVersion);
|
|
439
|
+
this.terminate(err);
|
|
440
|
+
}
|
|
441
|
+
}
|
|
442
|
+
retryResponse(statusCode, retryAfter) {
|
|
443
|
+
this.abortResponse();
|
|
444
|
+
if (retryAfter && this.retryAfter > 0) {
|
|
445
|
+
let offset = +retryAfter || new Date(retryAfter);
|
|
446
|
+
if (offset instanceof Date) {
|
|
447
|
+
offset = Math.max(0, offset.getTime() - Date.now());
|
|
448
|
+
}
|
|
449
|
+
else {
|
|
450
|
+
offset *= 1000;
|
|
451
|
+
}
|
|
452
|
+
if (offset > 0) {
|
|
453
|
+
if (offset <= this.retryAfter) {
|
|
454
|
+
this.sendWarning(`Retry After (${retryAfter})`);
|
|
455
|
+
setTimeout(() => {
|
|
456
|
+
this.init();
|
|
457
|
+
}, offset);
|
|
458
|
+
}
|
|
459
|
+
else {
|
|
460
|
+
this.terminate(this.formatStatus(statusCode));
|
|
461
|
+
}
|
|
462
|
+
return;
|
|
463
|
+
}
|
|
464
|
+
}
|
|
465
|
+
this.sendWarning(this.formatRetry((0, util_1.fromStatusCode)(statusCode)));
|
|
466
|
+
if ((0, util_1.isRetryable)(statusCode, true)) {
|
|
467
|
+
setImmediate(this.init.bind(this));
|
|
468
|
+
}
|
|
469
|
+
else {
|
|
470
|
+
setTimeout(() => {
|
|
471
|
+
this.init();
|
|
472
|
+
}, this.retryWait);
|
|
473
|
+
}
|
|
474
|
+
}
|
|
475
|
+
isRetry(value) {
|
|
476
|
+
return (0, util_1.isRetryable)(value) && ++this.retries <= this.retryLimit;
|
|
477
|
+
}
|
|
478
|
+
retryTimeout() {
|
|
479
|
+
this.sendWarning(this.formatRetry("HTTP connection timeout"));
|
|
480
|
+
this.init();
|
|
481
|
+
}
|
|
482
|
+
terminate(err) {
|
|
483
|
+
if (this.closed) {
|
|
484
|
+
return;
|
|
485
|
+
}
|
|
486
|
+
this.cleanup();
|
|
487
|
+
this.close();
|
|
488
|
+
this.reject(typeof err === 'string' ? new Error(err) : err);
|
|
489
|
+
}
|
|
490
|
+
sendWarning(message) {
|
|
491
|
+
if (this.state.verbose) {
|
|
492
|
+
const { host, url } = this.opts;
|
|
493
|
+
this.instance.formatMessage(1024, 'HTTP' + this.httpVersion, [message, host.origin], url.toString(), { ...module_1.LOG_STYLE_WARN });
|
|
494
|
+
}
|
|
495
|
+
}
|
|
496
|
+
formatStatus(value, hint) {
|
|
497
|
+
return value + ': ' + (hint || (0, util_1.fromStatusCode)(value)) + ` (${this.uri.toString()})`;
|
|
498
|
+
}
|
|
499
|
+
close() {
|
|
500
|
+
this.closed = true;
|
|
501
|
+
this.instance.reset(this);
|
|
502
|
+
if (this.aborted && !this.client.aborted) {
|
|
503
|
+
this.abortController?.abort();
|
|
504
|
+
}
|
|
505
|
+
}
|
|
506
|
+
cleanup() {
|
|
507
|
+
if ((0, types_1.isString)(this.pipeTo) && this.outStream) {
|
|
508
|
+
(0, util_1.cleanupStream)(this.outStream, this.pipeTo);
|
|
509
|
+
this.outStream = null;
|
|
510
|
+
}
|
|
511
|
+
}
|
|
512
|
+
formatNgFlags(value, statusCode, location) {
|
|
513
|
+
return location ? `Using HTTP 1.1 for URL redirect (${location})` : this.formatStatus(statusCode, value ? 'NGHTTP2 Error ' + value : '');
|
|
514
|
+
}
|
|
515
|
+
formatRetry(message) {
|
|
516
|
+
return `${message} (${this.retries} / ${this.retryLimit})`;
|
|
517
|
+
}
|
|
518
|
+
set abortController(value) {
|
|
519
|
+
this.opts.outAbort = value;
|
|
520
|
+
}
|
|
521
|
+
get abortController() {
|
|
522
|
+
return this.opts.outAbort || null;
|
|
523
|
+
}
|
|
524
|
+
set outStream(value) {
|
|
525
|
+
this[kOutStream] = value;
|
|
526
|
+
if (value) {
|
|
527
|
+
this.opts.outStream = value;
|
|
528
|
+
}
|
|
529
|
+
}
|
|
530
|
+
get outStream() {
|
|
531
|
+
return this[kOutStream];
|
|
532
|
+
}
|
|
533
|
+
get destroyed() {
|
|
534
|
+
return this.client.destroyed || this.httpVersion === 2 && this.client.aborted;
|
|
535
|
+
}
|
|
536
|
+
get host() {
|
|
537
|
+
return this.opts.host;
|
|
538
|
+
}
|
|
539
|
+
get httpVersion() {
|
|
540
|
+
return this.opts.httpVersion;
|
|
541
|
+
}
|
|
542
|
+
get pipeTo() {
|
|
543
|
+
return this.opts.pipeTo;
|
|
544
|
+
}
|
|
545
|
+
get silent() {
|
|
546
|
+
return this.opts.silent === false;
|
|
547
|
+
}
|
|
548
|
+
get retryLimit() {
|
|
549
|
+
return this.state.config.retryLimit;
|
|
550
|
+
}
|
|
551
|
+
get retryWait() {
|
|
552
|
+
return this.state.config.retryWait;
|
|
553
|
+
}
|
|
554
|
+
get retryAfter() {
|
|
555
|
+
return this.state.config.retryAfter;
|
|
556
|
+
}
|
|
557
|
+
get redirectLimit() {
|
|
558
|
+
return this.state.config.redirectLimit;
|
|
559
|
+
}
|
|
560
|
+
}
|
|
561
|
+
module.exports = HttpAdapter;
|
package/index.js
CHANGED
|
@@ -1006,13 +1006,14 @@ class Request extends module_1.default {
|
|
|
1006
1006
|
return Promise.reject(err);
|
|
1007
1007
|
}
|
|
1008
1008
|
}
|
|
1009
|
-
let pathname, headers, binOpts, silent;
|
|
1009
|
+
let pathname, headers, binOpts, signal, silent;
|
|
1010
1010
|
if (options) {
|
|
1011
1011
|
if (typeof options === 'string') {
|
|
1012
1012
|
pathname = options;
|
|
1013
1013
|
}
|
|
1014
1014
|
else {
|
|
1015
|
-
({ pathname,
|
|
1015
|
+
({ pathname, binOpts, signal, silent } = options);
|
|
1016
|
+
headers = (0, util_1.parseOutgoingHeaders)(options.headers);
|
|
1016
1017
|
if ((0, types_1.isArray)(binOpts)) {
|
|
1017
1018
|
let next = false;
|
|
1018
1019
|
binOpts = binOpts.filter(opt => !((0, types_1.isString)(opt) && /^-[a-z][\S\s]*$/i.test(opt.trim()))).map((opt) => {
|
|
@@ -1316,7 +1317,13 @@ class Request extends module_1.default {
|
|
|
1316
1317
|
}
|
|
1317
1318
|
for (const item of ARIA2.PID_QUEUE) {
|
|
1318
1319
|
try {
|
|
1319
|
-
|
|
1320
|
+
if (pid === item[0] && (signal === null || signal === void 0 ? void 0 : signal.aborted)) {
|
|
1321
|
+
process.kill(item[0]);
|
|
1322
|
+
closeTorrent(item[0]);
|
|
1323
|
+
}
|
|
1324
|
+
else {
|
|
1325
|
+
process.kill(item[0], 0);
|
|
1326
|
+
}
|
|
1320
1327
|
}
|
|
1321
1328
|
catch {
|
|
1322
1329
|
closeTorrent(item[0]);
|
|
@@ -1374,7 +1381,7 @@ class Request extends module_1.default {
|
|
|
1374
1381
|
open(uri, options) {
|
|
1375
1382
|
var _l;
|
|
1376
1383
|
var _m, _o;
|
|
1377
|
-
let { host, url, httpVersion, method = 'GET', encoding, format, headers, postData, keepAlive, agentTimeout, socketPath, timeout = this._config.connectTimeout, outStream } = options;
|
|
1384
|
+
let { host, url, httpVersion, method = 'GET', encoding, format, headers: outgoing, postData, keepAlive, agentTimeout, socketPath, timeout = this._config.connectTimeout, outStream } = options, headers = (0, util_1.parseOutgoingHeaders)(outgoing);
|
|
1378
1385
|
const getting = method === 'GET';
|
|
1379
1386
|
const posting = method === 'POST';
|
|
1380
1387
|
if (format) {
|
|
@@ -1629,6 +1636,9 @@ class Request extends module_1.default {
|
|
|
1629
1636
|
const ac = new AbortController();
|
|
1630
1637
|
this[kDownloading].add(options.outAbort = ac);
|
|
1631
1638
|
stream.addAbortSignal(ac.signal, request);
|
|
1639
|
+
if (options.signal) {
|
|
1640
|
+
stream.addAbortSignal(options.signal, request);
|
|
1641
|
+
}
|
|
1632
1642
|
this.signal.addEventListener('abort', () => ac.abort(), { once: true });
|
|
1633
1643
|
}
|
|
1634
1644
|
if (posting) {
|
|
@@ -1678,7 +1688,7 @@ class Request extends module_1.default {
|
|
|
1678
1688
|
else {
|
|
1679
1689
|
options = {};
|
|
1680
1690
|
}
|
|
1681
|
-
const headers = options.headers || (options.headers = {});
|
|
1691
|
+
const headers = (0, util_1.parseOutgoingHeaders)(options.headers || (options.headers = {}));
|
|
1682
1692
|
for (const attr in headers) {
|
|
1683
1693
|
const name = attr.toLowerCase();
|
|
1684
1694
|
if (name === 'content-type' || name === 'content-length') {
|
|
@@ -1841,7 +1851,7 @@ class Request extends module_1.default {
|
|
|
1841
1851
|
}
|
|
1842
1852
|
const client = this.open(href, request);
|
|
1843
1853
|
const { host, url, encoding, outFormat } = request;
|
|
1844
|
-
let buffer, aborted;
|
|
1854
|
+
let buffer, aborted = false;
|
|
1845
1855
|
({ httpVersion, outAbort } = request);
|
|
1846
1856
|
const isAborted = () => client.destroyed || httpVersion === 2 && client.aborted;
|
|
1847
1857
|
const isRetry = (value) => (0, util_1.isRetryable)(value) && ++retries <= this._config.retryLimit;
|
|
@@ -1856,14 +1866,11 @@ class Request extends module_1.default {
|
|
|
1856
1866
|
if (timeout) {
|
|
1857
1867
|
clearTimeout(timeout);
|
|
1858
1868
|
}
|
|
1859
|
-
buffer = null;
|
|
1860
|
-
aborted = true;
|
|
1861
1869
|
if (outAbort) {
|
|
1862
|
-
if (!client.aborted) {
|
|
1863
|
-
outAbort.abort();
|
|
1864
|
-
}
|
|
1865
1870
|
this[kDownloading].delete(outAbort);
|
|
1866
1871
|
}
|
|
1872
|
+
buffer = null;
|
|
1873
|
+
aborted = true;
|
|
1867
1874
|
client.destroy();
|
|
1868
1875
|
};
|
|
1869
1876
|
const retryTimeout = () => {
|
|
@@ -1888,7 +1895,6 @@ class Request extends module_1.default {
|
|
|
1888
1895
|
var _l;
|
|
1889
1896
|
if (readTimeout > 0) {
|
|
1890
1897
|
timeout = setTimeout(() => {
|
|
1891
|
-
abortResponse();
|
|
1892
1898
|
throwError((0, types_1.errorValue)("Timeout was exceeded", href.toString()));
|
|
1893
1899
|
}, readTimeout);
|
|
1894
1900
|
}
|
|
@@ -2017,9 +2023,9 @@ class Request extends module_1.default {
|
|
|
2017
2023
|
host.success(httpVersion);
|
|
2018
2024
|
};
|
|
2019
2025
|
const redirectResponse = (statusCode, location) => {
|
|
2020
|
-
abortResponse();
|
|
2021
2026
|
if (location) {
|
|
2022
2027
|
if (++redirects <= this._config.redirectLimit) {
|
|
2028
|
+
abortResponse();
|
|
2023
2029
|
downloadUri.call(this, Request.fromURL(url, location));
|
|
2024
2030
|
}
|
|
2025
2031
|
else {
|
|
@@ -2031,8 +2037,8 @@ class Request extends module_1.default {
|
|
|
2031
2037
|
}
|
|
2032
2038
|
};
|
|
2033
2039
|
const errorResponse = (err) => {
|
|
2034
|
-
abortResponse();
|
|
2035
2040
|
if ((0, util_1.checkRetryable)(err) && ++retries <= this._config.retryLimit) {
|
|
2041
|
+
abortResponse();
|
|
2036
2042
|
if (isConnectionTimeout(err)) {
|
|
2037
2043
|
retryTimeout();
|
|
2038
2044
|
}
|
|
@@ -2175,7 +2181,6 @@ class Request extends module_1.default {
|
|
|
2175
2181
|
retryResponse(statusCode, res.headers['retry-after']);
|
|
2176
2182
|
}
|
|
2177
2183
|
else {
|
|
2178
|
-
abortResponse();
|
|
2179
2184
|
throwError(formatStatus(statusCode));
|
|
2180
2185
|
}
|
|
2181
2186
|
})
|
|
@@ -2193,8 +2198,8 @@ class Request extends module_1.default {
|
|
|
2193
2198
|
if (aborted) {
|
|
2194
2199
|
return;
|
|
2195
2200
|
}
|
|
2196
|
-
abortResponse();
|
|
2197
2201
|
if (++retries <= this._config.retryLimit) {
|
|
2202
|
+
abortResponse();
|
|
2198
2203
|
retryTimeout();
|
|
2199
2204
|
}
|
|
2200
2205
|
else {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@e-mc/request",
|
|
3
|
-
"version": "0.7.
|
|
3
|
+
"version": "0.7.18",
|
|
4
4
|
"description": "Request constructor for E-mc.",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"types": "index.d.ts",
|
|
@@ -20,8 +20,8 @@
|
|
|
20
20
|
"license": "MIT",
|
|
21
21
|
"homepage": "https://github.com/anpham6/e-mc#readme",
|
|
22
22
|
"dependencies": {
|
|
23
|
-
"@e-mc/module": "0.7.
|
|
24
|
-
"@e-mc/types": "0.7.
|
|
23
|
+
"@e-mc/module": "0.7.18",
|
|
24
|
+
"@e-mc/types": "0.7.18",
|
|
25
25
|
"combined-stream": "^1.0.8",
|
|
26
26
|
"js-yaml": "^4.1.0",
|
|
27
27
|
"which": "^2.0.2"
|
package/util.d.ts
CHANGED
|
@@ -4,6 +4,7 @@ import type { IncomingHttpHeaders, OutgoingHttpHeaders } from 'http';
|
|
|
4
4
|
|
|
5
5
|
declare namespace util {
|
|
6
6
|
function parseHeader<T = unknown>(headers: IncomingHttpHeaders, name: string): T | undefined;
|
|
7
|
+
function parseOutgoingHeaders(headers: OutgoingHttpHeaders | Headers | undefined): OutgoingHttpHeaders | undefined;
|
|
7
8
|
function normalizeHeaders(headers: OutgoingHttpHeaders): OutgoingHttpHeaders;
|
|
8
9
|
function getBasicAuth(auth: AuthValue): string;
|
|
9
10
|
function getBasicAuth(username: unknown, password?: unknown): string;
|
package/util.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.fromSeconds = exports.asFloat = exports.asInt = exports.trimPath = exports.isRetryable = exports.checkRetryable = exports.hasBasicAuth = exports.getBasicAuth = exports.normalizeHeaders = exports.parseHeader = void 0;
|
|
3
|
+
exports.fromSeconds = exports.asFloat = exports.asInt = exports.trimPath = exports.isRetryable = exports.checkRetryable = exports.hasBasicAuth = exports.getBasicAuth = exports.normalizeHeaders = exports.parseOutgoingHeaders = exports.parseHeader = void 0;
|
|
4
4
|
const util = require("util");
|
|
5
5
|
const module_1 = require("@e-mc/module");
|
|
6
6
|
const types_1 = require("@e-mc/types");
|
|
@@ -25,6 +25,24 @@ function parseHeader(headers, name) {
|
|
|
25
25
|
}
|
|
26
26
|
}
|
|
27
27
|
exports.parseHeader = parseHeader;
|
|
28
|
+
function parseOutgoingHeaders(headers) {
|
|
29
|
+
if (headers) {
|
|
30
|
+
if (globalThis.Headers && headers instanceof Headers) {
|
|
31
|
+
const result = {};
|
|
32
|
+
headers.forEach((value, key) => {
|
|
33
|
+
if (key === 'set-cookie') {
|
|
34
|
+
(result[key] || (result[key] = [])).push(value);
|
|
35
|
+
}
|
|
36
|
+
else {
|
|
37
|
+
result[key] = value;
|
|
38
|
+
}
|
|
39
|
+
});
|
|
40
|
+
return result;
|
|
41
|
+
}
|
|
42
|
+
return headers;
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
exports.parseOutgoingHeaders = parseOutgoingHeaders;
|
|
28
46
|
function normalizeHeaders(headers) {
|
|
29
47
|
const result = Object.create(null);
|
|
30
48
|
for (const name in headers) {
|
|
@@ -37,11 +55,11 @@ function normalizeHeaders(headers) {
|
|
|
37
55
|
value = value.trim();
|
|
38
56
|
break;
|
|
39
57
|
default:
|
|
40
|
-
if (
|
|
41
|
-
|
|
42
|
-
break;
|
|
58
|
+
if (!(0, types_1.isArray)(value)) {
|
|
59
|
+
continue;
|
|
43
60
|
}
|
|
44
|
-
|
|
61
|
+
value = value.map(out => module_1.default.asString(out).trim());
|
|
62
|
+
break;
|
|
45
63
|
}
|
|
46
64
|
if (value) {
|
|
47
65
|
result[trimPath(name.trim().toLowerCase())] = value;
|