rekwest 4.1.0 → 4.2.1
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/README.md +188 -187
- package/dist/ackn.js +1 -1
- package/dist/constants.js +11 -5
- package/dist/defaults.js +41 -0
- package/dist/index.js +30 -211
- package/dist/postflight.js +111 -0
- package/dist/preflight.js +65 -0
- package/dist/utils.js +122 -73
- package/package.json +1 -1
- package/src/ackn.mjs +1 -1
- package/src/constants.mjs +8 -2
- package/src/defaults.mjs +44 -0
- package/src/index.mjs +15 -243
- package/src/postflight.mjs +136 -0
- package/src/preflight.mjs +69 -0
- package/src/utils.mjs +146 -79
package/dist/utils.js
CHANGED
|
@@ -1,38 +1,37 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
|
|
3
3
|
exports.__esModule = true;
|
|
4
|
-
exports.sanitize = exports.sameOrigin = exports.
|
|
4
|
+
exports.sanitize = exports.sameOrigin = exports.mixin = exports.merge = exports.maxRetryAfterError = exports.maxRetryAfter = exports.dispatch = exports.decompress = exports.compress = exports.brandCheck = exports.affix = exports.admix = void 0;
|
|
5
5
|
exports.tap = tap;
|
|
6
|
-
exports.transform = void 0;
|
|
6
|
+
exports.validation = exports.unwind = exports.transform = exports.transfer = void 0;
|
|
7
7
|
var _nodeBuffer = require("node:buffer");
|
|
8
|
-
var _nodeHttp = _interopRequireDefault(require("node:
|
|
8
|
+
var _nodeHttp = _interopRequireDefault(require("node:http"));
|
|
9
|
+
var _nodeHttp2 = _interopRequireDefault(require("node:http2"));
|
|
10
|
+
var _nodeHttps = _interopRequireDefault(require("node:https"));
|
|
9
11
|
var _nodeStream = require("node:stream");
|
|
10
12
|
var _consumers = require("node:stream/consumers");
|
|
13
|
+
var _promises = require("node:timers/promises");
|
|
11
14
|
var _nodeUtil = require("node:util");
|
|
12
15
|
var _nodeZlib = _interopRequireDefault(require("node:zlib"));
|
|
16
|
+
var _ackn = require("./ackn");
|
|
13
17
|
var _constants = require("./constants");
|
|
14
|
-
var _cookies = require("./cookies");
|
|
15
18
|
var _errors = require("./errors");
|
|
16
19
|
var _file = require("./file");
|
|
17
20
|
var _formdata = require("./formdata");
|
|
21
|
+
var _index = _interopRequireDefault(require("./index"));
|
|
18
22
|
var _mediatypes = require("./mediatypes");
|
|
23
|
+
var _postflight = require("./postflight");
|
|
24
|
+
var _preflight = require("./preflight");
|
|
19
25
|
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
|
|
20
26
|
const {
|
|
21
|
-
HTTP2_HEADER_ACCEPT,
|
|
22
|
-
HTTP2_HEADER_ACCEPT_ENCODING,
|
|
23
|
-
HTTP2_HEADER_AUTHORITY,
|
|
24
27
|
HTTP2_HEADER_CONTENT_ENCODING,
|
|
25
28
|
HTTP2_HEADER_CONTENT_LENGTH,
|
|
26
29
|
HTTP2_HEADER_CONTENT_TYPE,
|
|
27
|
-
|
|
28
|
-
HTTP2_HEADER_METHOD,
|
|
29
|
-
HTTP2_HEADER_PATH,
|
|
30
|
-
HTTP2_HEADER_SCHEME,
|
|
30
|
+
HTTP2_HEADER_RETRY_AFTER,
|
|
31
31
|
HTTP2_HEADER_STATUS,
|
|
32
32
|
HTTP2_METHOD_GET,
|
|
33
33
|
HTTP2_METHOD_HEAD
|
|
34
|
-
} =
|
|
35
|
-
const unwind = encodings => encodings.split(',').map(it => it.trim());
|
|
34
|
+
} = _nodeHttp2.default.constants;
|
|
36
35
|
const admix = (res, headers, options) => {
|
|
37
36
|
const {
|
|
38
37
|
h2
|
|
@@ -126,6 +125,10 @@ const dispatch = ({
|
|
|
126
125
|
}
|
|
127
126
|
};
|
|
128
127
|
exports.dispatch = dispatch;
|
|
128
|
+
const maxRetryAfter = Symbol('maxRetryAfter');
|
|
129
|
+
exports.maxRetryAfter = maxRetryAfter;
|
|
130
|
+
const maxRetryAfterError = (interval, options) => new _errors.RequestError(`Maximum '${HTTP2_HEADER_RETRY_AFTER}' limit exceeded: ${interval} ms.`, options);
|
|
131
|
+
exports.maxRetryAfterError = maxRetryAfterError;
|
|
129
132
|
const merge = (target = {}, ...rest) => {
|
|
130
133
|
target = JSON.parse(JSON.stringify(target));
|
|
131
134
|
if (!rest.length) {
|
|
@@ -215,7 +218,7 @@ const mixin = (res, {
|
|
|
215
218
|
if (/\bjson\b/i.test(contentType)) {
|
|
216
219
|
body = JSON.parse(body.toString(charset));
|
|
217
220
|
} else if (/\b(?:text|xml)\b/i.test(contentType)) {
|
|
218
|
-
if (/\b(?:latin1|ucs-2|utf-(?:8|16le))\b
|
|
221
|
+
if (/\b(?:latin1|ucs-2|utf-(?:8|16le))\b/i.test(charset)) {
|
|
219
222
|
body = body.toString(charset);
|
|
220
223
|
} else {
|
|
221
224
|
body = new TextDecoder(charset).decode(body);
|
|
@@ -235,66 +238,9 @@ const mixin = (res, {
|
|
|
235
238
|
});
|
|
236
239
|
};
|
|
237
240
|
exports.mixin = mixin;
|
|
238
|
-
const preflight = options => {
|
|
239
|
-
const {
|
|
240
|
-
cookies,
|
|
241
|
-
h2 = false,
|
|
242
|
-
headers,
|
|
243
|
-
method = HTTP2_METHOD_GET,
|
|
244
|
-
redirected,
|
|
245
|
-
url
|
|
246
|
-
} = options;
|
|
247
|
-
if (h2) {
|
|
248
|
-
options.endStream = [HTTP2_METHOD_GET, HTTP2_METHOD_HEAD].includes(method);
|
|
249
|
-
}
|
|
250
|
-
if (cookies !== false) {
|
|
251
|
-
let cookie = _cookies.Cookies.jar.get(url.origin);
|
|
252
|
-
if (cookies === Object(cookies) && !redirected) {
|
|
253
|
-
if (cookie) {
|
|
254
|
-
new _cookies.Cookies(cookies).forEach(function (val, key) {
|
|
255
|
-
this.set(key, val);
|
|
256
|
-
}, cookie);
|
|
257
|
-
} else {
|
|
258
|
-
cookie = new _cookies.Cookies(cookies);
|
|
259
|
-
_cookies.Cookies.jar.set(url.origin, cookie);
|
|
260
|
-
}
|
|
261
|
-
}
|
|
262
|
-
options.headers = {
|
|
263
|
-
...(cookie && {
|
|
264
|
-
[HTTP2_HEADER_COOKIE]: cookie
|
|
265
|
-
}),
|
|
266
|
-
...headers
|
|
267
|
-
};
|
|
268
|
-
}
|
|
269
|
-
options.digest ??= true;
|
|
270
|
-
options.follow ??= 20;
|
|
271
|
-
options.h2 ??= h2;
|
|
272
|
-
options.headers = {
|
|
273
|
-
[HTTP2_HEADER_ACCEPT]: `${_mediatypes.APPLICATION_JSON}, ${_mediatypes.TEXT_PLAIN}, ${_mediatypes.WILDCARD}`,
|
|
274
|
-
[HTTP2_HEADER_ACCEPT_ENCODING]: 'br, deflate, deflate-raw, gzip, identity',
|
|
275
|
-
...Object.entries(options.headers ?? {}).reduce((acc, [key, val]) => (acc[key.toLowerCase()] = val, acc), {}),
|
|
276
|
-
...(h2 && {
|
|
277
|
-
[HTTP2_HEADER_AUTHORITY]: url.host,
|
|
278
|
-
[HTTP2_HEADER_METHOD]: method,
|
|
279
|
-
[HTTP2_HEADER_PATH]: `${url.pathname}${url.search}`,
|
|
280
|
-
[HTTP2_HEADER_SCHEME]: url.protocol.replace(/\p{Punctuation}/gu, '')
|
|
281
|
-
})
|
|
282
|
-
};
|
|
283
|
-
options.method ??= method;
|
|
284
|
-
options.parse ??= true;
|
|
285
|
-
options.redirect ??= _constants.redirectModes.follow;
|
|
286
|
-
if (!Reflect.has(_constants.redirectModes, options.redirect)) {
|
|
287
|
-
options.createConnection?.().destroy();
|
|
288
|
-
throw new TypeError(`Failed to read the 'redirect' property from 'options': The provided value '${options.redirect}' is not a valid enum value.`);
|
|
289
|
-
}
|
|
290
|
-
options.redirected ??= false;
|
|
291
|
-
options.thenable ??= false;
|
|
292
|
-
return options;
|
|
293
|
-
};
|
|
294
|
-
exports.preflight = preflight;
|
|
295
241
|
const sanitize = (url, options = {}) => {
|
|
296
242
|
if (options.trimTrailingSlashes) {
|
|
297
|
-
url = `${url}`.replace(/(?<!:)\/+/
|
|
243
|
+
url = `${url}`.replace(/(?<!:)\/+/g, '/');
|
|
298
244
|
}
|
|
299
245
|
url = new URL(url);
|
|
300
246
|
return Object.assign(options, {
|
|
@@ -313,6 +259,94 @@ async function* tap(value) {
|
|
|
313
259
|
yield await value.arrayBuffer();
|
|
314
260
|
}
|
|
315
261
|
}
|
|
262
|
+
const transfer = async options => {
|
|
263
|
+
const {
|
|
264
|
+
digest,
|
|
265
|
+
h2,
|
|
266
|
+
redirected,
|
|
267
|
+
thenable,
|
|
268
|
+
url
|
|
269
|
+
} = options;
|
|
270
|
+
if (options.follow === 0) {
|
|
271
|
+
throw new _errors.RequestError(`Maximum redirect reached at: ${url.href}`);
|
|
272
|
+
}
|
|
273
|
+
if (url.protocol === 'https:') {
|
|
274
|
+
options = !h2 ? await (0, _ackn.ackn)(options) : {
|
|
275
|
+
...options,
|
|
276
|
+
createConnection: null,
|
|
277
|
+
protocol: url.protocol
|
|
278
|
+
};
|
|
279
|
+
} else if (Reflect.has(options, 'alpnProtocol')) {
|
|
280
|
+
['alpnProtocol', 'createConnection', 'h2', 'protocol'].forEach(it => Reflect.deleteProperty(options, it));
|
|
281
|
+
}
|
|
282
|
+
try {
|
|
283
|
+
options = await transform((0, _preflight.preflight)(options));
|
|
284
|
+
} catch (ex) {
|
|
285
|
+
options.createConnection?.().destroy();
|
|
286
|
+
throw ex;
|
|
287
|
+
}
|
|
288
|
+
const promise = new Promise((resolve, reject) => {
|
|
289
|
+
let client, req;
|
|
290
|
+
if (options.h2) {
|
|
291
|
+
client = _nodeHttp2.default.connect(url.origin, options);
|
|
292
|
+
req = client.request(options.headers, options);
|
|
293
|
+
} else {
|
|
294
|
+
const {
|
|
295
|
+
request
|
|
296
|
+
} = url.protocol === 'http:' ? _nodeHttp.default : _nodeHttps.default;
|
|
297
|
+
req = request(url, options);
|
|
298
|
+
}
|
|
299
|
+
affix(client, req, options);
|
|
300
|
+
req.once('error', reject);
|
|
301
|
+
req.once('frameError', reject);
|
|
302
|
+
req.once('goaway', reject);
|
|
303
|
+
req.once('response', res => (0, _postflight.postflight)(req, res, options, {
|
|
304
|
+
reject,
|
|
305
|
+
resolve
|
|
306
|
+
}));
|
|
307
|
+
dispatch(options, req);
|
|
308
|
+
});
|
|
309
|
+
try {
|
|
310
|
+
const res = await promise;
|
|
311
|
+
if (digest && !redirected) {
|
|
312
|
+
res.body = await res.body();
|
|
313
|
+
}
|
|
314
|
+
return res;
|
|
315
|
+
} catch (ex) {
|
|
316
|
+
const {
|
|
317
|
+
maxRetryAfter,
|
|
318
|
+
retry
|
|
319
|
+
} = options;
|
|
320
|
+
if (retry?.attempts && retry?.statusCodes.includes(ex.statusCode)) {
|
|
321
|
+
let {
|
|
322
|
+
interval
|
|
323
|
+
} = retry;
|
|
324
|
+
if (retry.retryAfter && ex.headers[HTTP2_HEADER_RETRY_AFTER]) {
|
|
325
|
+
interval = ex.headers[HTTP2_HEADER_RETRY_AFTER];
|
|
326
|
+
interval = Number(interval) * 1000 || new Date(interval) - Date.now();
|
|
327
|
+
if (interval > maxRetryAfter) {
|
|
328
|
+
throw maxRetryAfterError(interval, {
|
|
329
|
+
cause: ex
|
|
330
|
+
});
|
|
331
|
+
}
|
|
332
|
+
} else {
|
|
333
|
+
interval = new Function('interval', `return Math.ceil(${retry.backoffStrategy});`)(interval);
|
|
334
|
+
}
|
|
335
|
+
retry.attempts--;
|
|
336
|
+
retry.interval = interval;
|
|
337
|
+
return (0, _promises.setTimeout)(interval).then(() => (0, _index.default)(url, options));
|
|
338
|
+
}
|
|
339
|
+
if (digest && !redirected && ex.body) {
|
|
340
|
+
ex.body = await ex.body();
|
|
341
|
+
}
|
|
342
|
+
if (!thenable) {
|
|
343
|
+
throw ex;
|
|
344
|
+
} else {
|
|
345
|
+
return ex;
|
|
346
|
+
}
|
|
347
|
+
}
|
|
348
|
+
};
|
|
349
|
+
exports.transfer = transfer;
|
|
316
350
|
const transform = async options => {
|
|
317
351
|
let {
|
|
318
352
|
body,
|
|
@@ -371,4 +405,19 @@ const transform = async options => {
|
|
|
371
405
|
body
|
|
372
406
|
};
|
|
373
407
|
};
|
|
374
|
-
exports.transform = transform;
|
|
408
|
+
exports.transform = transform;
|
|
409
|
+
const unwind = encodings => encodings.split(',').map(it => it.trim());
|
|
410
|
+
exports.unwind = unwind;
|
|
411
|
+
const validation = (options = {}) => {
|
|
412
|
+
if (options.body && [HTTP2_METHOD_GET, HTTP2_METHOD_HEAD].includes(options.method)) {
|
|
413
|
+
throw new TypeError(`Request with ${HTTP2_METHOD_GET}/${HTTP2_METHOD_HEAD} method cannot have body.`);
|
|
414
|
+
}
|
|
415
|
+
if (!Object.values(_constants.requestCredentials).includes(options.credentials)) {
|
|
416
|
+
throw new TypeError(`Failed to read the 'credentials' property from 'options': The provided value '${options.credentials}' is not a valid enum value.`);
|
|
417
|
+
}
|
|
418
|
+
if (!Reflect.has(_constants.requestRedirect, options.redirect)) {
|
|
419
|
+
throw new TypeError(`Failed to read the 'redirect' property from 'options': The provided value '${options.redirect}' is not a valid enum value.`);
|
|
420
|
+
}
|
|
421
|
+
return options;
|
|
422
|
+
};
|
|
423
|
+
exports.validation = validation;
|
package/package.json
CHANGED
package/src/ackn.mjs
CHANGED
package/src/constants.mjs
CHANGED
|
@@ -8,13 +8,19 @@ const {
|
|
|
8
8
|
HTTP_STATUS_TEMPORARY_REDIRECT,
|
|
9
9
|
} = http2.constants;
|
|
10
10
|
|
|
11
|
-
export const
|
|
11
|
+
export const requestCredentials = {
|
|
12
|
+
include: 'include',
|
|
13
|
+
omit: 'omit',
|
|
14
|
+
sameOrigin: 'same-origin',
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
export const requestRedirect = {
|
|
12
18
|
error: 'error',
|
|
13
19
|
follow: 'follow',
|
|
14
20
|
manual: 'manual',
|
|
15
21
|
};
|
|
16
22
|
|
|
17
|
-
export const
|
|
23
|
+
export const requestRedirectCodes = [
|
|
18
24
|
HTTP_STATUS_MOVED_PERMANENTLY,
|
|
19
25
|
HTTP_STATUS_FOUND,
|
|
20
26
|
HTTP_STATUS_SEE_OTHER,
|
package/src/defaults.mjs
ADDED
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import http2 from 'node:http2';
|
|
2
|
+
import {
|
|
3
|
+
requestCredentials,
|
|
4
|
+
requestRedirect,
|
|
5
|
+
} from './constants.mjs';
|
|
6
|
+
import { maxRetryAfter } from './utils.mjs';
|
|
7
|
+
|
|
8
|
+
const {
|
|
9
|
+
HTTP2_METHOD_GET,
|
|
10
|
+
HTTP_STATUS_SERVICE_UNAVAILABLE,
|
|
11
|
+
HTTP_STATUS_TOO_MANY_REQUESTS,
|
|
12
|
+
} = http2.constants;
|
|
13
|
+
|
|
14
|
+
const stash = {
|
|
15
|
+
credentials: requestCredentials.sameOrigin,
|
|
16
|
+
digest: true,
|
|
17
|
+
follow: 20,
|
|
18
|
+
get maxRetryAfter() {
|
|
19
|
+
return this[maxRetryAfter] ?? this.timeout;
|
|
20
|
+
},
|
|
21
|
+
set maxRetryAfter(value) {
|
|
22
|
+
this[maxRetryAfter] = value;
|
|
23
|
+
},
|
|
24
|
+
method: HTTP2_METHOD_GET,
|
|
25
|
+
parse: true,
|
|
26
|
+
redirect: requestRedirect.follow,
|
|
27
|
+
redirected: false,
|
|
28
|
+
retry: {
|
|
29
|
+
attempts: 0,
|
|
30
|
+
backoffStrategy: 'interval * Math.log(Math.random() * (Math.E * Math.E - Math.E) + Math.E)',
|
|
31
|
+
interval: 1e3,
|
|
32
|
+
retryAfter: true,
|
|
33
|
+
statusCodes: [
|
|
34
|
+
HTTP_STATUS_TOO_MANY_REQUESTS,
|
|
35
|
+
HTTP_STATUS_SERVICE_UNAVAILABLE,
|
|
36
|
+
],
|
|
37
|
+
},
|
|
38
|
+
thenable: false,
|
|
39
|
+
timeout: 3e5,
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
export default {
|
|
43
|
+
stash,
|
|
44
|
+
};
|
package/src/index.mjs
CHANGED
|
@@ -1,25 +1,17 @@
|
|
|
1
1
|
import http from 'node:http';
|
|
2
2
|
import http2 from 'node:http2';
|
|
3
3
|
import https from 'node:https';
|
|
4
|
-
import {
|
|
5
|
-
import
|
|
6
|
-
import {
|
|
7
|
-
redirectModes,
|
|
8
|
-
redirectStatusCodes,
|
|
9
|
-
} from './constants.mjs';
|
|
10
|
-
import { Cookies } from './cookies.mjs';
|
|
11
|
-
import { RequestError } from './errors.mjs';
|
|
4
|
+
import { requestRedirect } from './constants.mjs';
|
|
5
|
+
import defaults from './defaults.mjs';
|
|
12
6
|
import { APPLICATION_OCTET_STREAM } from './mediatypes.mjs';
|
|
7
|
+
import { preflight } from './preflight.mjs';
|
|
13
8
|
import {
|
|
14
9
|
admix,
|
|
15
10
|
affix,
|
|
16
|
-
dispatch,
|
|
17
11
|
merge,
|
|
18
|
-
mixin,
|
|
19
|
-
preflight,
|
|
20
|
-
sameOrigin,
|
|
21
12
|
sanitize,
|
|
22
|
-
|
|
13
|
+
transfer,
|
|
14
|
+
validation,
|
|
23
15
|
} from './utils.mjs';
|
|
24
16
|
|
|
25
17
|
export { constants } from 'node:http2';
|
|
@@ -34,263 +26,43 @@ export * as mediatypes from './mediatypes.mjs';
|
|
|
34
26
|
export * from './utils.mjs';
|
|
35
27
|
|
|
36
28
|
const {
|
|
37
|
-
HTTP2_HEADER_AUTHORIZATION,
|
|
38
29
|
HTTP2_HEADER_CONTENT_TYPE,
|
|
39
|
-
HTTP2_HEADER_LOCATION,
|
|
40
|
-
HTTP2_HEADER_RETRY_AFTER,
|
|
41
|
-
HTTP2_HEADER_SET_COOKIE,
|
|
42
|
-
HTTP2_METHOD_GET,
|
|
43
|
-
HTTP2_METHOD_HEAD,
|
|
44
|
-
HTTP2_METHOD_POST,
|
|
45
|
-
HTTP_STATUS_BAD_REQUEST,
|
|
46
|
-
HTTP_STATUS_FOUND,
|
|
47
|
-
HTTP_STATUS_MOVED_PERMANENTLY,
|
|
48
|
-
HTTP_STATUS_SEE_OTHER,
|
|
49
|
-
HTTP_STATUS_SERVICE_UNAVAILABLE,
|
|
50
|
-
HTTP_STATUS_TOO_MANY_REQUESTS,
|
|
51
30
|
} = http2.constants;
|
|
52
31
|
|
|
53
|
-
|
|
54
|
-
const maxRetryAfterError = (
|
|
55
|
-
interval,
|
|
56
|
-
options,
|
|
57
|
-
) => new RequestError(`Maximum '${ HTTP2_HEADER_RETRY_AFTER }' limit exceeded: ${ interval } ms.`, options);
|
|
58
|
-
let defaults = {
|
|
59
|
-
follow: 20,
|
|
60
|
-
get maxRetryAfter() {
|
|
61
|
-
return this[maxRetryAfter] ?? this.timeout;
|
|
62
|
-
},
|
|
63
|
-
set maxRetryAfter(value) {
|
|
64
|
-
this[maxRetryAfter] = value;
|
|
65
|
-
},
|
|
66
|
-
method: HTTP2_METHOD_GET,
|
|
67
|
-
retry: {
|
|
68
|
-
attempts: 0,
|
|
69
|
-
backoffStrategy: 'interval * Math.log(Math.random() * (Math.E * Math.E - Math.E) + Math.E)',
|
|
70
|
-
interval: 1e3,
|
|
71
|
-
retryAfter: true,
|
|
72
|
-
statusCodes: [
|
|
73
|
-
HTTP_STATUS_TOO_MANY_REQUESTS,
|
|
74
|
-
HTTP_STATUS_SERVICE_UNAVAILABLE,
|
|
75
|
-
],
|
|
76
|
-
},
|
|
77
|
-
timeout: 3e5,
|
|
78
|
-
};
|
|
79
|
-
|
|
80
|
-
export default async function rekwest(...args) {
|
|
32
|
+
export default function rekwest(...args) {
|
|
81
33
|
let options = sanitize(...args);
|
|
82
|
-
const { url } = options;
|
|
83
34
|
|
|
84
35
|
if (!options.redirected) {
|
|
85
36
|
options = merge(rekwest.defaults, options);
|
|
86
37
|
}
|
|
87
38
|
|
|
88
|
-
|
|
89
|
-
HTTP2_METHOD_GET,
|
|
90
|
-
HTTP2_METHOD_HEAD,
|
|
91
|
-
].includes(options.method)) {
|
|
92
|
-
throw new TypeError(`Request with ${ HTTP2_METHOD_GET }/${ HTTP2_METHOD_HEAD } method cannot have body.`);
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
if (options.follow === 0) {
|
|
96
|
-
throw new RequestError(`Maximum redirect reached at: ${ url.href }`);
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
if (url.protocol === 'https:') {
|
|
100
|
-
options = await ackn(options);
|
|
101
|
-
} else if (Reflect.has(options, 'alpnProtocol')) {
|
|
102
|
-
[
|
|
103
|
-
'alpnProtocol',
|
|
104
|
-
'createConnection',
|
|
105
|
-
'h2',
|
|
106
|
-
'protocol',
|
|
107
|
-
].forEach((it) => Reflect.deleteProperty(options, it));
|
|
108
|
-
}
|
|
109
|
-
|
|
110
|
-
options = await transform(preflight(options));
|
|
111
|
-
|
|
112
|
-
const { cookies, digest, follow, h2, redirect, redirected, thenable } = options;
|
|
113
|
-
const { request } = (url.protocol === 'http:' ? http : https);
|
|
114
|
-
|
|
115
|
-
const promise = new Promise((resolve, reject) => {
|
|
116
|
-
let client, req;
|
|
117
|
-
|
|
118
|
-
if (h2) {
|
|
119
|
-
client = http2.connect(url.origin, options);
|
|
120
|
-
req = client.request(options.headers, options);
|
|
121
|
-
} else {
|
|
122
|
-
req = request(url, options);
|
|
123
|
-
}
|
|
124
|
-
|
|
125
|
-
affix(client, req, options);
|
|
126
|
-
req.once('error', reject);
|
|
127
|
-
req.once('frameError', reject);
|
|
128
|
-
req.once('goaway', reject);
|
|
129
|
-
req.once('response', (res) => {
|
|
130
|
-
let headers;
|
|
131
|
-
|
|
132
|
-
if (h2) {
|
|
133
|
-
headers = res;
|
|
134
|
-
res = req;
|
|
135
|
-
} else {
|
|
136
|
-
res.once('error', reject);
|
|
137
|
-
}
|
|
138
|
-
|
|
139
|
-
admix(res, headers, options);
|
|
140
|
-
|
|
141
|
-
if (cookies !== false && res.headers[HTTP2_HEADER_SET_COOKIE]) {
|
|
142
|
-
if (Cookies.jar.has(url.origin)) {
|
|
143
|
-
new Cookies(res.headers[HTTP2_HEADER_SET_COOKIE]).forEach(function (val, key) {
|
|
144
|
-
this.set(key, val);
|
|
145
|
-
}, Cookies.jar.get(url.origin));
|
|
146
|
-
} else {
|
|
147
|
-
Cookies.jar.set(url.origin, new Cookies(res.headers[HTTP2_HEADER_SET_COOKIE]));
|
|
148
|
-
}
|
|
149
|
-
}
|
|
150
|
-
|
|
151
|
-
Reflect.defineProperty(res, 'cookies', {
|
|
152
|
-
enumerable: true,
|
|
153
|
-
value: cookies !== false && Cookies.jar.has(url.origin)
|
|
154
|
-
? Cookies.jar.get(url.origin)
|
|
155
|
-
: void 0,
|
|
156
|
-
});
|
|
157
|
-
|
|
158
|
-
const { statusCode } = res;
|
|
159
|
-
|
|
160
|
-
if (follow && /3\d{2}/.test(statusCode) && res.headers[HTTP2_HEADER_LOCATION]) {
|
|
161
|
-
if (!redirectStatusCodes.includes(statusCode)) {
|
|
162
|
-
return res.emit('error', new RangeError(`Invalid status code: ${ statusCode }`));
|
|
163
|
-
}
|
|
164
|
-
|
|
165
|
-
if (redirect === redirectModes.error) {
|
|
166
|
-
return res.emit('error', new RequestError(`Unexpected redirect, redirect mode is set to '${ redirect }'.`));
|
|
167
|
-
}
|
|
168
|
-
|
|
169
|
-
if (redirect === redirectModes.follow) {
|
|
170
|
-
const location = new URL(res.headers[HTTP2_HEADER_LOCATION], url);
|
|
171
|
-
|
|
172
|
-
if (!/^https?:/.test(location.protocol)) {
|
|
173
|
-
return res.emit('error', new RequestError('URL scheme must be "http" or "https".'));
|
|
174
|
-
}
|
|
175
|
-
|
|
176
|
-
if (!sameOrigin(location, url)) {
|
|
177
|
-
Reflect.deleteProperty(options.headers, HTTP2_HEADER_AUTHORIZATION);
|
|
178
|
-
location.password = location.username = '';
|
|
179
|
-
}
|
|
180
|
-
|
|
181
|
-
options.url = location;
|
|
182
|
-
|
|
183
|
-
if (statusCode !== HTTP_STATUS_SEE_OTHER && options?.body?.pipe?.constructor === Function) {
|
|
184
|
-
return res.emit('error', new RequestError(`Unable to ${ redirect } redirect with streamable body.`));
|
|
185
|
-
}
|
|
186
|
-
|
|
187
|
-
options.follow--;
|
|
188
|
-
|
|
189
|
-
if (([
|
|
190
|
-
HTTP_STATUS_MOVED_PERMANENTLY,
|
|
191
|
-
HTTP_STATUS_FOUND,
|
|
192
|
-
].includes(statusCode) && request.method === HTTP2_METHOD_POST) || (statusCode === HTTP_STATUS_SEE_OTHER && ![
|
|
193
|
-
HTTP2_METHOD_GET,
|
|
194
|
-
HTTP2_METHOD_HEAD,
|
|
195
|
-
].includes(options.method))) {
|
|
196
|
-
Object.keys(options.headers).filter((it) => /^content-/i.test(it))
|
|
197
|
-
.forEach((it) => Reflect.deleteProperty(options.headers, it));
|
|
198
|
-
options.body = null;
|
|
199
|
-
options.method = HTTP2_METHOD_GET;
|
|
200
|
-
}
|
|
201
|
-
|
|
202
|
-
Reflect.set(options, 'redirected', true);
|
|
203
|
-
|
|
204
|
-
if (statusCode === HTTP_STATUS_MOVED_PERMANENTLY && res.headers[HTTP2_HEADER_RETRY_AFTER]) {
|
|
205
|
-
let interval = res.headers[HTTP2_HEADER_RETRY_AFTER];
|
|
206
|
-
|
|
207
|
-
interval = Number(interval) * 1000 || new Date(interval) - Date.now();
|
|
208
|
-
|
|
209
|
-
if (interval > options.maxRetryAfter) {
|
|
210
|
-
return res.emit('error', maxRetryAfterError(interval, { cause: mixin(res, options) }));
|
|
211
|
-
}
|
|
212
|
-
|
|
213
|
-
return setTimeoutPromise(interval).then(() => rekwest(options.url, options).then(resolve, reject));
|
|
214
|
-
}
|
|
215
|
-
|
|
216
|
-
return rekwest(options.url, options).then(resolve, reject);
|
|
217
|
-
}
|
|
218
|
-
}
|
|
219
|
-
|
|
220
|
-
if (statusCode >= HTTP_STATUS_BAD_REQUEST) {
|
|
221
|
-
return reject(mixin(res, options));
|
|
222
|
-
}
|
|
223
|
-
|
|
224
|
-
resolve(mixin(res, options));
|
|
225
|
-
});
|
|
226
|
-
|
|
227
|
-
dispatch(options, req);
|
|
228
|
-
});
|
|
229
|
-
|
|
230
|
-
try {
|
|
231
|
-
const res = await promise;
|
|
232
|
-
|
|
233
|
-
if (digest && !redirected) {
|
|
234
|
-
res.body = await res.body();
|
|
235
|
-
}
|
|
236
|
-
|
|
237
|
-
return res;
|
|
238
|
-
} catch (ex) {
|
|
239
|
-
const { maxRetryAfter, retry } = options;
|
|
240
|
-
|
|
241
|
-
if (retry?.attempts && retry?.statusCodes.includes(ex.statusCode)) {
|
|
242
|
-
let { interval } = retry;
|
|
243
|
-
|
|
244
|
-
if (retry.retryAfter && ex.headers[HTTP2_HEADER_RETRY_AFTER]) {
|
|
245
|
-
interval = ex.headers[HTTP2_HEADER_RETRY_AFTER];
|
|
246
|
-
interval = Number(interval) * 1000 || new Date(interval) - Date.now();
|
|
247
|
-
if (interval > maxRetryAfter) {
|
|
248
|
-
throw maxRetryAfterError(interval, { cause: ex });
|
|
249
|
-
}
|
|
250
|
-
} else {
|
|
251
|
-
interval = new Function('interval', `return Math.ceil(${ retry.backoffStrategy });`)(interval);
|
|
252
|
-
}
|
|
253
|
-
|
|
254
|
-
retry.attempts--;
|
|
255
|
-
retry.interval = interval;
|
|
256
|
-
|
|
257
|
-
return setTimeoutPromise(interval).then(() => rekwest(url, options));
|
|
258
|
-
}
|
|
259
|
-
|
|
260
|
-
if (digest && !redirected && ex.body) {
|
|
261
|
-
ex.body = await ex.body();
|
|
262
|
-
}
|
|
263
|
-
|
|
264
|
-
if (!thenable) {
|
|
265
|
-
throw ex;
|
|
266
|
-
} else {
|
|
267
|
-
return ex;
|
|
268
|
-
}
|
|
269
|
-
}
|
|
39
|
+
return transfer(validation(options));
|
|
270
40
|
}
|
|
271
41
|
|
|
272
42
|
Reflect.defineProperty(rekwest, 'stream', {
|
|
273
43
|
enumerable: true,
|
|
274
44
|
value(...args) {
|
|
275
45
|
const options = preflight({
|
|
276
|
-
...merge(rekwest.defaults, {
|
|
46
|
+
...validation(merge(rekwest.defaults, {
|
|
277
47
|
headers: { [HTTP2_HEADER_CONTENT_TYPE]: APPLICATION_OCTET_STREAM },
|
|
278
|
-
}, sanitize(...args)),
|
|
279
|
-
redirect:
|
|
48
|
+
}, sanitize(...args))),
|
|
49
|
+
redirect: requestRedirect.manual,
|
|
280
50
|
});
|
|
281
51
|
|
|
282
52
|
const { h2, url } = options;
|
|
283
|
-
const { request } = (url.protocol === 'http:' ? http : https);
|
|
284
53
|
let client, req;
|
|
285
54
|
|
|
286
55
|
if (h2) {
|
|
287
56
|
client = http2.connect(url.origin, options);
|
|
288
57
|
req = client.request(options.headers, options);
|
|
289
58
|
} else {
|
|
59
|
+
const { request } = (url.protocol === 'http:' ? http : https);
|
|
60
|
+
|
|
290
61
|
req = request(url, options);
|
|
291
62
|
}
|
|
292
63
|
|
|
293
64
|
affix(client, req, options);
|
|
65
|
+
|
|
294
66
|
req.once('response', (res) => {
|
|
295
67
|
let headers;
|
|
296
68
|
|
|
@@ -308,6 +80,6 @@ Reflect.defineProperty(rekwest, 'stream', {
|
|
|
308
80
|
|
|
309
81
|
Reflect.defineProperty(rekwest, 'defaults', {
|
|
310
82
|
enumerable: true,
|
|
311
|
-
get() { return defaults; },
|
|
312
|
-
set(value) { defaults = merge(defaults, value); },
|
|
83
|
+
get() { return defaults.stash; },
|
|
84
|
+
set(value) { defaults.stash = merge(defaults.stash, value); },
|
|
313
85
|
});
|