rekwest 2.3.5 → 3.0.0
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 +30 -20
- package/dist/errors.js +8 -4
- package/dist/helpers.js +126 -70
- package/dist/index.js +142 -69
- package/package.json +9 -8
- package/src/ackn.mjs +33 -33
- package/src/cookies.mjs +24 -24
- package/src/errors.mjs +4 -2
- package/src/file.mjs +40 -40
- package/src/formdata.mjs +224 -224
- package/src/helpers.mjs +116 -61
- package/src/index.mjs +115 -66
package/README.md
CHANGED
|
@@ -8,12 +8,12 @@ and [http2.request](https://nodejs.org/api/http2.html#http2_clienthttp2session_r
|
|
|
8
8
|
|
|
9
9
|
* Fetch-alike
|
|
10
10
|
* Cool-beans config options (with defaults)
|
|
11
|
-
* Automatic
|
|
11
|
+
* Automatic HTTP/2 support (ALPN negotiation)
|
|
12
12
|
* Automatic or opt-in body parse (with non-UTF-8 charset decoding)
|
|
13
13
|
* Automatic and simplistic `Cookies` treatment (with built-in jar)
|
|
14
14
|
* Automatic decompression (with opt-in body compression)
|
|
15
15
|
* Built-in streamable `File` & `FormData` interfaces
|
|
16
|
-
* Support redirects with fine-grained tune-ups
|
|
16
|
+
* Support redirects & retries with fine-grained tune-ups
|
|
17
17
|
* Support all legit request body types (include blobs & streams)
|
|
18
18
|
* Support both CJS and ESM module systems
|
|
19
19
|
* Fully promise-able and pipe-able
|
|
@@ -120,54 +120,64 @@ console.log(res.body);
|
|
|
120
120
|
extra [http2.ClientSessionOptions](https://nodejs.org/api/http2.html#http2_http2_connect_authority_options_listener)
|
|
121
121
|
& [http2.ClientSessionRequestOptions](https://nodejs.org/api/http2.html#http2_clienthttp2session_request_headers_options)
|
|
122
122
|
and [tls.ConnectionOptions](https://nodejs.org/api/tls.html#tls_tls_connect_options_callback)
|
|
123
|
-
for
|
|
123
|
+
for HTTP/2 attunes
|
|
124
124
|
* `body` **{string | Array | AsyncIterator | Blob | Buffer | File | FromData | Iterator | Object | Readable |
|
|
125
|
-
Uint8Array | URLSearchParams}**
|
|
126
|
-
* `cookies` **{boolean | Array[
|
|
125
|
+
Uint8Array | URLSearchParams}** The body to send with the request
|
|
126
|
+
* `cookies` **{boolean | Array<[k, v]> | Cookies | Object | URLSearchParams}** `Default: true` The cookies to add to
|
|
127
127
|
the request
|
|
128
|
-
* `digest` **{boolean}** `Default: true`
|
|
129
|
-
* `follow` **{number}** `Default: 20`
|
|
130
|
-
* `h2` **{boolean}** `Default: false` Forces use of
|
|
131
|
-
* `headers` **{Object}**
|
|
132
|
-
* `
|
|
133
|
-
* `
|
|
134
|
-
* `
|
|
128
|
+
* `digest` **{boolean}** `Default: true` Controls whether to read the response stream or just add a mixin
|
|
129
|
+
* `follow` **{number}** `Default: 20` The number of redirects to follow
|
|
130
|
+
* `h2` **{boolean}** `Default: false` Forces the use of HTTP/2 protocol
|
|
131
|
+
* `headers` **{Object}** The headers to add to the request
|
|
132
|
+
* `maxRetryAfter` **{number}** The upper limit of `retry-after` header. If unset, it will use `timeout` value
|
|
133
|
+
* `parse` **{boolean}** `Default: true` Controls whether to parse response body or simply return a buffer
|
|
134
|
+
* `redirect` **{error | follow | manual}** `Default: follow` Controls the redirect flows
|
|
135
|
+
* `retry` **{Object}** Represents the retry options
|
|
136
|
+
* `attempts` **{number}** `Default: 0` The number of retry attempts
|
|
137
|
+
* `backoffStrategy` **{string}** `Default: interval * Math.log(Math.random() * (Math.E * Math.E - Math.E) + Math.E)`
|
|
138
|
+
The backoff strategy algorithm that increases logarithmically. To fixate set value to `interval * 1`
|
|
139
|
+
* `interval` **{number}** `Default: 1e3` The initial retry interval
|
|
140
|
+
* `retryAfter` **{boolean}** `Default: true` Controls `retry-after` header receptiveness
|
|
141
|
+
* `statusCodes` **{number[]}** `Default: [429, 503]` The list of status codes to retry on
|
|
142
|
+
* `thenable` **{boolean}** `Default: false` Controls the promise resolutions
|
|
143
|
+
* `timeout` **{number}** `Default: 3e5` The number of milliseconds a request can take before termination
|
|
135
144
|
* **Returns:** Promise that resolves to
|
|
136
145
|
extended [http.IncomingMessage](https://nodejs.org/api/http.html#http_class_http_incomingmessage)
|
|
137
146
|
or [http2.ClientHttp2Stream](https://nodejs.org/api/http2.html#http2_class_clienthttp2stream) which is respectively
|
|
138
147
|
readable and duplex streams
|
|
139
148
|
* if `degist: true` & `parse: true`
|
|
140
|
-
* `body` **{string | Array | Buffer | Object}**
|
|
149
|
+
* `body` **{string | Array | Buffer | Object}** The body based on its content type
|
|
141
150
|
* if `degist: false`
|
|
142
151
|
* `arrayBuffer` **{AsyncFunction}** Reads the response and returns **ArrayBuffer**
|
|
143
152
|
* `blob` **{AsyncFunction}** Reads the response and returns **Blob**
|
|
144
153
|
* `body` **{AsyncFunction}** Reads the response and returns **Buffer** if `parse: false`
|
|
145
154
|
* `json` **{AsyncFunction}** Reads the response and returns **Object**
|
|
146
155
|
* `text` **{AsyncFunction}** Reads the response and returns **String**
|
|
147
|
-
* `bodyUsed` **{boolean}**
|
|
148
|
-
* `cookies` **{undefined | Cookies}**
|
|
149
|
-
* `headers` **{Object}**
|
|
156
|
+
* `bodyUsed` **{boolean}** Indicates whether the response were read or not
|
|
157
|
+
* `cookies` **{undefined | Cookies}** The cookies sent and received with the response
|
|
158
|
+
* `headers` **{Object}** The headers received with the response
|
|
150
159
|
* `httpVersion` **{string}** Indicates protocol version negotiated with the server
|
|
151
160
|
* `ok` **{boolean}** Indicates if the response was successful (statusCode: **200-299**)
|
|
152
161
|
* `redirected` **{boolean}** Indicates if the response is the result of a redirect
|
|
153
162
|
* `statusCode` **{number}** Indicates the status code of the response
|
|
154
|
-
* `trailers` **{undefined | Object}**
|
|
163
|
+
* `trailers` **{undefined | Object}** The trailer headers received with the response
|
|
155
164
|
|
|
156
165
|
---
|
|
157
166
|
|
|
158
167
|
#### `rekwest.defaults`
|
|
159
168
|
|
|
160
|
-
|
|
169
|
+
The object to fulfill with default [options](#rekwesturl-options)
|
|
161
170
|
|
|
162
171
|
---
|
|
163
172
|
|
|
164
173
|
#### `rekwest.stream(url[, options])`
|
|
165
174
|
|
|
166
|
-
|
|
175
|
+
The method with limited functionality to use with streams and/or pipes
|
|
167
176
|
|
|
168
177
|
* No automata
|
|
169
178
|
* No redirects
|
|
170
|
-
* Pass `h2: true` in options to use
|
|
179
|
+
* Pass `h2: true` in options to use HTTP/2 protocol
|
|
180
|
+
* Or use `ackn({ url: URL })` method in advance to probe the available protocols
|
|
171
181
|
|
|
172
182
|
---
|
|
173
183
|
|
package/dist/errors.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
|
|
3
3
|
exports.__esModule = true;
|
|
4
|
-
exports.RequestError = void 0;
|
|
4
|
+
exports.TimeoutError = exports.RequestError = void 0;
|
|
5
5
|
|
|
6
6
|
class RequestError extends Error {
|
|
7
7
|
get [Symbol.toStringTag]() {
|
|
@@ -12,11 +12,15 @@ class RequestError extends Error {
|
|
|
12
12
|
return this[Symbol.toStringTag];
|
|
13
13
|
}
|
|
14
14
|
|
|
15
|
-
constructor(
|
|
16
|
-
super(
|
|
15
|
+
constructor(...args) {
|
|
16
|
+
super(...args);
|
|
17
17
|
Error.captureStackTrace(this, this.constructor);
|
|
18
18
|
}
|
|
19
19
|
|
|
20
20
|
}
|
|
21
21
|
|
|
22
|
-
exports.RequestError = RequestError;
|
|
22
|
+
exports.RequestError = RequestError;
|
|
23
|
+
|
|
24
|
+
class TimeoutError extends RequestError {}
|
|
25
|
+
|
|
26
|
+
exports.TimeoutError = TimeoutError;
|
package/dist/helpers.js
CHANGED
|
@@ -1,15 +1,13 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
|
|
3
3
|
exports.__esModule = true;
|
|
4
|
-
exports.
|
|
4
|
+
exports.redirects = exports.preflight = exports.mixin = exports.merge = exports.dispatch = exports.decompress = exports.compress = exports.affix = exports.admix = void 0;
|
|
5
5
|
exports.tap = tap;
|
|
6
6
|
exports.transform = void 0;
|
|
7
7
|
|
|
8
8
|
var _buffer = require("buffer");
|
|
9
9
|
|
|
10
|
-
var _http = require("
|
|
11
|
-
|
|
12
|
-
var _http2 = _interopRequireDefault(require("http2"));
|
|
10
|
+
var _http = _interopRequireDefault(require("http2"));
|
|
13
11
|
|
|
14
12
|
var _stream = require("stream");
|
|
15
13
|
|
|
@@ -19,6 +17,8 @@ var _zlib = _interopRequireDefault(require("zlib"));
|
|
|
19
17
|
|
|
20
18
|
var _cookies = require("./cookies.js");
|
|
21
19
|
|
|
20
|
+
var _errors = require("./errors.js");
|
|
21
|
+
|
|
22
22
|
var _file = require("./file.js");
|
|
23
23
|
|
|
24
24
|
var _formdata = require("./formdata.js");
|
|
@@ -38,9 +38,10 @@ const {
|
|
|
38
38
|
HTTP2_HEADER_METHOD,
|
|
39
39
|
HTTP2_HEADER_PATH,
|
|
40
40
|
HTTP2_HEADER_SCHEME,
|
|
41
|
+
HTTP2_HEADER_STATUS,
|
|
41
42
|
HTTP2_METHOD_GET,
|
|
42
43
|
HTTP2_METHOD_HEAD
|
|
43
|
-
} =
|
|
44
|
+
} = _http.default.constants;
|
|
44
45
|
const brotliCompress = (0, _util.promisify)(_zlib.default.brotliCompress);
|
|
45
46
|
const brotliDecompress = (0, _util.promisify)(_zlib.default.brotliDecompress);
|
|
46
47
|
const gzip = (0, _util.promisify)(_zlib.default.gzip);
|
|
@@ -48,6 +49,51 @@ const gunzip = (0, _util.promisify)(_zlib.default.gunzip);
|
|
|
48
49
|
const deflate = (0, _util.promisify)(_zlib.default.deflate);
|
|
49
50
|
const inflate = (0, _util.promisify)(_zlib.default.inflate);
|
|
50
51
|
|
|
52
|
+
const admix = (res, headers, options) => {
|
|
53
|
+
const {
|
|
54
|
+
h2
|
|
55
|
+
} = options;
|
|
56
|
+
|
|
57
|
+
if (h2) {
|
|
58
|
+
Reflect.defineProperty(res, 'headers', {
|
|
59
|
+
enumerable: true,
|
|
60
|
+
value: headers
|
|
61
|
+
});
|
|
62
|
+
Reflect.defineProperty(res, 'httpVersion', {
|
|
63
|
+
enumerable: true,
|
|
64
|
+
value: `${h2 + 1}.0`
|
|
65
|
+
});
|
|
66
|
+
Reflect.defineProperty(res, 'statusCode', {
|
|
67
|
+
enumerable: true,
|
|
68
|
+
value: headers[HTTP2_HEADER_STATUS]
|
|
69
|
+
});
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
Reflect.defineProperty(res, 'ok', {
|
|
73
|
+
enumerable: true,
|
|
74
|
+
value: /^2\d{2}$/.test(res.statusCode)
|
|
75
|
+
});
|
|
76
|
+
Reflect.defineProperty(res, 'redirected', {
|
|
77
|
+
enumerable: true,
|
|
78
|
+
value: !!options.redirected
|
|
79
|
+
});
|
|
80
|
+
};
|
|
81
|
+
|
|
82
|
+
exports.admix = admix;
|
|
83
|
+
|
|
84
|
+
const affix = (client, req, options) => {
|
|
85
|
+
req.once('end', () => client?.close());
|
|
86
|
+
req.once('timeout', () => req.destroy(new _errors.TimeoutError(`Timed out after ${options.timeout} ms.`)));
|
|
87
|
+
req.once('trailers', trailers => {
|
|
88
|
+
Reflect.defineProperty(req, 'trailers', {
|
|
89
|
+
enumerable: true,
|
|
90
|
+
value: trailers
|
|
91
|
+
});
|
|
92
|
+
});
|
|
93
|
+
};
|
|
94
|
+
|
|
95
|
+
exports.affix = affix;
|
|
96
|
+
|
|
51
97
|
const compress = (buf, encoding, {
|
|
52
98
|
async = false
|
|
53
99
|
} = {}) => {
|
|
@@ -129,69 +175,7 @@ const merge = (target = {}, ...rest) => {
|
|
|
129
175
|
|
|
130
176
|
exports.merge = merge;
|
|
131
177
|
|
|
132
|
-
const
|
|
133
|
-
const url = options.url = new URL(options.url);
|
|
134
|
-
const {
|
|
135
|
-
cookies,
|
|
136
|
-
h2 = false,
|
|
137
|
-
method = HTTP2_METHOD_GET,
|
|
138
|
-
headers,
|
|
139
|
-
redirected
|
|
140
|
-
} = options;
|
|
141
|
-
|
|
142
|
-
if (!h2) {
|
|
143
|
-
options.agent ??= url.protocol === 'http:' ? _http.globalAgent : void 0;
|
|
144
|
-
} else {
|
|
145
|
-
options.endStream = [HTTP2_METHOD_GET, HTTP2_METHOD_HEAD].includes(method);
|
|
146
|
-
}
|
|
147
|
-
|
|
148
|
-
if (cookies !== false) {
|
|
149
|
-
let cookie = _cookies.Cookies.jar.get(url.origin);
|
|
150
|
-
|
|
151
|
-
if (cookies === Object(cookies) && !redirected) {
|
|
152
|
-
if (cookie) {
|
|
153
|
-
new _cookies.Cookies(cookies).forEach(function (val, key) {
|
|
154
|
-
this.set(key, val);
|
|
155
|
-
}, cookie);
|
|
156
|
-
} else {
|
|
157
|
-
cookie = new _cookies.Cookies(cookies);
|
|
158
|
-
|
|
159
|
-
_cookies.Cookies.jar.set(url.origin, cookie);
|
|
160
|
-
}
|
|
161
|
-
}
|
|
162
|
-
|
|
163
|
-
options.headers = { ...(cookie && {
|
|
164
|
-
[HTTP2_HEADER_COOKIE]: cookie
|
|
165
|
-
}),
|
|
166
|
-
...headers
|
|
167
|
-
};
|
|
168
|
-
}
|
|
169
|
-
|
|
170
|
-
options.digest ??= true;
|
|
171
|
-
options.follow ??= 20;
|
|
172
|
-
options.h2 ??= h2;
|
|
173
|
-
options.headers = {
|
|
174
|
-
[HTTP2_HEADER_ACCEPT]: `${_mediatypes.APPLICATION_JSON}, ${_mediatypes.TEXT_PLAIN}, ${_mediatypes.WILDCARD}`,
|
|
175
|
-
[HTTP2_HEADER_ACCEPT_ENCODING]: 'br, deflate, gzip, identity',
|
|
176
|
-
...Object.entries(options.headers ?? {}).reduce((acc, [key, val]) => (acc[key.toLowerCase()] = val, acc), {}),
|
|
177
|
-
...(h2 && {
|
|
178
|
-
[HTTP2_HEADER_AUTHORITY]: url.host,
|
|
179
|
-
[HTTP2_HEADER_METHOD]: method,
|
|
180
|
-
[HTTP2_HEADER_PATH]: `${url.pathname}${url.search}`,
|
|
181
|
-
[HTTP2_HEADER_SCHEME]: url.protocol.replace(/\p{Punctuation}/gu, '')
|
|
182
|
-
})
|
|
183
|
-
};
|
|
184
|
-
options.method ??= method;
|
|
185
|
-
options.parse ??= true;
|
|
186
|
-
options.redirect ??= 'follow';
|
|
187
|
-
options.redirected ??= false;
|
|
188
|
-
options.thenable ??= false;
|
|
189
|
-
return options;
|
|
190
|
-
};
|
|
191
|
-
|
|
192
|
-
exports.preflight = preflight;
|
|
193
|
-
|
|
194
|
-
const premix = (res, {
|
|
178
|
+
const mixin = (res, {
|
|
195
179
|
digest = false,
|
|
196
180
|
parse = false
|
|
197
181
|
} = {}) => {
|
|
@@ -234,7 +218,7 @@ const premix = (res, {
|
|
|
234
218
|
enumerable: true,
|
|
235
219
|
value: async function () {
|
|
236
220
|
if (this.bodyUsed) {
|
|
237
|
-
throw new TypeError('Response stream already read');
|
|
221
|
+
throw new TypeError('Response stream already read.');
|
|
238
222
|
}
|
|
239
223
|
|
|
240
224
|
let spool = [];
|
|
@@ -279,7 +263,79 @@ const premix = (res, {
|
|
|
279
263
|
});
|
|
280
264
|
};
|
|
281
265
|
|
|
282
|
-
exports.
|
|
266
|
+
exports.mixin = mixin;
|
|
267
|
+
|
|
268
|
+
const preflight = options => {
|
|
269
|
+
const url = options.url = new URL(options.url);
|
|
270
|
+
const {
|
|
271
|
+
cookies,
|
|
272
|
+
h2 = false,
|
|
273
|
+
method = HTTP2_METHOD_GET,
|
|
274
|
+
headers,
|
|
275
|
+
redirected
|
|
276
|
+
} = options;
|
|
277
|
+
|
|
278
|
+
if (h2) {
|
|
279
|
+
options.endStream = [HTTP2_METHOD_GET, HTTP2_METHOD_HEAD].includes(method);
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
if (cookies !== false) {
|
|
283
|
+
let cookie = _cookies.Cookies.jar.get(url.origin);
|
|
284
|
+
|
|
285
|
+
if (cookies === Object(cookies) && !redirected) {
|
|
286
|
+
if (cookie) {
|
|
287
|
+
new _cookies.Cookies(cookies).forEach(function (val, key) {
|
|
288
|
+
this.set(key, val);
|
|
289
|
+
}, cookie);
|
|
290
|
+
} else {
|
|
291
|
+
cookie = new _cookies.Cookies(cookies);
|
|
292
|
+
|
|
293
|
+
_cookies.Cookies.jar.set(url.origin, cookie);
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
options.headers = { ...(cookie && {
|
|
298
|
+
[HTTP2_HEADER_COOKIE]: cookie
|
|
299
|
+
}),
|
|
300
|
+
...headers
|
|
301
|
+
};
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
options.digest ??= true;
|
|
305
|
+
options.follow ??= 20;
|
|
306
|
+
options.h2 ??= h2;
|
|
307
|
+
options.headers = {
|
|
308
|
+
[HTTP2_HEADER_ACCEPT]: `${_mediatypes.APPLICATION_JSON}, ${_mediatypes.TEXT_PLAIN}, ${_mediatypes.WILDCARD}`,
|
|
309
|
+
[HTTP2_HEADER_ACCEPT_ENCODING]: 'br, deflate, gzip, identity',
|
|
310
|
+
...Object.entries(options.headers ?? {}).reduce((acc, [key, val]) => (acc[key.toLowerCase()] = val, acc), {}),
|
|
311
|
+
...(h2 && {
|
|
312
|
+
[HTTP2_HEADER_AUTHORITY]: url.host,
|
|
313
|
+
[HTTP2_HEADER_METHOD]: method,
|
|
314
|
+
[HTTP2_HEADER_PATH]: `${url.pathname}${url.search}`,
|
|
315
|
+
[HTTP2_HEADER_SCHEME]: url.protocol.replace(/\p{Punctuation}/gu, '')
|
|
316
|
+
})
|
|
317
|
+
};
|
|
318
|
+
options.method ??= method;
|
|
319
|
+
options.parse ??= true;
|
|
320
|
+
options.redirect ??= redirects.follow;
|
|
321
|
+
|
|
322
|
+
if (!Object.values(redirects).includes(options.redirect)) {
|
|
323
|
+
options.createConnection?.().destroy();
|
|
324
|
+
throw new TypeError(`Failed to read the 'redirect' property from 'options': The provided value '${options.redirect}' is not a valid enum value.`);
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
options.redirected ??= false;
|
|
328
|
+
options.thenable ??= false;
|
|
329
|
+
return options;
|
|
330
|
+
};
|
|
331
|
+
|
|
332
|
+
exports.preflight = preflight;
|
|
333
|
+
const redirects = {
|
|
334
|
+
error: 'error',
|
|
335
|
+
follow: 'follow',
|
|
336
|
+
manual: 'manual'
|
|
337
|
+
};
|
|
338
|
+
exports.redirects = redirects;
|
|
283
339
|
|
|
284
340
|
async function* tap(value) {
|
|
285
341
|
if (Reflect.has(value, Symbol.asyncIterator)) {
|
package/dist/index.js
CHANGED
|
@@ -7,11 +7,15 @@ var _exportNames = {
|
|
|
7
7
|
exports.constants = void 0;
|
|
8
8
|
exports.default = rekwest;
|
|
9
9
|
|
|
10
|
-
var _http = _interopRequireDefault(require("
|
|
10
|
+
var _http = _interopRequireDefault(require("http"));
|
|
11
11
|
|
|
12
|
-
|
|
12
|
+
var _http2 = _interopRequireDefault(require("http2"));
|
|
13
13
|
|
|
14
|
-
|
|
14
|
+
exports.constants = _http2.constants;
|
|
15
|
+
|
|
16
|
+
var _https = _interopRequireDefault(require("https"));
|
|
17
|
+
|
|
18
|
+
var _promises = require("timers/promises");
|
|
15
19
|
|
|
16
20
|
var _ackn = require("./ackn.js");
|
|
17
21
|
|
|
@@ -75,29 +79,55 @@ const {
|
|
|
75
79
|
HTTP2_HEADER_CONTENT_LENGTH,
|
|
76
80
|
HTTP2_HEADER_CONTENT_TYPE,
|
|
77
81
|
HTTP2_HEADER_LOCATION,
|
|
82
|
+
HTTP2_HEADER_RETRY_AFTER,
|
|
78
83
|
HTTP2_HEADER_SET_COOKIE,
|
|
79
84
|
HTTP2_HEADER_STATUS,
|
|
80
85
|
HTTP2_METHOD_GET,
|
|
81
86
|
HTTP2_METHOD_HEAD,
|
|
82
87
|
HTTP_STATUS_BAD_REQUEST,
|
|
83
|
-
|
|
84
|
-
|
|
88
|
+
HTTP_STATUS_MOVED_PERMANENTLY,
|
|
89
|
+
HTTP_STATUS_SEE_OTHER,
|
|
90
|
+
HTTP_STATUS_SERVICE_UNAVAILABLE,
|
|
91
|
+
HTTP_STATUS_TOO_MANY_REQUESTS
|
|
92
|
+
} = _http2.default.constants;
|
|
93
|
+
const maxRetryAfter = Symbol('maxRetryAfter');
|
|
94
|
+
|
|
95
|
+
const maxRetryAfterError = (interval, options) => new _errors.RequestError(`Maximum '${HTTP2_HEADER_RETRY_AFTER}' limit exceeded: ${interval} ms.`, options);
|
|
96
|
+
|
|
97
|
+
let defaults = {
|
|
98
|
+
follow: 20,
|
|
99
|
+
|
|
100
|
+
get maxRetryAfter() {
|
|
101
|
+
return this[maxRetryAfter] ?? this.timeout;
|
|
102
|
+
},
|
|
103
|
+
|
|
104
|
+
set maxRetryAfter(value) {
|
|
105
|
+
this[maxRetryAfter] = value;
|
|
106
|
+
},
|
|
107
|
+
|
|
108
|
+
method: HTTP2_METHOD_GET,
|
|
109
|
+
retry: {
|
|
110
|
+
attempts: 0,
|
|
111
|
+
backoffStrategy: 'interval * Math.log(Math.random() * (Math.E * Math.E - Math.E) + Math.E)',
|
|
112
|
+
interval: 1e3,
|
|
113
|
+
retryAfter: true,
|
|
114
|
+
statusCodes: [HTTP_STATUS_TOO_MANY_REQUESTS, HTTP_STATUS_SERVICE_UNAVAILABLE]
|
|
115
|
+
},
|
|
116
|
+
timeout: 3e5
|
|
117
|
+
};
|
|
85
118
|
|
|
86
119
|
async function rekwest(url, options = {}) {
|
|
87
120
|
url = options.url = new URL(url);
|
|
88
121
|
|
|
89
122
|
if (!options.redirected) {
|
|
90
|
-
options = (0, _helpers.merge)(rekwest.defaults,
|
|
91
|
-
follow: 20,
|
|
92
|
-
method: HTTP2_METHOD_GET
|
|
93
|
-
}, options);
|
|
123
|
+
options = (0, _helpers.merge)(rekwest.defaults, options);
|
|
94
124
|
}
|
|
95
125
|
|
|
96
126
|
if (options.body && [HTTP2_METHOD_GET, HTTP2_METHOD_HEAD].includes(options.method)) {
|
|
97
|
-
throw new TypeError(`Request with ${HTTP2_METHOD_GET}/${HTTP2_METHOD_HEAD} method cannot have body
|
|
127
|
+
throw new TypeError(`Request with ${HTTP2_METHOD_GET}/${HTTP2_METHOD_HEAD} method cannot have body.`);
|
|
98
128
|
}
|
|
99
129
|
|
|
100
|
-
if (
|
|
130
|
+
if (options.follow === 0) {
|
|
101
131
|
throw new _errors.RequestError(`Maximum redirect reached at: ${url.href}`);
|
|
102
132
|
}
|
|
103
133
|
|
|
@@ -115,8 +145,14 @@ async function rekwest(url, options = {}) {
|
|
|
115
145
|
h2,
|
|
116
146
|
redirect,
|
|
117
147
|
redirected,
|
|
118
|
-
thenable
|
|
148
|
+
thenable,
|
|
149
|
+
url: {
|
|
150
|
+
protocol
|
|
151
|
+
}
|
|
119
152
|
} = options;
|
|
153
|
+
const {
|
|
154
|
+
request
|
|
155
|
+
} = protocol === 'http:' ? _http.default : _https.default;
|
|
120
156
|
let {
|
|
121
157
|
body
|
|
122
158
|
} = options;
|
|
@@ -125,32 +161,28 @@ async function rekwest(url, options = {}) {
|
|
|
125
161
|
body &&= (0, _helpers.transform)(body, options);
|
|
126
162
|
|
|
127
163
|
if (h2) {
|
|
128
|
-
client =
|
|
164
|
+
client = _http2.default.connect(url.origin, options);
|
|
129
165
|
req = client.request(options.headers, options);
|
|
130
166
|
} else {
|
|
131
|
-
req =
|
|
167
|
+
req = request(url, options);
|
|
132
168
|
}
|
|
133
169
|
|
|
134
|
-
|
|
170
|
+
(0, _helpers.affix)(client, req, options);
|
|
171
|
+
req.once('error', reject);
|
|
172
|
+
req.once('frameError', reject);
|
|
173
|
+
req.once('goaway', reject);
|
|
174
|
+
req.once('response', res => {
|
|
175
|
+
let headers;
|
|
176
|
+
|
|
135
177
|
if (h2) {
|
|
136
|
-
|
|
178
|
+
headers = res;
|
|
137
179
|
res = req;
|
|
138
|
-
Reflect.defineProperty(res, 'headers', {
|
|
139
|
-
enumerable: true,
|
|
140
|
-
value: headers
|
|
141
|
-
});
|
|
142
|
-
Reflect.defineProperty(res, 'httpVersion', {
|
|
143
|
-
enumerable: true,
|
|
144
|
-
value: `${h2 + 1}.0`
|
|
145
|
-
});
|
|
146
|
-
Reflect.defineProperty(res, 'statusCode', {
|
|
147
|
-
enumerable: true,
|
|
148
|
-
value: headers[HTTP2_HEADER_STATUS]
|
|
149
|
-
});
|
|
150
180
|
} else {
|
|
151
|
-
res.
|
|
181
|
+
res.once('error', reject);
|
|
152
182
|
}
|
|
153
183
|
|
|
184
|
+
(0, _helpers.admix)(res, headers, options);
|
|
185
|
+
|
|
154
186
|
if (cookies !== false && res.headers[HTTP2_HEADER_SET_COOKIE]) {
|
|
155
187
|
if (_cookies.Cookies.jar.has(url.origin)) {
|
|
156
188
|
new _cookies.Cookies(res.headers[HTTP2_HEADER_SET_COOKIE]).forEach(function (val, key) {
|
|
@@ -167,15 +199,15 @@ async function rekwest(url, options = {}) {
|
|
|
167
199
|
});
|
|
168
200
|
|
|
169
201
|
if (follow && /^3\d{2}$/.test(res.statusCode) && res.headers[HTTP2_HEADER_LOCATION]) {
|
|
170
|
-
if (redirect ===
|
|
171
|
-
res.emit('error', new _errors.RequestError(`Unexpected redirect, redirect mode is set to '${redirect}'
|
|
202
|
+
if (redirect === _helpers.redirects.error) {
|
|
203
|
+
res.emit('error', new _errors.RequestError(`Unexpected redirect, redirect mode is set to '${redirect}'.`));
|
|
172
204
|
}
|
|
173
205
|
|
|
174
|
-
if (redirect ===
|
|
206
|
+
if (redirect === _helpers.redirects.follow) {
|
|
175
207
|
options.url = new URL(res.headers[HTTP2_HEADER_LOCATION], url).href;
|
|
176
208
|
|
|
177
209
|
if (res.statusCode !== HTTP_STATUS_SEE_OTHER && body === Object(body) && body.pipe?.constructor === Function) {
|
|
178
|
-
res.emit('error', new _errors.RequestError(`Unable to ${redirect} redirect with body as readable stream
|
|
210
|
+
res.emit('error', new _errors.RequestError(`Unable to ${redirect} redirect with body as readable stream.`));
|
|
179
211
|
}
|
|
180
212
|
|
|
181
213
|
options.follow--;
|
|
@@ -187,37 +219,29 @@ async function rekwest(url, options = {}) {
|
|
|
187
219
|
}
|
|
188
220
|
|
|
189
221
|
Reflect.set(options, 'redirected', true);
|
|
222
|
+
|
|
223
|
+
if (res.statusCode === HTTP_STATUS_MOVED_PERMANENTLY && res.headers[HTTP2_HEADER_RETRY_AFTER]) {
|
|
224
|
+
let interval = res.headers[HTTP2_HEADER_RETRY_AFTER];
|
|
225
|
+
interval = Number(interval) * 1000 || new Date(interval) - Date.now();
|
|
226
|
+
|
|
227
|
+
if (interval > options.maxRetryAfter) {
|
|
228
|
+
res.emit('error', maxRetryAfterError(interval, {
|
|
229
|
+
cause: (0, _helpers.mixin)(res, options)
|
|
230
|
+
}));
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
return (0, _promises.setTimeout)(interval).then(() => rekwest(options.url, options).then(resolve, reject));
|
|
234
|
+
}
|
|
235
|
+
|
|
190
236
|
return rekwest(options.url, options).then(resolve, reject);
|
|
191
237
|
}
|
|
192
238
|
}
|
|
193
239
|
|
|
194
|
-
Reflect.defineProperty(res, 'ok', {
|
|
195
|
-
enumerable: true,
|
|
196
|
-
value: /^2\d{2}$/.test(res.statusCode)
|
|
197
|
-
});
|
|
198
|
-
Reflect.defineProperty(res, 'redirected', {
|
|
199
|
-
enumerable: true,
|
|
200
|
-
value: options.redirected
|
|
201
|
-
});
|
|
202
|
-
|
|
203
240
|
if (res.statusCode >= HTTP_STATUS_BAD_REQUEST) {
|
|
204
|
-
return reject((0, _helpers.
|
|
241
|
+
return reject((0, _helpers.mixin)(res, options));
|
|
205
242
|
}
|
|
206
243
|
|
|
207
|
-
resolve((0, _helpers.
|
|
208
|
-
});
|
|
209
|
-
req.on('end', () => {
|
|
210
|
-
client?.close();
|
|
211
|
-
});
|
|
212
|
-
req.on('error', reject);
|
|
213
|
-
req.on('frameError', reject);
|
|
214
|
-
req.on('goaway', reject);
|
|
215
|
-
req.on('timeout', req.destroy);
|
|
216
|
-
req.on('trailers', trailers => {
|
|
217
|
-
Reflect.defineProperty(req, 'trailers', {
|
|
218
|
-
enumerable: true,
|
|
219
|
-
value: trailers
|
|
220
|
-
});
|
|
244
|
+
resolve((0, _helpers.mixin)(res, options));
|
|
221
245
|
});
|
|
222
246
|
(0, _helpers.dispatch)(req, { ...options,
|
|
223
247
|
body
|
|
@@ -233,6 +257,29 @@ async function rekwest(url, options = {}) {
|
|
|
233
257
|
|
|
234
258
|
return res;
|
|
235
259
|
} catch (ex) {
|
|
260
|
+
if (options.retry?.attempts && options.retry?.statusCodes.includes(ex.statusCode)) {
|
|
261
|
+
let {
|
|
262
|
+
interval
|
|
263
|
+
} = options.retry;
|
|
264
|
+
|
|
265
|
+
if (options.retry.retryAfter && ex.headers[HTTP2_HEADER_RETRY_AFTER]) {
|
|
266
|
+
interval = ex.headers[HTTP2_HEADER_RETRY_AFTER];
|
|
267
|
+
interval = Number(interval) * 1000 || new Date(interval) - Date.now();
|
|
268
|
+
|
|
269
|
+
if (interval > options.maxRetryAfter) {
|
|
270
|
+
throw maxRetryAfterError(interval, {
|
|
271
|
+
cause: ex
|
|
272
|
+
});
|
|
273
|
+
}
|
|
274
|
+
} else {
|
|
275
|
+
interval = new Function('interval', `return Math.ceil(${options.retry.backoffStrategy});`)(interval);
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
options.retry.attempts--;
|
|
279
|
+
options.retry.interval = interval;
|
|
280
|
+
return (0, _promises.setTimeout)(interval).then(() => rekwest(url, options));
|
|
281
|
+
}
|
|
282
|
+
|
|
236
283
|
if (digest && !redirected && ex.body) {
|
|
237
284
|
ex.body = await ex.body();
|
|
238
285
|
}
|
|
@@ -254,24 +301,50 @@ Reflect.defineProperty(rekwest, 'stream', {
|
|
|
254
301
|
headers: {
|
|
255
302
|
[HTTP2_HEADER_CONTENT_TYPE]: _mediatypes.APPLICATION_OCTET_STREAM
|
|
256
303
|
}
|
|
257
|
-
}, options)
|
|
304
|
+
}, options),
|
|
305
|
+
redirect: _helpers.redirects.manual
|
|
258
306
|
});
|
|
307
|
+
const {
|
|
308
|
+
h2,
|
|
309
|
+
url: {
|
|
310
|
+
protocol
|
|
311
|
+
}
|
|
312
|
+
} = options;
|
|
313
|
+
const {
|
|
314
|
+
request
|
|
315
|
+
} = protocol === 'http:' ? _http.default : _https.default;
|
|
316
|
+
let client, req;
|
|
259
317
|
|
|
260
|
-
if (
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
req.
|
|
265
|
-
client.close();
|
|
266
|
-
});
|
|
267
|
-
return req;
|
|
318
|
+
if (h2) {
|
|
319
|
+
client = _http2.default.connect(url.origin, options);
|
|
320
|
+
req = client.request(options.headers, options);
|
|
321
|
+
} else {
|
|
322
|
+
req = request(options.url, options);
|
|
268
323
|
}
|
|
269
324
|
|
|
270
|
-
|
|
325
|
+
(0, _helpers.affix)(client, req, options);
|
|
326
|
+
req.once('response', res => {
|
|
327
|
+
let headers;
|
|
328
|
+
|
|
329
|
+
if (h2) {
|
|
330
|
+
headers = res;
|
|
331
|
+
res = req;
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
(0, _helpers.admix)(res, headers, options);
|
|
335
|
+
});
|
|
336
|
+
return req;
|
|
271
337
|
}
|
|
272
338
|
});
|
|
273
339
|
Reflect.defineProperty(rekwest, 'defaults', {
|
|
274
340
|
enumerable: true,
|
|
275
|
-
|
|
276
|
-
|
|
341
|
+
|
|
342
|
+
get() {
|
|
343
|
+
return defaults;
|
|
344
|
+
},
|
|
345
|
+
|
|
346
|
+
set(value) {
|
|
347
|
+
defaults = (0, _helpers.merge)(defaults, value);
|
|
348
|
+
}
|
|
349
|
+
|
|
277
350
|
});
|