rekwest 2.4.0 → 3.1.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 -21
- package/dist/errors.js +8 -4
- package/dist/helpers.js +124 -74
- package/dist/index.js +120 -60
- package/package.json +7 -7
- 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 +377 -331
- package/src/index.mjs +105 -62
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,63 @@ 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
|
|
124
|
-
* `body` **{string | Array |
|
|
125
|
-
|
|
126
|
-
* `cookies` **{boolean | Array[
|
|
123
|
+
for HTTP/2 attunes
|
|
124
|
+
* `body` **{string | Array | ArrayBuffer | ArrayBufferView | AsyncIterator | Blob | Buffer | DataView | File |
|
|
125
|
+
FromData | Iterator | Object | Readable | SharedArrayBuffer | 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 simply 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
|
|
171
180
|
* Or use `ackn({ url: URL })` method in advance to probe the available protocols
|
|
172
181
|
|
|
173
182
|
---
|
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,7 +1,7 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
|
|
3
3
|
exports.__esModule = true;
|
|
4
|
-
exports.redirects = 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
|
|
|
@@ -17,6 +17,8 @@ var _zlib = _interopRequireDefault(require("zlib"));
|
|
|
17
17
|
|
|
18
18
|
var _cookies = require("./cookies.js");
|
|
19
19
|
|
|
20
|
+
var _errors = require("./errors.js");
|
|
21
|
+
|
|
20
22
|
var _file = require("./file.js");
|
|
21
23
|
|
|
22
24
|
var _formdata = require("./formdata.js");
|
|
@@ -36,6 +38,7 @@ const {
|
|
|
36
38
|
HTTP2_HEADER_METHOD,
|
|
37
39
|
HTTP2_HEADER_PATH,
|
|
38
40
|
HTTP2_HEADER_SCHEME,
|
|
41
|
+
HTTP2_HEADER_STATUS,
|
|
39
42
|
HTTP2_METHOD_GET,
|
|
40
43
|
HTTP2_METHOD_HEAD
|
|
41
44
|
} = _http.default.constants;
|
|
@@ -46,6 +49,51 @@ const gunzip = (0, _util.promisify)(_zlib.default.gunzip);
|
|
|
46
49
|
const deflate = (0, _util.promisify)(_zlib.default.deflate);
|
|
47
50
|
const inflate = (0, _util.promisify)(_zlib.default.inflate);
|
|
48
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
|
+
|
|
49
97
|
const compress = (buf, encoding, {
|
|
50
98
|
async = false
|
|
51
99
|
} = {}) => {
|
|
@@ -78,10 +126,6 @@ const dispatch = (req, {
|
|
|
78
126
|
body,
|
|
79
127
|
headers
|
|
80
128
|
}) => {
|
|
81
|
-
if (_util.types.isUint8Array(body)) {
|
|
82
|
-
return req.end(body);
|
|
83
|
-
}
|
|
84
|
-
|
|
85
129
|
if (body === Object(body) && !Buffer.isBuffer(body)) {
|
|
86
130
|
if (body.pipe?.constructor !== Function && (Reflect.has(body, Symbol.asyncIterator) || Reflect.has(body, Symbol.iterator))) {
|
|
87
131
|
body = _stream.Readable.from(body);
|
|
@@ -127,73 +171,7 @@ const merge = (target = {}, ...rest) => {
|
|
|
127
171
|
|
|
128
172
|
exports.merge = merge;
|
|
129
173
|
|
|
130
|
-
const
|
|
131
|
-
const url = options.url = new URL(options.url);
|
|
132
|
-
const {
|
|
133
|
-
cookies,
|
|
134
|
-
h2 = false,
|
|
135
|
-
method = HTTP2_METHOD_GET,
|
|
136
|
-
headers,
|
|
137
|
-
redirected
|
|
138
|
-
} = options;
|
|
139
|
-
|
|
140
|
-
if (h2) {
|
|
141
|
-
options.endStream = [HTTP2_METHOD_GET, HTTP2_METHOD_HEAD].includes(method);
|
|
142
|
-
}
|
|
143
|
-
|
|
144
|
-
if (cookies !== false) {
|
|
145
|
-
let cookie = _cookies.Cookies.jar.get(url.origin);
|
|
146
|
-
|
|
147
|
-
if (cookies === Object(cookies) && !redirected) {
|
|
148
|
-
if (cookie) {
|
|
149
|
-
new _cookies.Cookies(cookies).forEach(function (val, key) {
|
|
150
|
-
this.set(key, val);
|
|
151
|
-
}, cookie);
|
|
152
|
-
} else {
|
|
153
|
-
cookie = new _cookies.Cookies(cookies);
|
|
154
|
-
|
|
155
|
-
_cookies.Cookies.jar.set(url.origin, cookie);
|
|
156
|
-
}
|
|
157
|
-
}
|
|
158
|
-
|
|
159
|
-
options.headers = { ...(cookie && {
|
|
160
|
-
[HTTP2_HEADER_COOKIE]: cookie
|
|
161
|
-
}),
|
|
162
|
-
...headers
|
|
163
|
-
};
|
|
164
|
-
}
|
|
165
|
-
|
|
166
|
-
options.digest ??= true;
|
|
167
|
-
options.follow ??= 20;
|
|
168
|
-
options.h2 ??= h2;
|
|
169
|
-
options.headers = {
|
|
170
|
-
[HTTP2_HEADER_ACCEPT]: `${_mediatypes.APPLICATION_JSON}, ${_mediatypes.TEXT_PLAIN}, ${_mediatypes.WILDCARD}`,
|
|
171
|
-
[HTTP2_HEADER_ACCEPT_ENCODING]: 'br, deflate, gzip, identity',
|
|
172
|
-
...Object.entries(options.headers ?? {}).reduce((acc, [key, val]) => (acc[key.toLowerCase()] = val, acc), {}),
|
|
173
|
-
...(h2 && {
|
|
174
|
-
[HTTP2_HEADER_AUTHORITY]: url.host,
|
|
175
|
-
[HTTP2_HEADER_METHOD]: method,
|
|
176
|
-
[HTTP2_HEADER_PATH]: `${url.pathname}${url.search}`,
|
|
177
|
-
[HTTP2_HEADER_SCHEME]: url.protocol.replace(/\p{Punctuation}/gu, '')
|
|
178
|
-
})
|
|
179
|
-
};
|
|
180
|
-
options.method ??= method;
|
|
181
|
-
options.parse ??= true;
|
|
182
|
-
options.redirect ??= redirects.follow;
|
|
183
|
-
|
|
184
|
-
if (!Object.values(redirects).includes(options.redirect)) {
|
|
185
|
-
options.createConnection?.().destroy();
|
|
186
|
-
throw new TypeError(`Failed to read the 'redirect' property from 'options': The provided value '${options.redirect}' is not a valid enum value.`);
|
|
187
|
-
}
|
|
188
|
-
|
|
189
|
-
options.redirected ??= false;
|
|
190
|
-
options.thenable ??= false;
|
|
191
|
-
return options;
|
|
192
|
-
};
|
|
193
|
-
|
|
194
|
-
exports.preflight = preflight;
|
|
195
|
-
|
|
196
|
-
const premix = (res, {
|
|
174
|
+
const mixin = (res, {
|
|
197
175
|
digest = false,
|
|
198
176
|
parse = false
|
|
199
177
|
} = {}) => {
|
|
@@ -281,7 +259,73 @@ const premix = (res, {
|
|
|
281
259
|
});
|
|
282
260
|
};
|
|
283
261
|
|
|
284
|
-
exports.
|
|
262
|
+
exports.mixin = mixin;
|
|
263
|
+
|
|
264
|
+
const preflight = options => {
|
|
265
|
+
const url = options.url = new URL(options.url);
|
|
266
|
+
const {
|
|
267
|
+
cookies,
|
|
268
|
+
h2 = false,
|
|
269
|
+
method = HTTP2_METHOD_GET,
|
|
270
|
+
headers,
|
|
271
|
+
redirected
|
|
272
|
+
} = options;
|
|
273
|
+
|
|
274
|
+
if (h2) {
|
|
275
|
+
options.endStream = [HTTP2_METHOD_GET, HTTP2_METHOD_HEAD].includes(method);
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
if (cookies !== false) {
|
|
279
|
+
let cookie = _cookies.Cookies.jar.get(url.origin);
|
|
280
|
+
|
|
281
|
+
if (cookies === Object(cookies) && !redirected) {
|
|
282
|
+
if (cookie) {
|
|
283
|
+
new _cookies.Cookies(cookies).forEach(function (val, key) {
|
|
284
|
+
this.set(key, val);
|
|
285
|
+
}, cookie);
|
|
286
|
+
} else {
|
|
287
|
+
cookie = new _cookies.Cookies(cookies);
|
|
288
|
+
|
|
289
|
+
_cookies.Cookies.jar.set(url.origin, cookie);
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
options.headers = { ...(cookie && {
|
|
294
|
+
[HTTP2_HEADER_COOKIE]: cookie
|
|
295
|
+
}),
|
|
296
|
+
...headers
|
|
297
|
+
};
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
options.digest ??= true;
|
|
301
|
+
options.follow ??= 20;
|
|
302
|
+
options.h2 ??= h2;
|
|
303
|
+
options.headers = {
|
|
304
|
+
[HTTP2_HEADER_ACCEPT]: `${_mediatypes.APPLICATION_JSON}, ${_mediatypes.TEXT_PLAIN}, ${_mediatypes.WILDCARD}`,
|
|
305
|
+
[HTTP2_HEADER_ACCEPT_ENCODING]: 'br, deflate, gzip, identity',
|
|
306
|
+
...Object.entries(options.headers ?? {}).reduce((acc, [key, val]) => (acc[key.toLowerCase()] = val, acc), {}),
|
|
307
|
+
...(h2 && {
|
|
308
|
+
[HTTP2_HEADER_AUTHORITY]: url.host,
|
|
309
|
+
[HTTP2_HEADER_METHOD]: method,
|
|
310
|
+
[HTTP2_HEADER_PATH]: `${url.pathname}${url.search}`,
|
|
311
|
+
[HTTP2_HEADER_SCHEME]: url.protocol.replace(/\p{Punctuation}/gu, '')
|
|
312
|
+
})
|
|
313
|
+
};
|
|
314
|
+
options.method ??= method;
|
|
315
|
+
options.parse ??= true;
|
|
316
|
+
options.redirect ??= redirects.follow;
|
|
317
|
+
|
|
318
|
+
if (!Object.values(redirects).includes(options.redirect)) {
|
|
319
|
+
options.createConnection?.().destroy();
|
|
320
|
+
throw new TypeError(`Failed to read the 'redirect' property from 'options': The provided value '${options.redirect}' is not a valid enum value.`);
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
options.redirected ??= false;
|
|
324
|
+
options.thenable ??= false;
|
|
325
|
+
return options;
|
|
326
|
+
};
|
|
327
|
+
|
|
328
|
+
exports.preflight = preflight;
|
|
285
329
|
const redirects = {
|
|
286
330
|
error: 'error',
|
|
287
331
|
follow: 'follow',
|
|
@@ -302,6 +346,12 @@ async function* tap(value) {
|
|
|
302
346
|
const transform = (body, options) => {
|
|
303
347
|
let headers = {};
|
|
304
348
|
|
|
349
|
+
if (_util.types.isAnyArrayBuffer(body) && !Buffer.isBuffer(body)) {
|
|
350
|
+
body = Buffer.from(body);
|
|
351
|
+
} else if (_util.types.isArrayBufferView(body) && !Buffer.isBuffer(body)) {
|
|
352
|
+
body = Buffer.from(body.buffer, body.byteOffset, body.byteLength);
|
|
353
|
+
}
|
|
354
|
+
|
|
305
355
|
if (_file.File.alike(body)) {
|
|
306
356
|
headers = {
|
|
307
357
|
[HTTP2_HEADER_CONTENT_LENGTH]: body.size,
|
|
@@ -326,7 +376,7 @@ const transform = (body, options) => {
|
|
|
326
376
|
body = JSON.stringify(body);
|
|
327
377
|
}
|
|
328
378
|
|
|
329
|
-
if (
|
|
379
|
+
if (Buffer.isBuffer(body) || body !== Object(body)) {
|
|
330
380
|
if (options.headers[HTTP2_HEADER_CONTENT_ENCODING]) {
|
|
331
381
|
body = compress(body, options.headers[HTTP2_HEADER_CONTENT_ENCODING]);
|
|
332
382
|
}
|
package/dist/index.js
CHANGED
|
@@ -15,6 +15,8 @@ exports.constants = _http2.constants;
|
|
|
15
15
|
|
|
16
16
|
var _https = _interopRequireDefault(require("https"));
|
|
17
17
|
|
|
18
|
+
var _promises = require("timers/promises");
|
|
19
|
+
|
|
18
20
|
var _ackn = require("./ackn.js");
|
|
19
21
|
|
|
20
22
|
Object.keys(_ackn).forEach(function (key) {
|
|
@@ -77,29 +79,54 @@ const {
|
|
|
77
79
|
HTTP2_HEADER_CONTENT_LENGTH,
|
|
78
80
|
HTTP2_HEADER_CONTENT_TYPE,
|
|
79
81
|
HTTP2_HEADER_LOCATION,
|
|
82
|
+
HTTP2_HEADER_RETRY_AFTER,
|
|
80
83
|
HTTP2_HEADER_SET_COOKIE,
|
|
81
|
-
HTTP2_HEADER_STATUS,
|
|
82
84
|
HTTP2_METHOD_GET,
|
|
83
85
|
HTTP2_METHOD_HEAD,
|
|
84
86
|
HTTP_STATUS_BAD_REQUEST,
|
|
85
|
-
|
|
87
|
+
HTTP_STATUS_MOVED_PERMANENTLY,
|
|
88
|
+
HTTP_STATUS_SEE_OTHER,
|
|
89
|
+
HTTP_STATUS_SERVICE_UNAVAILABLE,
|
|
90
|
+
HTTP_STATUS_TOO_MANY_REQUESTS
|
|
86
91
|
} = _http2.default.constants;
|
|
92
|
+
const maxRetryAfter = Symbol('maxRetryAfter');
|
|
93
|
+
|
|
94
|
+
const maxRetryAfterError = (interval, options) => new _errors.RequestError(`Maximum '${HTTP2_HEADER_RETRY_AFTER}' limit exceeded: ${interval} ms.`, options);
|
|
95
|
+
|
|
96
|
+
let defaults = {
|
|
97
|
+
follow: 20,
|
|
98
|
+
|
|
99
|
+
get maxRetryAfter() {
|
|
100
|
+
return this[maxRetryAfter] ?? this.timeout;
|
|
101
|
+
},
|
|
102
|
+
|
|
103
|
+
set maxRetryAfter(value) {
|
|
104
|
+
this[maxRetryAfter] = value;
|
|
105
|
+
},
|
|
106
|
+
|
|
107
|
+
method: HTTP2_METHOD_GET,
|
|
108
|
+
retry: {
|
|
109
|
+
attempts: 0,
|
|
110
|
+
backoffStrategy: 'interval * Math.log(Math.random() * (Math.E * Math.E - Math.E) + Math.E)',
|
|
111
|
+
interval: 1e3,
|
|
112
|
+
retryAfter: true,
|
|
113
|
+
statusCodes: [HTTP_STATUS_TOO_MANY_REQUESTS, HTTP_STATUS_SERVICE_UNAVAILABLE]
|
|
114
|
+
},
|
|
115
|
+
timeout: 3e5
|
|
116
|
+
};
|
|
87
117
|
|
|
88
118
|
async function rekwest(url, options = {}) {
|
|
89
119
|
url = options.url = new URL(url);
|
|
90
120
|
|
|
91
121
|
if (!options.redirected) {
|
|
92
|
-
options = (0, _helpers.merge)(rekwest.defaults,
|
|
93
|
-
follow: 20,
|
|
94
|
-
method: HTTP2_METHOD_GET
|
|
95
|
-
}, options);
|
|
122
|
+
options = (0, _helpers.merge)(rekwest.defaults, options);
|
|
96
123
|
}
|
|
97
124
|
|
|
98
125
|
if (options.body && [HTTP2_METHOD_GET, HTTP2_METHOD_HEAD].includes(options.method)) {
|
|
99
126
|
throw new TypeError(`Request with ${HTTP2_METHOD_GET}/${HTTP2_METHOD_HEAD} method cannot have body.`);
|
|
100
127
|
}
|
|
101
128
|
|
|
102
|
-
if (
|
|
129
|
+
if (options.follow === 0) {
|
|
103
130
|
throw new _errors.RequestError(`Maximum redirect reached at: ${url.href}`);
|
|
104
131
|
}
|
|
105
132
|
|
|
@@ -139,26 +166,22 @@ async function rekwest(url, options = {}) {
|
|
|
139
166
|
req = request(url, options);
|
|
140
167
|
}
|
|
141
168
|
|
|
142
|
-
|
|
169
|
+
(0, _helpers.affix)(client, req, options);
|
|
170
|
+
req.once('error', reject);
|
|
171
|
+
req.once('frameError', reject);
|
|
172
|
+
req.once('goaway', reject);
|
|
173
|
+
req.once('response', res => {
|
|
174
|
+
let headers;
|
|
175
|
+
|
|
143
176
|
if (h2) {
|
|
144
|
-
|
|
177
|
+
headers = res;
|
|
145
178
|
res = req;
|
|
146
|
-
Reflect.defineProperty(res, 'headers', {
|
|
147
|
-
enumerable: true,
|
|
148
|
-
value: headers
|
|
149
|
-
});
|
|
150
|
-
Reflect.defineProperty(res, 'httpVersion', {
|
|
151
|
-
enumerable: true,
|
|
152
|
-
value: `${h2 + 1}.0`
|
|
153
|
-
});
|
|
154
|
-
Reflect.defineProperty(res, 'statusCode', {
|
|
155
|
-
enumerable: true,
|
|
156
|
-
value: headers[HTTP2_HEADER_STATUS]
|
|
157
|
-
});
|
|
158
179
|
} else {
|
|
159
|
-
res.
|
|
180
|
+
res.once('error', reject);
|
|
160
181
|
}
|
|
161
182
|
|
|
183
|
+
(0, _helpers.admix)(res, headers, options);
|
|
184
|
+
|
|
162
185
|
if (cookies !== false && res.headers[HTTP2_HEADER_SET_COOKIE]) {
|
|
163
186
|
if (_cookies.Cookies.jar.has(url.origin)) {
|
|
164
187
|
new _cookies.Cookies(res.headers[HTTP2_HEADER_SET_COOKIE]).forEach(function (val, key) {
|
|
@@ -195,37 +218,29 @@ async function rekwest(url, options = {}) {
|
|
|
195
218
|
}
|
|
196
219
|
|
|
197
220
|
Reflect.set(options, 'redirected', true);
|
|
221
|
+
|
|
222
|
+
if (res.statusCode === HTTP_STATUS_MOVED_PERMANENTLY && res.headers[HTTP2_HEADER_RETRY_AFTER]) {
|
|
223
|
+
let interval = res.headers[HTTP2_HEADER_RETRY_AFTER];
|
|
224
|
+
interval = Number(interval) * 1000 || new Date(interval) - Date.now();
|
|
225
|
+
|
|
226
|
+
if (interval > options.maxRetryAfter) {
|
|
227
|
+
res.emit('error', maxRetryAfterError(interval, {
|
|
228
|
+
cause: (0, _helpers.mixin)(res, options)
|
|
229
|
+
}));
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
return (0, _promises.setTimeout)(interval).then(() => rekwest(options.url, options).then(resolve, reject));
|
|
233
|
+
}
|
|
234
|
+
|
|
198
235
|
return rekwest(options.url, options).then(resolve, reject);
|
|
199
236
|
}
|
|
200
237
|
}
|
|
201
238
|
|
|
202
|
-
Reflect.defineProperty(res, 'ok', {
|
|
203
|
-
enumerable: true,
|
|
204
|
-
value: /^2\d{2}$/.test(res.statusCode)
|
|
205
|
-
});
|
|
206
|
-
Reflect.defineProperty(res, 'redirected', {
|
|
207
|
-
enumerable: true,
|
|
208
|
-
value: options.redirected
|
|
209
|
-
});
|
|
210
|
-
|
|
211
239
|
if (res.statusCode >= HTTP_STATUS_BAD_REQUEST) {
|
|
212
|
-
return reject((0, _helpers.
|
|
240
|
+
return reject((0, _helpers.mixin)(res, options));
|
|
213
241
|
}
|
|
214
242
|
|
|
215
|
-
resolve((0, _helpers.
|
|
216
|
-
});
|
|
217
|
-
req.on('end', () => {
|
|
218
|
-
client?.close();
|
|
219
|
-
});
|
|
220
|
-
req.on('error', reject);
|
|
221
|
-
req.on('frameError', reject);
|
|
222
|
-
req.on('goaway', reject);
|
|
223
|
-
req.on('timeout', req.destroy);
|
|
224
|
-
req.on('trailers', trailers => {
|
|
225
|
-
Reflect.defineProperty(req, 'trailers', {
|
|
226
|
-
enumerable: true,
|
|
227
|
-
value: trailers
|
|
228
|
-
});
|
|
243
|
+
resolve((0, _helpers.mixin)(res, options));
|
|
229
244
|
});
|
|
230
245
|
(0, _helpers.dispatch)(req, { ...options,
|
|
231
246
|
body
|
|
@@ -241,6 +256,34 @@ async function rekwest(url, options = {}) {
|
|
|
241
256
|
|
|
242
257
|
return res;
|
|
243
258
|
} catch (ex) {
|
|
259
|
+
const {
|
|
260
|
+
maxRetryAfter,
|
|
261
|
+
retry
|
|
262
|
+
} = options;
|
|
263
|
+
|
|
264
|
+
if (retry?.attempts && retry?.statusCodes.includes(ex.statusCode)) {
|
|
265
|
+
let {
|
|
266
|
+
interval
|
|
267
|
+
} = retry;
|
|
268
|
+
|
|
269
|
+
if (retry.retryAfter && ex.headers[HTTP2_HEADER_RETRY_AFTER]) {
|
|
270
|
+
interval = ex.headers[HTTP2_HEADER_RETRY_AFTER];
|
|
271
|
+
interval = Number(interval) * 1000 || new Date(interval) - Date.now();
|
|
272
|
+
|
|
273
|
+
if (interval > maxRetryAfter) {
|
|
274
|
+
throw maxRetryAfterError(interval, {
|
|
275
|
+
cause: ex
|
|
276
|
+
});
|
|
277
|
+
}
|
|
278
|
+
} else {
|
|
279
|
+
interval = new Function('interval', `return Math.ceil(${retry.backoffStrategy});`)(interval);
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
retry.attempts--;
|
|
283
|
+
retry.interval = interval;
|
|
284
|
+
return (0, _promises.setTimeout)(interval).then(() => rekwest(url, options));
|
|
285
|
+
}
|
|
286
|
+
|
|
244
287
|
if (digest && !redirected && ex.body) {
|
|
245
288
|
ex.body = await ex.body();
|
|
246
289
|
}
|
|
@@ -265,18 +308,8 @@ Reflect.defineProperty(rekwest, 'stream', {
|
|
|
265
308
|
}, options),
|
|
266
309
|
redirect: _helpers.redirects.manual
|
|
267
310
|
});
|
|
268
|
-
|
|
269
|
-
if (options.h2) {
|
|
270
|
-
const client = _http2.default.connect(url.origin, options);
|
|
271
|
-
|
|
272
|
-
const req = client.request(options.headers, options);
|
|
273
|
-
req.on('end', () => {
|
|
274
|
-
client.close();
|
|
275
|
-
});
|
|
276
|
-
return req;
|
|
277
|
-
}
|
|
278
|
-
|
|
279
311
|
const {
|
|
312
|
+
h2,
|
|
280
313
|
url: {
|
|
281
314
|
protocol
|
|
282
315
|
}
|
|
@@ -284,11 +317,38 @@ Reflect.defineProperty(rekwest, 'stream', {
|
|
|
284
317
|
const {
|
|
285
318
|
request
|
|
286
319
|
} = protocol === 'http:' ? _http.default : _https.default;
|
|
287
|
-
|
|
320
|
+
let client, req;
|
|
321
|
+
|
|
322
|
+
if (h2) {
|
|
323
|
+
client = _http2.default.connect(url.origin, options);
|
|
324
|
+
req = client.request(options.headers, options);
|
|
325
|
+
} else {
|
|
326
|
+
req = request(options.url, options);
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
(0, _helpers.affix)(client, req, options);
|
|
330
|
+
req.once('response', res => {
|
|
331
|
+
let headers;
|
|
332
|
+
|
|
333
|
+
if (h2) {
|
|
334
|
+
headers = res;
|
|
335
|
+
res = req;
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
(0, _helpers.admix)(res, headers, options);
|
|
339
|
+
});
|
|
340
|
+
return req;
|
|
288
341
|
}
|
|
289
342
|
});
|
|
290
343
|
Reflect.defineProperty(rekwest, 'defaults', {
|
|
291
344
|
enumerable: true,
|
|
292
|
-
|
|
293
|
-
|
|
345
|
+
|
|
346
|
+
get() {
|
|
347
|
+
return defaults;
|
|
348
|
+
},
|
|
349
|
+
|
|
350
|
+
set(value) {
|
|
351
|
+
defaults = (0, _helpers.merge)(defaults, value);
|
|
352
|
+
}
|
|
353
|
+
|
|
294
354
|
});
|
package/package.json
CHANGED
|
@@ -8,14 +8,14 @@
|
|
|
8
8
|
"url": "https://github.com/bricss/rekwest/issues"
|
|
9
9
|
},
|
|
10
10
|
"devDependencies": {
|
|
11
|
-
"@babel/cli": "^7.
|
|
12
|
-
"@babel/core": "^7.
|
|
13
|
-
"@babel/eslint-parser": "^7.
|
|
11
|
+
"@babel/cli": "^7.17.6",
|
|
12
|
+
"@babel/core": "^7.17.5",
|
|
13
|
+
"@babel/eslint-parser": "^7.17.0",
|
|
14
14
|
"@babel/preset-env": "^7.16.11",
|
|
15
15
|
"c8": "^7.11.0",
|
|
16
|
-
"eslint": "^8.
|
|
17
|
-
"eslint-config-ultra-refined": "^2.4.
|
|
18
|
-
"mocha": "^9.2.
|
|
16
|
+
"eslint": "^8.10.0",
|
|
17
|
+
"eslint-config-ultra-refined": "^2.4.1",
|
|
18
|
+
"mocha": "^9.2.1"
|
|
19
19
|
},
|
|
20
20
|
"description": "The robust request library that humanity deserves 🌐",
|
|
21
21
|
"engines": {
|
|
@@ -60,5 +60,5 @@
|
|
|
60
60
|
"test:bail": "mocha --bail",
|
|
61
61
|
"test:cover": "c8 --include=src --reporter=lcov --reporter=text npm test"
|
|
62
62
|
},
|
|
63
|
-
"version": "
|
|
63
|
+
"version": "3.1.0"
|
|
64
64
|
}
|