@e-mc/request 0.8.20 → 0.8.22
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/README.md +4 -4
- package/http/adapter/index.js +561 -0
- package/index.js +21 -17
- package/package.json +3 -3
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
|
|
package/README.md
CHANGED
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
|
|
10
10
|
## Interface
|
|
11
11
|
|
|
12
|
-
- https://www.unpkg.com/@e-mc/types@0.8.
|
|
12
|
+
- https://www.unpkg.com/@e-mc/types@0.8.22/lib/index.d.ts
|
|
13
13
|
|
|
14
14
|
```typescript
|
|
15
15
|
import type { IModule, ModuleConstructor } from "./index";
|
|
@@ -82,9 +82,9 @@ interface RequestConstructor extends ModuleConstructor {
|
|
|
82
82
|
|
|
83
83
|
## References
|
|
84
84
|
|
|
85
|
-
- https://www.unpkg.com/@e-mc/types@0.8.
|
|
86
|
-
- https://www.unpkg.com/@e-mc/types@0.8.
|
|
87
|
-
- https://www.unpkg.com/@e-mc/types@0.8.
|
|
85
|
+
- https://www.unpkg.com/@e-mc/types@0.8.22/lib/http.d.ts
|
|
86
|
+
- https://www.unpkg.com/@e-mc/types@0.8.22/lib/request.d.ts
|
|
87
|
+
- https://www.unpkg.com/@e-mc/types@0.8.22/lib/settings.d.ts
|
|
88
88
|
|
|
89
89
|
## LICENSE
|
|
90
90
|
|
|
@@ -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
|
@@ -316,8 +316,8 @@ function decompressEncoding(value, chunkSize) {
|
|
|
316
316
|
break;
|
|
317
317
|
}
|
|
318
318
|
}
|
|
319
|
-
function abortHeaders(href, request, options) {
|
|
320
|
-
const reason = (0, types_1.
|
|
319
|
+
function abortHeaders(href, request, options, statusCode = '') {
|
|
320
|
+
const reason = (0, types_1.errorMessage)(statusCode, "Aborted by client", href);
|
|
321
321
|
const outAbort = options.outAbort;
|
|
322
322
|
if (outAbort) {
|
|
323
323
|
outAbort.abort(reason);
|
|
@@ -1061,13 +1061,13 @@ class Request extends module_1.default {
|
|
|
1061
1061
|
return Promise.reject(err);
|
|
1062
1062
|
}
|
|
1063
1063
|
}
|
|
1064
|
-
let pathname, headers, binOpts, silent;
|
|
1064
|
+
let pathname, headers, binOpts, signal, silent;
|
|
1065
1065
|
if (options) {
|
|
1066
1066
|
if (typeof options === 'string') {
|
|
1067
1067
|
pathname = options;
|
|
1068
1068
|
}
|
|
1069
1069
|
else {
|
|
1070
|
-
({ pathname, headers, binOpts, silent } = options);
|
|
1070
|
+
({ pathname, headers, binOpts, signal, silent } = options);
|
|
1071
1071
|
if ((0, types_1.isArray)(binOpts)) {
|
|
1072
1072
|
let next = false;
|
|
1073
1073
|
binOpts = binOpts.filter(opt => !((0, types_1.isString)(opt) && /^-[a-z][\S\s]*$/i.test(opt.trim()))).map((opt) => {
|
|
@@ -1371,7 +1371,13 @@ class Request extends module_1.default {
|
|
|
1371
1371
|
}
|
|
1372
1372
|
for (const item of ARIA2.PID_QUEUE) {
|
|
1373
1373
|
try {
|
|
1374
|
-
|
|
1374
|
+
if (pid === item[0] && (signal === null || signal === void 0 ? void 0 : signal.aborted)) {
|
|
1375
|
+
process.kill(item[0]);
|
|
1376
|
+
closeTorrent(item[0]);
|
|
1377
|
+
}
|
|
1378
|
+
else {
|
|
1379
|
+
process.kill(item[0], 0);
|
|
1380
|
+
}
|
|
1375
1381
|
}
|
|
1376
1382
|
catch {
|
|
1377
1383
|
closeTorrent(item[0]);
|
|
@@ -1686,6 +1692,9 @@ class Request extends module_1.default {
|
|
|
1686
1692
|
const ac = new AbortController();
|
|
1687
1693
|
this[kDownloading].add(options.outAbort = ac);
|
|
1688
1694
|
stream.addAbortSignal(ac.signal, request);
|
|
1695
|
+
if (options.signal) {
|
|
1696
|
+
stream.addAbortSignal(options.signal, request);
|
|
1697
|
+
}
|
|
1689
1698
|
this.signal.addEventListener('abort', () => ac.abort(new Error("Aborted by process")), { once: true });
|
|
1690
1699
|
}
|
|
1691
1700
|
if (posting) {
|
|
@@ -1898,7 +1907,7 @@ class Request extends module_1.default {
|
|
|
1898
1907
|
}
|
|
1899
1908
|
const client = this.open(href, request);
|
|
1900
1909
|
const { host, url, encoding, outFormat } = request;
|
|
1901
|
-
let buffer, aborted;
|
|
1910
|
+
let buffer, aborted = false;
|
|
1902
1911
|
({ httpVersion, outAbort } = request);
|
|
1903
1912
|
const isAborted = () => client.destroyed || httpVersion === 2 && client.aborted;
|
|
1904
1913
|
const isRetry = (value) => (0, util_1.isRetryable)(value) && ++retries <= this._config.retryLimit;
|
|
@@ -1914,14 +1923,11 @@ class Request extends module_1.default {
|
|
|
1914
1923
|
if (timeout) {
|
|
1915
1924
|
clearTimeout(timeout);
|
|
1916
1925
|
}
|
|
1917
|
-
buffer = null;
|
|
1918
|
-
aborted = true;
|
|
1919
1926
|
if (outAbort) {
|
|
1920
|
-
if (!client.aborted) {
|
|
1921
|
-
outAbort.abort();
|
|
1922
|
-
}
|
|
1923
1927
|
this[kDownloading].delete(outAbort);
|
|
1924
1928
|
}
|
|
1929
|
+
buffer = null;
|
|
1930
|
+
aborted = true;
|
|
1925
1931
|
client.destroy();
|
|
1926
1932
|
};
|
|
1927
1933
|
const retryTimeout = () => {
|
|
@@ -1946,7 +1952,6 @@ class Request extends module_1.default {
|
|
|
1946
1952
|
var _o;
|
|
1947
1953
|
if (readTimeout > 0) {
|
|
1948
1954
|
timeout = setTimeout(() => {
|
|
1949
|
-
abortResponse();
|
|
1950
1955
|
throwError((0, types_1.errorValue)("Timeout was exceeded", href.toString()));
|
|
1951
1956
|
}, readTimeout);
|
|
1952
1957
|
}
|
|
@@ -2070,9 +2075,9 @@ class Request extends module_1.default {
|
|
|
2070
2075
|
host.success(httpVersion);
|
|
2071
2076
|
};
|
|
2072
2077
|
const redirectResponse = (statusCode, location) => {
|
|
2073
|
-
abortResponse();
|
|
2074
2078
|
if (location) {
|
|
2075
2079
|
if (++redirects <= this._config.redirectLimit) {
|
|
2080
|
+
abortResponse();
|
|
2076
2081
|
downloadUri.call(this, Request.fromURL(url, location));
|
|
2077
2082
|
}
|
|
2078
2083
|
else {
|
|
@@ -2084,11 +2089,11 @@ class Request extends module_1.default {
|
|
|
2084
2089
|
}
|
|
2085
2090
|
};
|
|
2086
2091
|
const errorResponse = (err) => {
|
|
2087
|
-
abortResponse();
|
|
2088
2092
|
if (wasAborted(err)) {
|
|
2089
2093
|
throwError(err);
|
|
2090
2094
|
}
|
|
2091
2095
|
else if ((0, util_1.checkRetryable)(err) && ++retries <= this._config.retryLimit) {
|
|
2096
|
+
abortResponse();
|
|
2092
2097
|
if (isConnectionTimeout(err)) {
|
|
2093
2098
|
retryTimeout();
|
|
2094
2099
|
}
|
|
@@ -2233,7 +2238,6 @@ class Request extends module_1.default {
|
|
|
2233
2238
|
retryResponse(statusCode, res.headers['retry-after']);
|
|
2234
2239
|
}
|
|
2235
2240
|
else {
|
|
2236
|
-
abortResponse();
|
|
2237
2241
|
throwError(formatStatus(statusCode));
|
|
2238
2242
|
}
|
|
2239
2243
|
})
|
|
@@ -2251,8 +2255,8 @@ class Request extends module_1.default {
|
|
|
2251
2255
|
if (aborted) {
|
|
2252
2256
|
return;
|
|
2253
2257
|
}
|
|
2254
|
-
abortResponse();
|
|
2255
2258
|
if (++retries <= this._config.retryLimit) {
|
|
2259
|
+
abortResponse();
|
|
2256
2260
|
retryTimeout();
|
|
2257
2261
|
}
|
|
2258
2262
|
else {
|
|
@@ -2294,7 +2298,7 @@ class Request extends module_1.default {
|
|
|
2294
2298
|
for (const callback of called) {
|
|
2295
2299
|
try {
|
|
2296
2300
|
if (callback(code, headers, url) === true) {
|
|
2297
|
-
abortHeaders.call(this, href, request, options);
|
|
2301
|
+
abortHeaders.call(this, href, request, options, code);
|
|
2298
2302
|
return false;
|
|
2299
2303
|
}
|
|
2300
2304
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@e-mc/request",
|
|
3
|
-
"version": "0.8.
|
|
3
|
+
"version": "0.8.22",
|
|
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.8.
|
|
24
|
-
"@e-mc/types": "0.8.
|
|
23
|
+
"@e-mc/module": "0.8.22",
|
|
24
|
+
"@e-mc/types": "0.8.22",
|
|
25
25
|
"combined-stream": "^1.0.8",
|
|
26
26
|
"js-yaml": "^4.1.0",
|
|
27
27
|
"picomatch": "^3.0.1",
|