rekwest 2.3.6 → 3.0.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 +30 -20
- package/dist/errors.js +8 -4
- package/dist/helpers.js +124 -64
- package/dist/index.js +122 -66
- 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 -58
- package/src/index.mjs +110 -67
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 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
|
|
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,7 +1,7 @@
|
|
|
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
|
|
|
@@ -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,67 +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 ??= 'follow';
|
|
183
|
-
options.redirected ??= false;
|
|
184
|
-
options.thenable ??= false;
|
|
185
|
-
return options;
|
|
186
|
-
};
|
|
187
|
-
|
|
188
|
-
exports.preflight = preflight;
|
|
189
|
-
|
|
190
|
-
const premix = (res, {
|
|
178
|
+
const mixin = (res, {
|
|
191
179
|
digest = false,
|
|
192
180
|
parse = false
|
|
193
181
|
} = {}) => {
|
|
@@ -230,7 +218,7 @@ const premix = (res, {
|
|
|
230
218
|
enumerable: true,
|
|
231
219
|
value: async function () {
|
|
232
220
|
if (this.bodyUsed) {
|
|
233
|
-
throw new TypeError('Response stream already read');
|
|
221
|
+
throw new TypeError('Response stream already read.');
|
|
234
222
|
}
|
|
235
223
|
|
|
236
224
|
let spool = [];
|
|
@@ -275,7 +263,79 @@ const premix = (res, {
|
|
|
275
263
|
});
|
|
276
264
|
};
|
|
277
265
|
|
|
278
|
-
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;
|
|
279
339
|
|
|
280
340
|
async function* tap(value) {
|
|
281
341
|
if (Reflect.has(value, Symbol.asyncIterator)) {
|
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
|
-
throw new TypeError(`Request with ${HTTP2_METHOD_GET}/${HTTP2_METHOD_HEAD} method cannot have body
|
|
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) {
|
|
@@ -175,15 +198,15 @@ async function rekwest(url, options = {}) {
|
|
|
175
198
|
});
|
|
176
199
|
|
|
177
200
|
if (follow && /^3\d{2}$/.test(res.statusCode) && res.headers[HTTP2_HEADER_LOCATION]) {
|
|
178
|
-
if (redirect ===
|
|
179
|
-
res.emit('error', new _errors.RequestError(`Unexpected redirect, redirect mode is set to '${redirect}'
|
|
201
|
+
if (redirect === _helpers.redirects.error) {
|
|
202
|
+
res.emit('error', new _errors.RequestError(`Unexpected redirect, redirect mode is set to '${redirect}'.`));
|
|
180
203
|
}
|
|
181
204
|
|
|
182
|
-
if (redirect ===
|
|
205
|
+
if (redirect === _helpers.redirects.follow) {
|
|
183
206
|
options.url = new URL(res.headers[HTTP2_HEADER_LOCATION], url).href;
|
|
184
207
|
|
|
185
208
|
if (res.statusCode !== HTTP_STATUS_SEE_OTHER && body === Object(body) && body.pipe?.constructor === Function) {
|
|
186
|
-
res.emit('error', new _errors.RequestError(`Unable to ${redirect} redirect with body as readable stream
|
|
209
|
+
res.emit('error', new _errors.RequestError(`Unable to ${redirect} redirect with body as readable stream.`));
|
|
187
210
|
}
|
|
188
211
|
|
|
189
212
|
options.follow--;
|
|
@@ -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,29 @@ async function rekwest(url, options = {}) {
|
|
|
241
256
|
|
|
242
257
|
return res;
|
|
243
258
|
} catch (ex) {
|
|
259
|
+
if (options.retry?.attempts && options.retry?.statusCodes.includes(ex.statusCode)) {
|
|
260
|
+
let {
|
|
261
|
+
interval
|
|
262
|
+
} = options.retry;
|
|
263
|
+
|
|
264
|
+
if (options.retry.retryAfter && ex.headers[HTTP2_HEADER_RETRY_AFTER]) {
|
|
265
|
+
interval = ex.headers[HTTP2_HEADER_RETRY_AFTER];
|
|
266
|
+
interval = Number(interval) * 1000 || new Date(interval) - Date.now();
|
|
267
|
+
|
|
268
|
+
if (interval > options.maxRetryAfter) {
|
|
269
|
+
throw maxRetryAfterError(interval, {
|
|
270
|
+
cause: ex
|
|
271
|
+
});
|
|
272
|
+
}
|
|
273
|
+
} else {
|
|
274
|
+
interval = new Function('interval', `return Math.ceil(${options.retry.backoffStrategy});`)(interval);
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
options.retry.attempts--;
|
|
278
|
+
options.retry.interval = interval;
|
|
279
|
+
return (0, _promises.setTimeout)(interval).then(() => rekwest(url, options));
|
|
280
|
+
}
|
|
281
|
+
|
|
244
282
|
if (digest && !redirected && ex.body) {
|
|
245
283
|
ex.body = await ex.body();
|
|
246
284
|
}
|
|
@@ -262,20 +300,11 @@ Reflect.defineProperty(rekwest, 'stream', {
|
|
|
262
300
|
headers: {
|
|
263
301
|
[HTTP2_HEADER_CONTENT_TYPE]: _mediatypes.APPLICATION_OCTET_STREAM
|
|
264
302
|
}
|
|
265
|
-
}, options)
|
|
303
|
+
}, options),
|
|
304
|
+
redirect: _helpers.redirects.manual
|
|
266
305
|
});
|
|
267
|
-
|
|
268
|
-
if (options.h2) {
|
|
269
|
-
const client = _http2.default.connect(url.origin, options);
|
|
270
|
-
|
|
271
|
-
const req = client.request(options.headers, options);
|
|
272
|
-
req.on('end', () => {
|
|
273
|
-
client.close();
|
|
274
|
-
});
|
|
275
|
-
return req;
|
|
276
|
-
}
|
|
277
|
-
|
|
278
306
|
const {
|
|
307
|
+
h2,
|
|
279
308
|
url: {
|
|
280
309
|
protocol
|
|
281
310
|
}
|
|
@@ -283,11 +312,38 @@ Reflect.defineProperty(rekwest, 'stream', {
|
|
|
283
312
|
const {
|
|
284
313
|
request
|
|
285
314
|
} = protocol === 'http:' ? _http.default : _https.default;
|
|
286
|
-
|
|
315
|
+
let client, req;
|
|
316
|
+
|
|
317
|
+
if (h2) {
|
|
318
|
+
client = _http2.default.connect(url.origin, options);
|
|
319
|
+
req = client.request(options.headers, options);
|
|
320
|
+
} else {
|
|
321
|
+
req = request(options.url, options);
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
(0, _helpers.affix)(client, req, options);
|
|
325
|
+
req.once('response', res => {
|
|
326
|
+
let headers;
|
|
327
|
+
|
|
328
|
+
if (h2) {
|
|
329
|
+
headers = res;
|
|
330
|
+
res = req;
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
(0, _helpers.admix)(res, headers, options);
|
|
334
|
+
});
|
|
335
|
+
return req;
|
|
287
336
|
}
|
|
288
337
|
});
|
|
289
338
|
Reflect.defineProperty(rekwest, 'defaults', {
|
|
290
339
|
enumerable: true,
|
|
291
|
-
|
|
292
|
-
|
|
340
|
+
|
|
341
|
+
get() {
|
|
342
|
+
return defaults;
|
|
343
|
+
},
|
|
344
|
+
|
|
345
|
+
set(value) {
|
|
346
|
+
defaults = (0, _helpers.merge)(defaults, value);
|
|
347
|
+
}
|
|
348
|
+
|
|
293
349
|
});
|
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.
|
|
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": {
|
|
@@ -56,8 +56,9 @@
|
|
|
56
56
|
"lint": "eslint . --ext .cjs,.js,.mjs",
|
|
57
57
|
"prepack": "npm run build && sh pony.sh",
|
|
58
58
|
"pretest": "rm -rf coverage && npm run cert:gen",
|
|
59
|
-
"test": "mocha
|
|
59
|
+
"test": "mocha",
|
|
60
|
+
"test:bail": "mocha --bail",
|
|
60
61
|
"test:cover": "c8 --include=src --reporter=lcov --reporter=text npm test"
|
|
61
62
|
},
|
|
62
|
-
"version": "
|
|
63
|
+
"version": "3.0.1"
|
|
63
64
|
}
|