@e-mc/request 0.6.13 → 0.6.15
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 +554 -0
- package/index.js +42 -33
- 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,554 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var _a;
|
|
3
|
+
const fs = require("node:fs");
|
|
4
|
+
const http2 = require("node:http2");
|
|
5
|
+
const yaml = require("js-yaml");
|
|
6
|
+
const types_1 = require("@e-mc/types");
|
|
7
|
+
const module_1 = require("@e-mc/module");
|
|
8
|
+
const util_1 = require("@e-mc/request/util");
|
|
9
|
+
const kOutStream = Symbol('outStream');
|
|
10
|
+
const kOptions = Symbol('options');
|
|
11
|
+
let LOG_TIMEFORMAT = 'readable';
|
|
12
|
+
class HttpAdapter {
|
|
13
|
+
static isUnsupported(value) {
|
|
14
|
+
return value === 421 || value === 505;
|
|
15
|
+
}
|
|
16
|
+
static isDowngrade(err) {
|
|
17
|
+
return err instanceof Error && (err.code === 'ERR_HTTP2_ERROR' || this.isUnsupported(Math.abs(err.errno)));
|
|
18
|
+
}
|
|
19
|
+
static wasAborted(err) {
|
|
20
|
+
return err instanceof Error && err.message.startsWith("Aborted");
|
|
21
|
+
}
|
|
22
|
+
static isConnectionError(err) {
|
|
23
|
+
switch (err instanceof Error && err.code) {
|
|
24
|
+
case 'ETIMEDOUT':
|
|
25
|
+
case 'ECONNRESET':
|
|
26
|
+
return true;
|
|
27
|
+
default:
|
|
28
|
+
return false;
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
static defineHostConfig({ settings }) {
|
|
32
|
+
const time_format = settings?.time_format;
|
|
33
|
+
switch (time_format) {
|
|
34
|
+
case 'readable':
|
|
35
|
+
case 'relative':
|
|
36
|
+
case 'none':
|
|
37
|
+
LOG_TIMEFORMAT = time_format;
|
|
38
|
+
break;
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
constructor(instance, state, uri, options) {
|
|
42
|
+
this.instance = instance;
|
|
43
|
+
this.state = state;
|
|
44
|
+
this.uri = uri;
|
|
45
|
+
this.contentLength = 0;
|
|
46
|
+
this.retries = 0;
|
|
47
|
+
this.redirects = 0;
|
|
48
|
+
this.closed = false;
|
|
49
|
+
this.aborted = false;
|
|
50
|
+
this.timeout = null;
|
|
51
|
+
this.dataTime = null;
|
|
52
|
+
this.delayTime = undefined;
|
|
53
|
+
this[_a] = null;
|
|
54
|
+
this[kOptions] = options;
|
|
55
|
+
this.startTime = state.log ? process.hrtime.bigint() : BigInt(0);
|
|
56
|
+
this.setOpts();
|
|
57
|
+
}
|
|
58
|
+
async start() {
|
|
59
|
+
return new Promise((resolve, reject) => {
|
|
60
|
+
this.resolve = resolve;
|
|
61
|
+
this.reject = reject;
|
|
62
|
+
this.init();
|
|
63
|
+
});
|
|
64
|
+
}
|
|
65
|
+
init() {
|
|
66
|
+
this.aborted = false;
|
|
67
|
+
this.setWriteStream();
|
|
68
|
+
this.client = this.instance.open(this.uri, this.opts);
|
|
69
|
+
if (this.opts.httpVersion === 2) {
|
|
70
|
+
this.client
|
|
71
|
+
.on('response', (headers, flags) => {
|
|
72
|
+
if (this.destroyed) {
|
|
73
|
+
return;
|
|
74
|
+
}
|
|
75
|
+
const statusCode = headers[':status'];
|
|
76
|
+
if (statusCode < 300) {
|
|
77
|
+
this.acceptResponse(headers);
|
|
78
|
+
}
|
|
79
|
+
else if (statusCode < 400) {
|
|
80
|
+
this.redirectResponse(statusCode, headers.location);
|
|
81
|
+
}
|
|
82
|
+
else if (statusCode === 401 ||
|
|
83
|
+
statusCode === 402 ||
|
|
84
|
+
statusCode === 403 ||
|
|
85
|
+
statusCode === 404 ||
|
|
86
|
+
statusCode === 407 ||
|
|
87
|
+
statusCode === 410) {
|
|
88
|
+
this.terminate(this.formatStatus(statusCode));
|
|
89
|
+
}
|
|
90
|
+
else if (this.isRetry(statusCode)) {
|
|
91
|
+
this.retryResponse(statusCode, headers['retry-after']);
|
|
92
|
+
}
|
|
93
|
+
else if (HttpAdapter.isUnsupported(statusCode)) {
|
|
94
|
+
this.retryDownload(true, this.formatNgFlags(http2.constants.NGHTTP2_PROTOCOL_ERROR, statusCode));
|
|
95
|
+
}
|
|
96
|
+
else {
|
|
97
|
+
switch (flags) {
|
|
98
|
+
case http2.constants.NGHTTP2_PROTOCOL_ERROR:
|
|
99
|
+
case http2.constants.NGHTTP2_INADEQUATE_SECURITY:
|
|
100
|
+
case http2.constants.NGHTTP2_HTTP_1_1_REQUIRED:
|
|
101
|
+
this.retryDownload(true, this.formatNgFlags(flags, statusCode, headers.location));
|
|
102
|
+
break;
|
|
103
|
+
default:
|
|
104
|
+
this.retryDownload(false, this.formatStatus(statusCode));
|
|
105
|
+
break;
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
})
|
|
109
|
+
.on('unknownProtocol', () => {
|
|
110
|
+
if (this.aborted) {
|
|
111
|
+
return;
|
|
112
|
+
}
|
|
113
|
+
this.retryDownload(true, 'Unknown protocol (HTTP/2)');
|
|
114
|
+
})
|
|
115
|
+
.on('aborted', () => {
|
|
116
|
+
this.aborted = true;
|
|
117
|
+
this.terminate((0, types_1.createAbortError)());
|
|
118
|
+
})
|
|
119
|
+
.on('error', async (err) => {
|
|
120
|
+
if (this.aborted) {
|
|
121
|
+
return;
|
|
122
|
+
}
|
|
123
|
+
if (HttpAdapter.wasAborted(err)) {
|
|
124
|
+
this.errorResponse(err);
|
|
125
|
+
}
|
|
126
|
+
else {
|
|
127
|
+
switch (!HttpAdapter.isDowngrade(err) && await this.host.hasProtocol(2)) {
|
|
128
|
+
case 1:
|
|
129
|
+
this.errorResponse(err);
|
|
130
|
+
break;
|
|
131
|
+
case 2:
|
|
132
|
+
this.retryDownload(false, err);
|
|
133
|
+
break;
|
|
134
|
+
default:
|
|
135
|
+
this.retryDownload(true, err);
|
|
136
|
+
break;
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
});
|
|
140
|
+
}
|
|
141
|
+
else {
|
|
142
|
+
this.client
|
|
143
|
+
.on('response', res => {
|
|
144
|
+
if (this.destroyed) {
|
|
145
|
+
return;
|
|
146
|
+
}
|
|
147
|
+
const statusCode = res.statusCode;
|
|
148
|
+
if (statusCode < 300) {
|
|
149
|
+
this.acceptResponse(res.headers);
|
|
150
|
+
}
|
|
151
|
+
else if (statusCode < 400) {
|
|
152
|
+
this.redirectResponse(statusCode, res.headers.location);
|
|
153
|
+
}
|
|
154
|
+
else if (this.isRetry(statusCode)) {
|
|
155
|
+
this.retryResponse(statusCode, res.headers['retry-after']);
|
|
156
|
+
}
|
|
157
|
+
else {
|
|
158
|
+
this.terminate(this.formatStatus(statusCode));
|
|
159
|
+
}
|
|
160
|
+
})
|
|
161
|
+
.on('abort', () => {
|
|
162
|
+
this.aborted = true;
|
|
163
|
+
this.terminate((0, types_1.createAbortError)());
|
|
164
|
+
})
|
|
165
|
+
.on('error', err => {
|
|
166
|
+
if (this.aborted) {
|
|
167
|
+
return;
|
|
168
|
+
}
|
|
169
|
+
this.errorResponse(err);
|
|
170
|
+
});
|
|
171
|
+
}
|
|
172
|
+
this.client.on('timeout', () => {
|
|
173
|
+
if (this.aborted) {
|
|
174
|
+
return;
|
|
175
|
+
}
|
|
176
|
+
if (++this.retries <= this.retryLimit) {
|
|
177
|
+
this.abortResponse();
|
|
178
|
+
this.retryTimeout();
|
|
179
|
+
}
|
|
180
|
+
else {
|
|
181
|
+
this.terminate(this.formatStatus(408));
|
|
182
|
+
}
|
|
183
|
+
});
|
|
184
|
+
}
|
|
185
|
+
setOpts(uri) {
|
|
186
|
+
if (uri) {
|
|
187
|
+
this.uri = uri;
|
|
188
|
+
}
|
|
189
|
+
this.opts = this.instance.opts(this.uri, this[kOptions]);
|
|
190
|
+
}
|
|
191
|
+
setWriteStream() {
|
|
192
|
+
const pipeTo = this.pipeTo;
|
|
193
|
+
if (typeof pipeTo === 'string') {
|
|
194
|
+
try {
|
|
195
|
+
this.outStream = fs.createWriteStream(pipeTo, { emitClose: false, highWaterMark: this.host.streamSize });
|
|
196
|
+
}
|
|
197
|
+
catch (err) {
|
|
198
|
+
this.terminate(err);
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
else if (pipeTo) {
|
|
202
|
+
this.outStream = pipeTo;
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
retryDownload(downgrade, message) {
|
|
206
|
+
if (this.aborted) {
|
|
207
|
+
return;
|
|
208
|
+
}
|
|
209
|
+
this.abortResponse();
|
|
210
|
+
if (downgrade) {
|
|
211
|
+
const host = this.host;
|
|
212
|
+
host.failed(2);
|
|
213
|
+
if (host.version > 1) {
|
|
214
|
+
if (this.state.verbose) {
|
|
215
|
+
this.instance.formatMessage(1024, 'HTTP2', ["Unsupported protocol", host.origin], message, { failed: true });
|
|
216
|
+
}
|
|
217
|
+
host.version = 1;
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
this.opts.httpVersion = 1;
|
|
221
|
+
this.init();
|
|
222
|
+
}
|
|
223
|
+
acceptResponse(headers) {
|
|
224
|
+
const opts = this.opts;
|
|
225
|
+
if ('outHeaders' in opts) {
|
|
226
|
+
opts.outHeaders = headers;
|
|
227
|
+
}
|
|
228
|
+
if ('outFilename' in opts) {
|
|
229
|
+
opts.outFilename = (0, util_1.parseHeader)(headers, 'content-disposition');
|
|
230
|
+
}
|
|
231
|
+
const pipeline = this.pipeTo ? !(0, types_1.isString)(this.pipeTo) : false;
|
|
232
|
+
const enabled = opts.connected?.call(this.client, headers) !== false && !pipeline;
|
|
233
|
+
const maxBufferSize = opts.maxBufferSize ? (0, types_1.alignSize)(opts.maxBufferSize) : 0;
|
|
234
|
+
this.contentLength = parseInt(headers['content-length'] || '0');
|
|
235
|
+
const updating = opts.progressId !== undefined && !!this.instance.host && this.contentLength > 0;
|
|
236
|
+
const readTimeout = this.instance.readTimeout;
|
|
237
|
+
let log = this.state.log, buffer = null, dataLength = 0;
|
|
238
|
+
this.client.once('readable', () => {
|
|
239
|
+
if (readTimeout > 0) {
|
|
240
|
+
this.timeout = setTimeout(() => {
|
|
241
|
+
this.terminate((0, types_1.errorValue)("Timeout was exceeded", this.uri.toString()));
|
|
242
|
+
}, readTimeout);
|
|
243
|
+
}
|
|
244
|
+
if (log) {
|
|
245
|
+
switch (this.instance.settings?.time_format || LOG_TIMEFORMAT) {
|
|
246
|
+
case 'readable':
|
|
247
|
+
this.delayTime = process.hrtime.bigint() - this.startTime;
|
|
248
|
+
break;
|
|
249
|
+
case 'relative':
|
|
250
|
+
this.delayTime = Date.now() - this.instance.startTime;
|
|
251
|
+
break;
|
|
252
|
+
case 'none':
|
|
253
|
+
if (!this.silent) {
|
|
254
|
+
log = false;
|
|
255
|
+
}
|
|
256
|
+
break;
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
this.dataTime = process.hrtime.bigint();
|
|
260
|
+
if (updating) {
|
|
261
|
+
this.updateProgress(0, 0);
|
|
262
|
+
}
|
|
263
|
+
});
|
|
264
|
+
if (enabled) {
|
|
265
|
+
const encoding = opts.encoding;
|
|
266
|
+
this.client.on('data', (chunk) => {
|
|
267
|
+
if (buffer) {
|
|
268
|
+
if (typeof buffer === 'string') {
|
|
269
|
+
buffer += typeof chunk === 'string' ? chunk : chunk.toString(encoding);
|
|
270
|
+
}
|
|
271
|
+
else if (Array.isArray(buffer)) {
|
|
272
|
+
buffer.push(typeof chunk === 'string' ? Buffer.from(chunk, encoding) : chunk);
|
|
273
|
+
}
|
|
274
|
+
else {
|
|
275
|
+
buffer = Buffer.concat([buffer, typeof chunk === 'string' ? Buffer.from(chunk, encoding) : chunk]);
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
else {
|
|
279
|
+
buffer = typeof chunk === 'string' ? chunk : [chunk];
|
|
280
|
+
}
|
|
281
|
+
if (updating) {
|
|
282
|
+
dataLength += Buffer.byteLength(chunk, encoding);
|
|
283
|
+
this.updateProgress(dataLength, this.contentLength);
|
|
284
|
+
}
|
|
285
|
+
if (maxBufferSize > 0) {
|
|
286
|
+
if (!updating) {
|
|
287
|
+
dataLength += Buffer.byteLength(chunk, encoding);
|
|
288
|
+
}
|
|
289
|
+
if (dataLength > maxBufferSize) {
|
|
290
|
+
this.terminate((0, types_1.errorValue)("Size limit was exceeded", this.uri.toString()));
|
|
291
|
+
}
|
|
292
|
+
}
|
|
293
|
+
});
|
|
294
|
+
}
|
|
295
|
+
this.client.once('end', () => {
|
|
296
|
+
if (this.closed || this.aborted) {
|
|
297
|
+
return;
|
|
298
|
+
}
|
|
299
|
+
this.close();
|
|
300
|
+
const encoding = opts.encoding;
|
|
301
|
+
let result;
|
|
302
|
+
if (buffer) {
|
|
303
|
+
if (Array.isArray(buffer)) {
|
|
304
|
+
buffer = Buffer.concat(buffer);
|
|
305
|
+
if (encoding) {
|
|
306
|
+
buffer = buffer.toString(encoding);
|
|
307
|
+
}
|
|
308
|
+
}
|
|
309
|
+
dataLength = Buffer.byteLength(buffer, encoding);
|
|
310
|
+
if (updating) {
|
|
311
|
+
this.updateProgress(dataLength, dataLength);
|
|
312
|
+
}
|
|
313
|
+
if (typeof buffer === 'string') {
|
|
314
|
+
if (buffer.startsWith('\uFEFF') && encoding !== 'utf16le' && encoding !== 'utf-16le') {
|
|
315
|
+
buffer = buffer.substring(1);
|
|
316
|
+
}
|
|
317
|
+
if (opts.outFormat) {
|
|
318
|
+
const { out: format, parser } = opts.outFormat;
|
|
319
|
+
let packageName;
|
|
320
|
+
try {
|
|
321
|
+
switch (format) {
|
|
322
|
+
case 'yaml':
|
|
323
|
+
result = yaml.load(buffer, parser);
|
|
324
|
+
break;
|
|
325
|
+
case 'json5':
|
|
326
|
+
result = require(packageName = 'json5').parse(buffer);
|
|
327
|
+
break;
|
|
328
|
+
case 'xml':
|
|
329
|
+
result = new (require(packageName = 'fast-xml-parser').XMLParser)(parser).parse(buffer);
|
|
330
|
+
break;
|
|
331
|
+
case 'toml':
|
|
332
|
+
result = require(packageName = 'toml').parse(buffer);
|
|
333
|
+
break;
|
|
334
|
+
default:
|
|
335
|
+
result = JSON.parse(buffer);
|
|
336
|
+
break;
|
|
337
|
+
}
|
|
338
|
+
if (!(0, types_1.isObject)(result)) {
|
|
339
|
+
result = null;
|
|
340
|
+
}
|
|
341
|
+
}
|
|
342
|
+
catch (err) {
|
|
343
|
+
if (this.state.verbose && !(packageName && this.instance.checkPackage(err, packageName))) {
|
|
344
|
+
this.instance.writeFail(["Unable to parse URI response", format], err, 1024);
|
|
345
|
+
}
|
|
346
|
+
result = null;
|
|
347
|
+
}
|
|
348
|
+
}
|
|
349
|
+
}
|
|
350
|
+
if (result === undefined) {
|
|
351
|
+
result = buffer;
|
|
352
|
+
}
|
|
353
|
+
}
|
|
354
|
+
else {
|
|
355
|
+
if (updating) {
|
|
356
|
+
this.updateProgress(0, this.contentLength);
|
|
357
|
+
}
|
|
358
|
+
if (enabled && this.instance.readExpect === 'always') {
|
|
359
|
+
this.terminate("No data received");
|
|
360
|
+
return;
|
|
361
|
+
}
|
|
362
|
+
result = encoding && !pipeline ? '' : null;
|
|
363
|
+
}
|
|
364
|
+
this.endResponse(result, dataLength, log);
|
|
365
|
+
});
|
|
366
|
+
this.host.success(this.httpVersion);
|
|
367
|
+
}
|
|
368
|
+
updateProgress(dataLength, contentLength) {
|
|
369
|
+
const { host, moduleName } = this.instance;
|
|
370
|
+
host.updateProgress(moduleName, this.opts.progressId, dataLength, contentLength, this.dataTime);
|
|
371
|
+
}
|
|
372
|
+
endResponse(result, dataLength = 0, logging = this.state.log) {
|
|
373
|
+
if (logging) {
|
|
374
|
+
const messageUnit = this.dataTime && dataLength > 0 ? (0, util_1.getTransferRate)(dataLength, (0, types_1.convertTime)(process.hrtime.bigint() - this.dataTime, false) * 1000) : undefined;
|
|
375
|
+
this.instance.writeTimeProcess('HTTP' + this.httpVersion, this.opts.statusMessage || this.uri.toString(), this.startTime, {
|
|
376
|
+
type: 1024,
|
|
377
|
+
queue: !!this.instance.host,
|
|
378
|
+
titleBgColor: !result ? 'bgBlue' : undefined,
|
|
379
|
+
messageUnit,
|
|
380
|
+
messageUnitMinWidth: 9,
|
|
381
|
+
delayTime: this.delayTime,
|
|
382
|
+
bypassLog: module_1.hasLogType(32768)
|
|
383
|
+
});
|
|
384
|
+
}
|
|
385
|
+
this.resolve(result);
|
|
386
|
+
}
|
|
387
|
+
redirectResponse(statusCode, location) {
|
|
388
|
+
if (location) {
|
|
389
|
+
if (this.opts.followRedirect === false) {
|
|
390
|
+
this.terminate(this.formatStatus(statusCode, "Follow redirect was disabled"));
|
|
391
|
+
}
|
|
392
|
+
else if (++this.redirects <= this.redirectLimit) {
|
|
393
|
+
this.abortResponse();
|
|
394
|
+
this.setOpts((0, util_1.fromURL)(this.opts.url, location));
|
|
395
|
+
this.init();
|
|
396
|
+
}
|
|
397
|
+
else {
|
|
398
|
+
this.terminate(this.formatStatus(statusCode, "Exceeded redirect limit"));
|
|
399
|
+
}
|
|
400
|
+
}
|
|
401
|
+
else {
|
|
402
|
+
this.terminate(this.formatStatus(statusCode, "Missing redirect location"));
|
|
403
|
+
}
|
|
404
|
+
}
|
|
405
|
+
abortResponse() {
|
|
406
|
+
if (this.closed) {
|
|
407
|
+
return;
|
|
408
|
+
}
|
|
409
|
+
this.aborted = true;
|
|
410
|
+
this.client.destroy();
|
|
411
|
+
this.instance.reset(this);
|
|
412
|
+
this.cleanup();
|
|
413
|
+
}
|
|
414
|
+
errorResponse(err) {
|
|
415
|
+
if (HttpAdapter.wasAborted(err)) {
|
|
416
|
+
this.terminate(err);
|
|
417
|
+
}
|
|
418
|
+
else if ((0, util_1.checkRetryable)(err) && ++this.retries <= this.retryLimit) {
|
|
419
|
+
this.abortResponse();
|
|
420
|
+
if (HttpAdapter.isConnectionError(err)) {
|
|
421
|
+
this.retryTimeout();
|
|
422
|
+
}
|
|
423
|
+
else {
|
|
424
|
+
setTimeout(() => {
|
|
425
|
+
this.init();
|
|
426
|
+
}, this.retryWait);
|
|
427
|
+
}
|
|
428
|
+
}
|
|
429
|
+
else {
|
|
430
|
+
this.host.error(this.httpVersion);
|
|
431
|
+
this.terminate(err);
|
|
432
|
+
}
|
|
433
|
+
}
|
|
434
|
+
retryResponse(statusCode, retryAfter) {
|
|
435
|
+
this.abortResponse();
|
|
436
|
+
if (retryAfter && this.retryAfter > 0) {
|
|
437
|
+
let offset = +retryAfter || new Date(retryAfter);
|
|
438
|
+
if (offset instanceof Date) {
|
|
439
|
+
offset = Math.max(0, offset.getTime() - Date.now());
|
|
440
|
+
}
|
|
441
|
+
else {
|
|
442
|
+
offset *= 1000;
|
|
443
|
+
}
|
|
444
|
+
if (offset > 0) {
|
|
445
|
+
if (offset <= this.retryAfter) {
|
|
446
|
+
this.sendWarning(`Retry After (${retryAfter})`);
|
|
447
|
+
setTimeout(() => {
|
|
448
|
+
this.init();
|
|
449
|
+
}, offset);
|
|
450
|
+
}
|
|
451
|
+
else {
|
|
452
|
+
this.terminate(this.formatStatus(statusCode));
|
|
453
|
+
}
|
|
454
|
+
return;
|
|
455
|
+
}
|
|
456
|
+
}
|
|
457
|
+
this.sendWarning(this.formatRetry((0, util_1.fromStatusCode)(statusCode)));
|
|
458
|
+
if ((0, util_1.isRetryable)(statusCode, true)) {
|
|
459
|
+
setImmediate(this.init.bind(this));
|
|
460
|
+
}
|
|
461
|
+
else {
|
|
462
|
+
setTimeout(() => {
|
|
463
|
+
this.init();
|
|
464
|
+
}, this.retryWait);
|
|
465
|
+
}
|
|
466
|
+
}
|
|
467
|
+
isRetry(value) {
|
|
468
|
+
return (0, util_1.isRetryable)(value) && ++this.retries <= this.retryLimit;
|
|
469
|
+
}
|
|
470
|
+
retryTimeout() {
|
|
471
|
+
this.sendWarning(this.formatRetry("HTTP connection timeout"));
|
|
472
|
+
this.init();
|
|
473
|
+
}
|
|
474
|
+
terminate(err) {
|
|
475
|
+
if (this.closed) {
|
|
476
|
+
return;
|
|
477
|
+
}
|
|
478
|
+
this.cleanup();
|
|
479
|
+
this.close();
|
|
480
|
+
this.reject(typeof err === 'string' ? new Error(err) : err);
|
|
481
|
+
}
|
|
482
|
+
sendWarning(message) {
|
|
483
|
+
if (this.state.verbose) {
|
|
484
|
+
const { host, url } = this.opts;
|
|
485
|
+
this.instance.formatMessage(1024, 'HTTP' + this.httpVersion, [message, host.origin], url.toString(), { ...module_1.LOG_STYLE_WARN });
|
|
486
|
+
}
|
|
487
|
+
}
|
|
488
|
+
formatStatus(value, hint) {
|
|
489
|
+
return value + ': ' + (hint || (0, util_1.fromStatusCode)(value)) + ` (${this.uri.toString()})`;
|
|
490
|
+
}
|
|
491
|
+
close() {
|
|
492
|
+
this.closed = true;
|
|
493
|
+
this.instance.reset(this);
|
|
494
|
+
if (this.aborted && !this.client.aborted) {
|
|
495
|
+
this.abortController?.abort();
|
|
496
|
+
}
|
|
497
|
+
}
|
|
498
|
+
cleanup() {
|
|
499
|
+
if ((0, types_1.isString)(this.pipeTo) && this.outStream) {
|
|
500
|
+
(0, util_1.cleanupStream)(this.outStream, this.pipeTo);
|
|
501
|
+
this.outStream = null;
|
|
502
|
+
}
|
|
503
|
+
}
|
|
504
|
+
formatNgFlags(value, statusCode, location) {
|
|
505
|
+
return location ? `Using HTTP 1.1 for URL redirect (${location})` : this.formatStatus(statusCode, value ? 'NGHTTP2 Error ' + value : '');
|
|
506
|
+
}
|
|
507
|
+
formatRetry(message) {
|
|
508
|
+
return `${message} (${this.retries} / ${this.retryLimit})`;
|
|
509
|
+
}
|
|
510
|
+
set abortController(value) {
|
|
511
|
+
this.opts.outAbort = value;
|
|
512
|
+
}
|
|
513
|
+
get abortController() {
|
|
514
|
+
return this.opts.outAbort || null;
|
|
515
|
+
}
|
|
516
|
+
set outStream(value) {
|
|
517
|
+
this[kOutStream] = value;
|
|
518
|
+
if (value) {
|
|
519
|
+
this.opts.outStream = value;
|
|
520
|
+
}
|
|
521
|
+
}
|
|
522
|
+
get outStream() {
|
|
523
|
+
return this[kOutStream];
|
|
524
|
+
}
|
|
525
|
+
get destroyed() {
|
|
526
|
+
return this.client.destroyed || this.httpVersion === 2 && this.client.aborted;
|
|
527
|
+
}
|
|
528
|
+
get host() {
|
|
529
|
+
return this.opts.host;
|
|
530
|
+
}
|
|
531
|
+
get httpVersion() {
|
|
532
|
+
return this.opts.httpVersion;
|
|
533
|
+
}
|
|
534
|
+
get pipeTo() {
|
|
535
|
+
return this.opts.pipeTo;
|
|
536
|
+
}
|
|
537
|
+
get silent() {
|
|
538
|
+
return this.opts.silent === false;
|
|
539
|
+
}
|
|
540
|
+
get retryLimit() {
|
|
541
|
+
return this.state.config.retryLimit;
|
|
542
|
+
}
|
|
543
|
+
get retryWait() {
|
|
544
|
+
return this.state.config.retryWait;
|
|
545
|
+
}
|
|
546
|
+
get retryAfter() {
|
|
547
|
+
return this.state.config.retryAfter;
|
|
548
|
+
}
|
|
549
|
+
get redirectLimit() {
|
|
550
|
+
return this.state.config.redirectLimit;
|
|
551
|
+
}
|
|
552
|
+
}
|
|
553
|
+
_a = kOutStream;
|
|
554
|
+
module.exports = HttpAdapter;
|
package/index.js
CHANGED
|
@@ -384,7 +384,7 @@ class Request extends module_1.default {
|
|
|
384
384
|
if ((lowest_speed_limit = parseSize(lowest_speed_limit, true)) !== undefined) {
|
|
385
385
|
ARIA2.LOWEST_SPEED_LIMIT = lowest_speed_limit;
|
|
386
386
|
}
|
|
387
|
-
if (always_resume) {
|
|
387
|
+
if (typeof always_resume === 'boolean') {
|
|
388
388
|
ARIA2.ALWAYS_RESUME = always_resume;
|
|
389
389
|
}
|
|
390
390
|
if ((0, types_1.isString)(file_allocation)) {
|
|
@@ -398,7 +398,7 @@ class Request extends module_1.default {
|
|
|
398
398
|
break;
|
|
399
399
|
}
|
|
400
400
|
}
|
|
401
|
-
if (conf_path && this.isPath(conf_path = path.resolve(conf_path))) {
|
|
401
|
+
if (conf_path === '' || (0, types_1.isString)(conf_path) && this.isPath(conf_path = path.resolve(conf_path))) {
|
|
402
402
|
ARIA2.CONF_PATH = conf_path;
|
|
403
403
|
}
|
|
404
404
|
}
|
|
@@ -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) => {
|
|
@@ -1026,7 +1027,7 @@ class Request extends module_1.default {
|
|
|
1026
1027
|
case 'string': {
|
|
1027
1028
|
const value = opt.trim();
|
|
1028
1029
|
if (value.startsWith('--')) {
|
|
1029
|
-
const match = /^(--[a-z]+[a-z0-9-]*)(=)?\s*(.*)
|
|
1030
|
+
const match = /^(--[a-z]+[a-z0-9-]*)(=)?\s*(.*)$/s.exec(value);
|
|
1030
1031
|
if (match) {
|
|
1031
1032
|
switch (match[1]) {
|
|
1032
1033
|
case '--daemon':
|
|
@@ -1082,8 +1083,8 @@ class Request extends module_1.default {
|
|
|
1082
1083
|
({ protocol, origin, username, password, href: uri } = uri);
|
|
1083
1084
|
}
|
|
1084
1085
|
const escapeQuote = (value) => {
|
|
1085
|
-
value = value.replace(/"/g, '\\"');
|
|
1086
|
-
return PLATFORM_WIN32 ? value : value.replace(/
|
|
1086
|
+
value = value.replace(/(?<!\\)"/g, '\\"');
|
|
1087
|
+
return PLATFORM_WIN32 ? value : value.replace(/(?<!\\)\$/g, '\\$');
|
|
1087
1088
|
};
|
|
1088
1089
|
const init = [
|
|
1089
1090
|
`--dir="${escapeQuote(pathname)}"`,
|
|
@@ -1228,10 +1229,13 @@ class Request extends module_1.default {
|
|
|
1228
1229
|
}
|
|
1229
1230
|
if (binOpts) {
|
|
1230
1231
|
for (const leading of binOpts) {
|
|
1231
|
-
|
|
1232
|
-
|
|
1233
|
-
|
|
1234
|
-
|
|
1232
|
+
if (leading.startsWith('-')) {
|
|
1233
|
+
const pattern = new RegExp(`^${leading}(?:=|$)`);
|
|
1234
|
+
for (let i = 0; i < opts.length; ++i) {
|
|
1235
|
+
if (pattern.test(opts[i])) {
|
|
1236
|
+
opts.splice(i--, i);
|
|
1237
|
+
break;
|
|
1238
|
+
}
|
|
1235
1239
|
}
|
|
1236
1240
|
}
|
|
1237
1241
|
}
|
|
@@ -1243,18 +1247,14 @@ class Request extends module_1.default {
|
|
|
1243
1247
|
opts.push(`"${escapeQuote(uri)}"`);
|
|
1244
1248
|
args = args.concat(init, opts);
|
|
1245
1249
|
const startTime = Date.now();
|
|
1246
|
-
let out = '', message = ''
|
|
1250
|
+
let out = '', message = '';
|
|
1247
1251
|
const errorResponse = (pid, err) => {
|
|
1248
|
-
aborted = true;
|
|
1249
1252
|
closeTorrent(pid);
|
|
1250
1253
|
reject(err);
|
|
1251
1254
|
};
|
|
1252
1255
|
const { pid, stdout, stderr } = child_process.spawn(module_1.default.sanitizeCmd(ARIA2.BIN), args, { cwd: pathname, shell: true, signal: this.signal, uid: ARIA2.EXEC_UID, gid: ARIA2.EXEC_GID })
|
|
1253
1256
|
.on('exit', code => {
|
|
1254
1257
|
closeTorrent(pid);
|
|
1255
|
-
if (aborted) {
|
|
1256
|
-
return;
|
|
1257
|
-
}
|
|
1258
1258
|
if (this.aborted) {
|
|
1259
1259
|
errorResponse(pid, (0, types_1.createAbortError)());
|
|
1260
1260
|
return;
|
|
@@ -1285,10 +1285,15 @@ class Request extends module_1.default {
|
|
|
1285
1285
|
break;
|
|
1286
1286
|
}
|
|
1287
1287
|
}
|
|
1288
|
-
if (result.length
|
|
1289
|
-
|
|
1288
|
+
if (result.length > 0) {
|
|
1289
|
+
if (!silent && LOG_HTTP && LOG_TIMEPROCESS) {
|
|
1290
|
+
this.writeTimeProcess("aria2", uri, startTime, { type: 1024, queue: true, messageUnit, messageUnitMinWidth: 9, bypassLog: true });
|
|
1291
|
+
}
|
|
1292
|
+
this.addLog(types_1.STATUS_TYPE.INFO, out, currentTime, currentTime - startTime, "aria2", uri);
|
|
1293
|
+
}
|
|
1294
|
+
else {
|
|
1295
|
+
this.addLog(types_1.STATUS_TYPE.ERROR, 'No files were successfully downloaded', currentTime, currentTime - startTime, "aria2", uri);
|
|
1290
1296
|
}
|
|
1291
|
-
this.addLog(result.length ? types_1.STATUS_TYPE.INFO : types_1.STATUS_TYPE.ERROR, out, currentTime, currentTime - startTime, "aria2", uri);
|
|
1292
1297
|
resolve(result);
|
|
1293
1298
|
}
|
|
1294
1299
|
else {
|
|
@@ -1311,7 +1316,13 @@ class Request extends module_1.default {
|
|
|
1311
1316
|
}
|
|
1312
1317
|
for (const item of ARIA2.PID_QUEUE) {
|
|
1313
1318
|
try {
|
|
1314
|
-
|
|
1319
|
+
if (pid === item[0] && (signal === null || signal === void 0 ? void 0 : signal.aborted)) {
|
|
1320
|
+
process.kill(item[0]);
|
|
1321
|
+
closeTorrent(item[0]);
|
|
1322
|
+
}
|
|
1323
|
+
else {
|
|
1324
|
+
process.kill(item[0], 0);
|
|
1325
|
+
}
|
|
1315
1326
|
}
|
|
1316
1327
|
catch {
|
|
1317
1328
|
closeTorrent(item[0]);
|
|
@@ -1369,7 +1380,7 @@ class Request extends module_1.default {
|
|
|
1369
1380
|
open(uri, options) {
|
|
1370
1381
|
var _l;
|
|
1371
1382
|
var _m, _o;
|
|
1372
|
-
let { host, url, httpVersion, method = 'GET', encoding, format, headers, postData, keepAlive, agentTimeout, socketPath, timeout = this._config.connectTimeout, outStream } = options;
|
|
1383
|
+
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);
|
|
1373
1384
|
const getting = method === 'GET';
|
|
1374
1385
|
const posting = method === 'POST';
|
|
1375
1386
|
if (format) {
|
|
@@ -1624,6 +1635,9 @@ class Request extends module_1.default {
|
|
|
1624
1635
|
const ac = new AbortController();
|
|
1625
1636
|
this[kDownloading].add(options.outAbort = ac);
|
|
1626
1637
|
stream.addAbortSignal(ac.signal, request);
|
|
1638
|
+
if (options.signal) {
|
|
1639
|
+
stream.addAbortSignal(options.signal, request);
|
|
1640
|
+
}
|
|
1627
1641
|
this.signal.addEventListener('abort', () => ac.abort(), { once: true });
|
|
1628
1642
|
}
|
|
1629
1643
|
if (posting) {
|
|
@@ -1673,7 +1687,7 @@ class Request extends module_1.default {
|
|
|
1673
1687
|
else {
|
|
1674
1688
|
options = {};
|
|
1675
1689
|
}
|
|
1676
|
-
const headers = options.headers || (options.headers = {});
|
|
1690
|
+
const headers = (0, util_1.parseOutgoingHeaders)(options.headers || (options.headers = {}));
|
|
1677
1691
|
for (const attr in headers) {
|
|
1678
1692
|
const name = attr.toLowerCase();
|
|
1679
1693
|
if (name === 'content-type' || name === 'content-length') {
|
|
@@ -1836,7 +1850,7 @@ class Request extends module_1.default {
|
|
|
1836
1850
|
}
|
|
1837
1851
|
const client = this.open(href, request);
|
|
1838
1852
|
const { host, url, encoding, outFormat } = request;
|
|
1839
|
-
let buffer, aborted;
|
|
1853
|
+
let buffer, aborted = false;
|
|
1840
1854
|
({ httpVersion, outAbort } = request);
|
|
1841
1855
|
const isAborted = () => client.destroyed || httpVersion === 2 && client.aborted;
|
|
1842
1856
|
const isRetry = (value) => (0, util_1.isRetryable)(value) && ++retries <= this._config.retryLimit;
|
|
@@ -1851,14 +1865,11 @@ class Request extends module_1.default {
|
|
|
1851
1865
|
if (timeout) {
|
|
1852
1866
|
clearTimeout(timeout);
|
|
1853
1867
|
}
|
|
1854
|
-
buffer = null;
|
|
1855
|
-
aborted = true;
|
|
1856
1868
|
if (outAbort) {
|
|
1857
|
-
if (!client.aborted) {
|
|
1858
|
-
outAbort.abort();
|
|
1859
|
-
}
|
|
1860
1869
|
this[kDownloading].delete(outAbort);
|
|
1861
1870
|
}
|
|
1871
|
+
buffer = null;
|
|
1872
|
+
aborted = true;
|
|
1862
1873
|
client.destroy();
|
|
1863
1874
|
};
|
|
1864
1875
|
const retryTimeout = () => {
|
|
@@ -1883,7 +1894,6 @@ class Request extends module_1.default {
|
|
|
1883
1894
|
var _l;
|
|
1884
1895
|
if (readTimeout > 0) {
|
|
1885
1896
|
timeout = setTimeout(() => {
|
|
1886
|
-
abortResponse();
|
|
1887
1897
|
throwError((0, types_1.errorValue)("Timeout was exceeded", href.toString()));
|
|
1888
1898
|
}, readTimeout);
|
|
1889
1899
|
}
|
|
@@ -2012,9 +2022,9 @@ class Request extends module_1.default {
|
|
|
2012
2022
|
host.success(httpVersion);
|
|
2013
2023
|
};
|
|
2014
2024
|
const redirectResponse = (statusCode, location) => {
|
|
2015
|
-
abortResponse();
|
|
2016
2025
|
if (location) {
|
|
2017
2026
|
if (++redirects <= this._config.redirectLimit) {
|
|
2027
|
+
abortResponse();
|
|
2018
2028
|
downloadUri.call(this, Request.fromURL(url, location));
|
|
2019
2029
|
}
|
|
2020
2030
|
else {
|
|
@@ -2026,8 +2036,8 @@ class Request extends module_1.default {
|
|
|
2026
2036
|
}
|
|
2027
2037
|
};
|
|
2028
2038
|
const errorResponse = (err) => {
|
|
2029
|
-
abortResponse();
|
|
2030
2039
|
if ((0, util_1.checkRetryable)(err) && ++retries <= this._config.retryLimit) {
|
|
2040
|
+
abortResponse();
|
|
2031
2041
|
if (isConnectionTimeout(err)) {
|
|
2032
2042
|
retryTimeout();
|
|
2033
2043
|
}
|
|
@@ -2170,7 +2180,6 @@ class Request extends module_1.default {
|
|
|
2170
2180
|
retryResponse(statusCode, res.headers['retry-after']);
|
|
2171
2181
|
}
|
|
2172
2182
|
else {
|
|
2173
|
-
abortResponse();
|
|
2174
2183
|
throwError(formatStatus(statusCode));
|
|
2175
2184
|
}
|
|
2176
2185
|
})
|
|
@@ -2188,8 +2197,8 @@ class Request extends module_1.default {
|
|
|
2188
2197
|
if (aborted) {
|
|
2189
2198
|
return;
|
|
2190
2199
|
}
|
|
2191
|
-
abortResponse();
|
|
2192
2200
|
if (++retries <= this._config.retryLimit) {
|
|
2201
|
+
abortResponse();
|
|
2193
2202
|
retryTimeout();
|
|
2194
2203
|
}
|
|
2195
2204
|
else {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@e-mc/request",
|
|
3
|
-
"version": "0.6.
|
|
3
|
+
"version": "0.6.15",
|
|
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.6.
|
|
24
|
-
"@e-mc/types": "0.6.
|
|
23
|
+
"@e-mc/module": "0.6.15",
|
|
24
|
+
"@e-mc/types": "0.6.15",
|
|
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;
|