@e-mc/request 0.5.15 → 0.5.17
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 +3 -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
|
@@ -366,7 +366,7 @@ class Request extends module_1.default {
|
|
|
366
366
|
if ((lowest_speed_limit = parseSize(lowest_speed_limit, true)) !== undefined) {
|
|
367
367
|
ARIA2.LOWEST_SPEED_LIMIT = lowest_speed_limit;
|
|
368
368
|
}
|
|
369
|
-
if (always_resume) {
|
|
369
|
+
if (typeof always_resume === 'boolean') {
|
|
370
370
|
ARIA2.ALWAYS_RESUME = always_resume;
|
|
371
371
|
}
|
|
372
372
|
if ((0, types_1.isString)(file_allocation)) {
|
|
@@ -380,7 +380,7 @@ class Request extends module_1.default {
|
|
|
380
380
|
break;
|
|
381
381
|
}
|
|
382
382
|
}
|
|
383
|
-
if (conf_path && this.isPath(conf_path = path.resolve(conf_path))) {
|
|
383
|
+
if (conf_path === '' || (0, types_1.isString)(conf_path) && this.isPath(conf_path = path.resolve(conf_path))) {
|
|
384
384
|
ARIA2.CONF_PATH = conf_path;
|
|
385
385
|
}
|
|
386
386
|
}
|
|
@@ -975,13 +975,14 @@ class Request extends module_1.default {
|
|
|
975
975
|
return Promise.reject(err);
|
|
976
976
|
}
|
|
977
977
|
}
|
|
978
|
-
let pathname, headers, binOpts, silent;
|
|
978
|
+
let pathname, headers, binOpts, signal, silent;
|
|
979
979
|
if (options) {
|
|
980
980
|
if (typeof options === 'string') {
|
|
981
981
|
pathname = options;
|
|
982
982
|
}
|
|
983
983
|
else {
|
|
984
|
-
({ pathname,
|
|
984
|
+
({ pathname, binOpts, signal, silent } = options);
|
|
985
|
+
headers = (0, util_1.parseOutgoingHeaders)(options.headers);
|
|
985
986
|
if ((0, types_1.isArray)(binOpts)) {
|
|
986
987
|
let next = false;
|
|
987
988
|
binOpts = binOpts.filter(opt => !((0, types_1.isString)(opt) && /^-[a-z][\S\s]*$/i.test(opt.trim()))).map((opt) => {
|
|
@@ -995,7 +996,7 @@ class Request extends module_1.default {
|
|
|
995
996
|
case 'string': {
|
|
996
997
|
const value = opt.trim();
|
|
997
998
|
if (value.startsWith('--')) {
|
|
998
|
-
const match = /^(--[a-z]+[a-z0-9-]*)(=)?\s*(.*)
|
|
999
|
+
const match = /^(--[a-z]+[a-z0-9-]*)(=)?\s*(.*)$/s.exec(value);
|
|
999
1000
|
if (match) {
|
|
1000
1001
|
switch (match[1]) {
|
|
1001
1002
|
case '--daemon':
|
|
@@ -1051,8 +1052,8 @@ class Request extends module_1.default {
|
|
|
1051
1052
|
({ protocol, origin, username, password, href: uri } = uri);
|
|
1052
1053
|
}
|
|
1053
1054
|
const escapeQuote = (value) => {
|
|
1054
|
-
value = value.replace(/"/g, '\\"');
|
|
1055
|
-
return PLATFORM_WIN32 ? value : value.replace(/
|
|
1055
|
+
value = value.replace(/(?<!\\)"/g, '\\"');
|
|
1056
|
+
return PLATFORM_WIN32 ? value : value.replace(/(?<!\\)\$/g, '\\$');
|
|
1056
1057
|
};
|
|
1057
1058
|
const init = [
|
|
1058
1059
|
`--dir="${escapeQuote(pathname)}"`,
|
|
@@ -1197,10 +1198,13 @@ class Request extends module_1.default {
|
|
|
1197
1198
|
}
|
|
1198
1199
|
if (binOpts) {
|
|
1199
1200
|
for (const leading of binOpts) {
|
|
1200
|
-
|
|
1201
|
-
|
|
1202
|
-
|
|
1203
|
-
|
|
1201
|
+
if (leading.startsWith('-')) {
|
|
1202
|
+
const pattern = new RegExp(`^${leading}(?:=|$)`);
|
|
1203
|
+
for (let i = 0; i < opts.length; ++i) {
|
|
1204
|
+
if (pattern.test(opts[i])) {
|
|
1205
|
+
opts.splice(i--, i);
|
|
1206
|
+
break;
|
|
1207
|
+
}
|
|
1204
1208
|
}
|
|
1205
1209
|
}
|
|
1206
1210
|
}
|
|
@@ -1212,18 +1216,14 @@ class Request extends module_1.default {
|
|
|
1212
1216
|
opts.push(`"${escapeQuote(uri)}"`);
|
|
1213
1217
|
args = args.concat(init, opts);
|
|
1214
1218
|
const startTime = Date.now();
|
|
1215
|
-
let out = '', message = ''
|
|
1219
|
+
let out = '', message = '';
|
|
1216
1220
|
const errorResponse = (pid, err) => {
|
|
1217
|
-
aborted = true;
|
|
1218
1221
|
closeTorrent(pid);
|
|
1219
1222
|
reject(err);
|
|
1220
1223
|
};
|
|
1221
1224
|
const { pid, stdout, stderr } = child_process.spawn(module_1.default.sanitizeCmd(ARIA2.BIN), args, { cwd: pathname, shell: true, signal: this.signal })
|
|
1222
1225
|
.on('exit', code => {
|
|
1223
1226
|
closeTorrent(pid);
|
|
1224
|
-
if (aborted) {
|
|
1225
|
-
return;
|
|
1226
|
-
}
|
|
1227
1227
|
if (this.aborted) {
|
|
1228
1228
|
errorResponse(pid, (0, types_1.createAbortError)());
|
|
1229
1229
|
return;
|
|
@@ -1254,10 +1254,15 @@ class Request extends module_1.default {
|
|
|
1254
1254
|
break;
|
|
1255
1255
|
}
|
|
1256
1256
|
}
|
|
1257
|
-
if (result.length
|
|
1258
|
-
|
|
1257
|
+
if (result.length > 0) {
|
|
1258
|
+
if (!silent && LOG_HTTP && LOG_TIMEPROCESS) {
|
|
1259
|
+
this.writeTimeProcess("aria2", uri, startTime, { type: 1024, queue: true, messageUnit, messageUnitMinWidth: 9, bypassLog: true });
|
|
1260
|
+
}
|
|
1261
|
+
this.addLog(types_1.STATUS_TYPE.INFO, out, currentTime, currentTime - startTime, "aria2", uri);
|
|
1262
|
+
}
|
|
1263
|
+
else {
|
|
1264
|
+
this.addLog(types_1.STATUS_TYPE.ERROR, 'No files were successfully downloaded', currentTime, currentTime - startTime, "aria2", uri);
|
|
1259
1265
|
}
|
|
1260
|
-
this.addLog(result.length ? types_1.STATUS_TYPE.INFO : types_1.STATUS_TYPE.ERROR, out, currentTime, currentTime - startTime, "aria2", uri);
|
|
1261
1266
|
resolve(result);
|
|
1262
1267
|
}
|
|
1263
1268
|
else {
|
|
@@ -1280,7 +1285,13 @@ class Request extends module_1.default {
|
|
|
1280
1285
|
}
|
|
1281
1286
|
for (const item of ARIA2.PID_QUEUE) {
|
|
1282
1287
|
try {
|
|
1283
|
-
|
|
1288
|
+
if (pid === item[0] && (signal === null || signal === void 0 ? void 0 : signal.aborted)) {
|
|
1289
|
+
process.kill(item[0]);
|
|
1290
|
+
closeTorrent(item[0]);
|
|
1291
|
+
}
|
|
1292
|
+
else {
|
|
1293
|
+
process.kill(item[0], 0);
|
|
1294
|
+
}
|
|
1284
1295
|
}
|
|
1285
1296
|
catch {
|
|
1286
1297
|
closeTorrent(item[0]);
|
|
@@ -1338,7 +1349,7 @@ class Request extends module_1.default {
|
|
|
1338
1349
|
open(uri, options) {
|
|
1339
1350
|
var _l;
|
|
1340
1351
|
var _m, _o;
|
|
1341
|
-
let { host, url, httpVersion, method = 'GET', encoding, format, headers, postData, keepAlive, agentTimeout, socketPath, timeout = this._config.connectTimeout, outStream } = options;
|
|
1352
|
+
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);
|
|
1342
1353
|
const getting = method === 'GET';
|
|
1343
1354
|
const posting = method === 'POST';
|
|
1344
1355
|
if (format) {
|
|
@@ -1585,6 +1596,9 @@ class Request extends module_1.default {
|
|
|
1585
1596
|
const ac = new AbortController();
|
|
1586
1597
|
this[kDownloading].add(options.outAbort = ac);
|
|
1587
1598
|
stream.addAbortSignal(ac.signal, request);
|
|
1599
|
+
if (options.signal) {
|
|
1600
|
+
stream.addAbortSignal(options.signal, request);
|
|
1601
|
+
}
|
|
1588
1602
|
this.signal.addEventListener('abort', () => ac.abort(), { once: true });
|
|
1589
1603
|
}
|
|
1590
1604
|
if (posting) {
|
|
@@ -1634,7 +1648,7 @@ class Request extends module_1.default {
|
|
|
1634
1648
|
else {
|
|
1635
1649
|
options = {};
|
|
1636
1650
|
}
|
|
1637
|
-
const headers = options.headers || (options.headers = {});
|
|
1651
|
+
const headers = (0, util_1.parseOutgoingHeaders)(options.headers || (options.headers = {}));
|
|
1638
1652
|
for (const attr in headers) {
|
|
1639
1653
|
switch (attr.toLowerCase()) {
|
|
1640
1654
|
case 'content-type':
|
|
@@ -1800,7 +1814,7 @@ class Request extends module_1.default {
|
|
|
1800
1814
|
}
|
|
1801
1815
|
const client = this.open(href, request);
|
|
1802
1816
|
const { host, url, encoding, outFormat } = request;
|
|
1803
|
-
let buffer, aborted;
|
|
1817
|
+
let buffer, aborted = false;
|
|
1804
1818
|
({ httpVersion, outAbort } = request);
|
|
1805
1819
|
const isAborted = () => client.destroyed || httpVersion === 2 && client.aborted;
|
|
1806
1820
|
const isRetry = (value) => (0, util_1.isRetryable)(value) && ++retries <= this._config.retryLimit;
|
|
@@ -1815,14 +1829,11 @@ class Request extends module_1.default {
|
|
|
1815
1829
|
if (timeout) {
|
|
1816
1830
|
clearTimeout(timeout);
|
|
1817
1831
|
}
|
|
1818
|
-
buffer = null;
|
|
1819
|
-
aborted = true;
|
|
1820
1832
|
if (outAbort) {
|
|
1821
|
-
if (!client.aborted) {
|
|
1822
|
-
outAbort.abort();
|
|
1823
|
-
}
|
|
1824
1833
|
this[kDownloading].delete(outAbort);
|
|
1825
1834
|
}
|
|
1835
|
+
buffer = null;
|
|
1836
|
+
aborted = true;
|
|
1826
1837
|
client.destroy();
|
|
1827
1838
|
};
|
|
1828
1839
|
const retryTimeout = () => {
|
|
@@ -1846,7 +1857,6 @@ class Request extends module_1.default {
|
|
|
1846
1857
|
var _l;
|
|
1847
1858
|
if (this.readTimeout > 0) {
|
|
1848
1859
|
timeout = setTimeout(() => {
|
|
1849
|
-
abortResponse();
|
|
1850
1860
|
throwError((0, types_1.errorValue)("Timeout was exceeded", href.toString()));
|
|
1851
1861
|
}, this.readTimeout);
|
|
1852
1862
|
}
|
|
@@ -1975,9 +1985,9 @@ class Request extends module_1.default {
|
|
|
1975
1985
|
host.success(httpVersion);
|
|
1976
1986
|
};
|
|
1977
1987
|
const redirectResponse = (statusCode, location) => {
|
|
1978
|
-
abortResponse();
|
|
1979
1988
|
if (location) {
|
|
1980
1989
|
if (++redirects <= this._config.redirectLimit) {
|
|
1990
|
+
abortResponse();
|
|
1981
1991
|
downloadUri.call(this, Request.fromURL(url, location));
|
|
1982
1992
|
}
|
|
1983
1993
|
else {
|
|
@@ -1989,8 +1999,8 @@ class Request extends module_1.default {
|
|
|
1989
1999
|
}
|
|
1990
2000
|
};
|
|
1991
2001
|
const errorResponse = (err) => {
|
|
1992
|
-
abortResponse();
|
|
1993
2002
|
if ((0, util_1.checkRetryable)(err) && ++retries <= this._config.retryLimit) {
|
|
2003
|
+
abortResponse();
|
|
1994
2004
|
if (isConnectionTimeout(err)) {
|
|
1995
2005
|
retryTimeout();
|
|
1996
2006
|
}
|
|
@@ -2133,7 +2143,6 @@ class Request extends module_1.default {
|
|
|
2133
2143
|
retryResponse(statusCode, res.headers['retry-after']);
|
|
2134
2144
|
}
|
|
2135
2145
|
else {
|
|
2136
|
-
abortResponse();
|
|
2137
2146
|
throwError(formatStatus(statusCode));
|
|
2138
2147
|
}
|
|
2139
2148
|
})
|
|
@@ -2151,8 +2160,8 @@ class Request extends module_1.default {
|
|
|
2151
2160
|
if (aborted) {
|
|
2152
2161
|
return;
|
|
2153
2162
|
}
|
|
2154
|
-
abortResponse();
|
|
2155
2163
|
if (++retries <= this._config.retryLimit) {
|
|
2164
|
+
abortResponse();
|
|
2156
2165
|
retryTimeout();
|
|
2157
2166
|
}
|
|
2158
2167
|
else {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@e-mc/request",
|
|
3
|
-
"version": "0.5.
|
|
3
|
+
"version": "0.5.17",
|
|
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.5.
|
|
24
|
-
"@e-mc/types": "0.5.
|
|
23
|
+
"@e-mc/module": "0.5.17",
|
|
24
|
+
"@e-mc/types": "0.5.17",
|
|
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
|
@@ -1,7 +1,10 @@
|
|
|
1
|
+
import type { AuthValue } from '@e-mc/types/lib/http';
|
|
2
|
+
|
|
1
3
|
import type { IncomingHttpHeaders, OutgoingHttpHeaders } from 'http';
|
|
2
4
|
|
|
3
5
|
declare namespace util {
|
|
4
6
|
function parseHeader<T = unknown>(headers: IncomingHttpHeaders, name: string): T | undefined;
|
|
7
|
+
function parseOutgoingHeaders(headers: OutgoingHttpHeaders | Headers | undefined): OutgoingHttpHeaders | undefined;
|
|
5
8
|
function normalizeHeaders(headers: OutgoingHttpHeaders): OutgoingHttpHeaders;
|
|
6
9
|
function getBasicAuth(auth: AuthValue): string;
|
|
7
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;
|