rekwest 2.4.0 → 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 +29 -20
- package/dist/errors.js +8 -4
- package/dist/helpers.js +117 -69
- package/dist/index.js +115 -59
- 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 +375 -331
- package/src/index.mjs +103 -61
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
|
|
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
|
|
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
|
} = {}) => {
|
|
@@ -127,73 +175,7 @@ const merge = (target = {}, ...rest) => {
|
|
|
127
175
|
|
|
128
176
|
exports.merge = merge;
|
|
129
177
|
|
|
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, {
|
|
178
|
+
const mixin = (res, {
|
|
197
179
|
digest = false,
|
|
198
180
|
parse = false
|
|
199
181
|
} = {}) => {
|
|
@@ -281,7 +263,73 @@ const premix = (res, {
|
|
|
281
263
|
});
|
|
282
264
|
};
|
|
283
265
|
|
|
284
|
-
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;
|
|
285
333
|
const redirects = {
|
|
286
334
|
error: 'error',
|
|
287
335
|
follow: 'follow',
|
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,55 @@ 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
84
|
HTTP2_HEADER_STATUS,
|
|
82
85
|
HTTP2_METHOD_GET,
|
|
83
86
|
HTTP2_METHOD_HEAD,
|
|
84
87
|
HTTP_STATUS_BAD_REQUEST,
|
|
85
|
-
|
|
88
|
+
HTTP_STATUS_MOVED_PERMANENTLY,
|
|
89
|
+
HTTP_STATUS_SEE_OTHER,
|
|
90
|
+
HTTP_STATUS_SERVICE_UNAVAILABLE,
|
|
91
|
+
HTTP_STATUS_TOO_MANY_REQUESTS
|
|
86
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
|
+
};
|
|
87
118
|
|
|
88
119
|
async function rekwest(url, options = {}) {
|
|
89
120
|
url = options.url = new URL(url);
|
|
90
121
|
|
|
91
122
|
if (!options.redirected) {
|
|
92
|
-
options = (0, _helpers.merge)(rekwest.defaults,
|
|
93
|
-
follow: 20,
|
|
94
|
-
method: HTTP2_METHOD_GET
|
|
95
|
-
}, options);
|
|
123
|
+
options = (0, _helpers.merge)(rekwest.defaults, options);
|
|
96
124
|
}
|
|
97
125
|
|
|
98
126
|
if (options.body && [HTTP2_METHOD_GET, HTTP2_METHOD_HEAD].includes(options.method)) {
|
|
99
127
|
throw new TypeError(`Request with ${HTTP2_METHOD_GET}/${HTTP2_METHOD_HEAD} method cannot have body.`);
|
|
100
128
|
}
|
|
101
129
|
|
|
102
|
-
if (
|
|
130
|
+
if (options.follow === 0) {
|
|
103
131
|
throw new _errors.RequestError(`Maximum redirect reached at: ${url.href}`);
|
|
104
132
|
}
|
|
105
133
|
|
|
@@ -139,26 +167,22 @@ async function rekwest(url, options = {}) {
|
|
|
139
167
|
req = request(url, options);
|
|
140
168
|
}
|
|
141
169
|
|
|
142
|
-
|
|
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
|
+
|
|
143
177
|
if (h2) {
|
|
144
|
-
|
|
178
|
+
headers = res;
|
|
145
179
|
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
180
|
} else {
|
|
159
|
-
res.
|
|
181
|
+
res.once('error', reject);
|
|
160
182
|
}
|
|
161
183
|
|
|
184
|
+
(0, _helpers.admix)(res, headers, options);
|
|
185
|
+
|
|
162
186
|
if (cookies !== false && res.headers[HTTP2_HEADER_SET_COOKIE]) {
|
|
163
187
|
if (_cookies.Cookies.jar.has(url.origin)) {
|
|
164
188
|
new _cookies.Cookies(res.headers[HTTP2_HEADER_SET_COOKIE]).forEach(function (val, key) {
|
|
@@ -195,37 +219,29 @@ async function rekwest(url, options = {}) {
|
|
|
195
219
|
}
|
|
196
220
|
|
|
197
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
|
+
|
|
198
236
|
return rekwest(options.url, options).then(resolve, reject);
|
|
199
237
|
}
|
|
200
238
|
}
|
|
201
239
|
|
|
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
240
|
if (res.statusCode >= HTTP_STATUS_BAD_REQUEST) {
|
|
212
|
-
return reject((0, _helpers.
|
|
241
|
+
return reject((0, _helpers.mixin)(res, options));
|
|
213
242
|
}
|
|
214
243
|
|
|
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
|
-
});
|
|
244
|
+
resolve((0, _helpers.mixin)(res, options));
|
|
229
245
|
});
|
|
230
246
|
(0, _helpers.dispatch)(req, { ...options,
|
|
231
247
|
body
|
|
@@ -241,6 +257,29 @@ async function rekwest(url, options = {}) {
|
|
|
241
257
|
|
|
242
258
|
return res;
|
|
243
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
|
+
|
|
244
283
|
if (digest && !redirected && ex.body) {
|
|
245
284
|
ex.body = await ex.body();
|
|
246
285
|
}
|
|
@@ -265,18 +304,8 @@ Reflect.defineProperty(rekwest, 'stream', {
|
|
|
265
304
|
}, options),
|
|
266
305
|
redirect: _helpers.redirects.manual
|
|
267
306
|
});
|
|
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
307
|
const {
|
|
308
|
+
h2,
|
|
280
309
|
url: {
|
|
281
310
|
protocol
|
|
282
311
|
}
|
|
@@ -284,11 +313,38 @@ Reflect.defineProperty(rekwest, 'stream', {
|
|
|
284
313
|
const {
|
|
285
314
|
request
|
|
286
315
|
} = protocol === 'http:' ? _http.default : _https.default;
|
|
287
|
-
|
|
316
|
+
let client, req;
|
|
317
|
+
|
|
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);
|
|
323
|
+
}
|
|
324
|
+
|
|
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;
|
|
288
337
|
}
|
|
289
338
|
});
|
|
290
339
|
Reflect.defineProperty(rekwest, 'defaults', {
|
|
291
340
|
enumerable: true,
|
|
292
|
-
|
|
293
|
-
|
|
341
|
+
|
|
342
|
+
get() {
|
|
343
|
+
return defaults;
|
|
344
|
+
},
|
|
345
|
+
|
|
346
|
+
set(value) {
|
|
347
|
+
defaults = (0, _helpers.merge)(defaults, value);
|
|
348
|
+
}
|
|
349
|
+
|
|
294
350
|
});
|
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.0.0"
|
|
64
64
|
}
|