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/src/index.mjs
CHANGED
|
@@ -1,14 +1,17 @@
|
|
|
1
1
|
import http from 'http';
|
|
2
2
|
import http2 from 'http2';
|
|
3
3
|
import https from 'https';
|
|
4
|
+
import { setTimeout as setTimeoutPromise } from 'timers/promises';
|
|
4
5
|
import { ackn } from './ackn.mjs';
|
|
5
6
|
import { Cookies } from './cookies.mjs';
|
|
6
7
|
import { RequestError } from './errors.mjs';
|
|
7
8
|
import {
|
|
9
|
+
admix,
|
|
10
|
+
affix,
|
|
8
11
|
dispatch,
|
|
9
12
|
merge,
|
|
13
|
+
mixin,
|
|
10
14
|
preflight,
|
|
11
|
-
premix,
|
|
12
15
|
redirects,
|
|
13
16
|
transform,
|
|
14
17
|
} from './helpers.mjs';
|
|
@@ -27,18 +30,48 @@ const {
|
|
|
27
30
|
HTTP2_HEADER_CONTENT_LENGTH,
|
|
28
31
|
HTTP2_HEADER_CONTENT_TYPE,
|
|
29
32
|
HTTP2_HEADER_LOCATION,
|
|
33
|
+
HTTP2_HEADER_RETRY_AFTER,
|
|
30
34
|
HTTP2_HEADER_SET_COOKIE,
|
|
31
|
-
HTTP2_HEADER_STATUS,
|
|
32
35
|
HTTP2_METHOD_GET,
|
|
33
36
|
HTTP2_METHOD_HEAD,
|
|
34
37
|
HTTP_STATUS_BAD_REQUEST,
|
|
38
|
+
HTTP_STATUS_MOVED_PERMANENTLY,
|
|
35
39
|
HTTP_STATUS_SEE_OTHER,
|
|
40
|
+
HTTP_STATUS_SERVICE_UNAVAILABLE,
|
|
41
|
+
HTTP_STATUS_TOO_MANY_REQUESTS,
|
|
36
42
|
} = http2.constants;
|
|
37
43
|
|
|
44
|
+
const maxRetryAfter = Symbol('maxRetryAfter');
|
|
45
|
+
const maxRetryAfterError = (
|
|
46
|
+
interval,
|
|
47
|
+
options,
|
|
48
|
+
) => new RequestError(`Maximum '${ HTTP2_HEADER_RETRY_AFTER }' limit exceeded: ${ interval } ms.`, options);
|
|
49
|
+
let defaults = {
|
|
50
|
+
follow: 20,
|
|
51
|
+
get maxRetryAfter() {
|
|
52
|
+
return this[maxRetryAfter] ?? this.timeout;
|
|
53
|
+
},
|
|
54
|
+
set maxRetryAfter(value) {
|
|
55
|
+
this[maxRetryAfter] = value;
|
|
56
|
+
},
|
|
57
|
+
method: HTTP2_METHOD_GET,
|
|
58
|
+
retry: {
|
|
59
|
+
attempts: 0,
|
|
60
|
+
backoffStrategy: 'interval * Math.log(Math.random() * (Math.E * Math.E - Math.E) + Math.E)',
|
|
61
|
+
interval: 1e3,
|
|
62
|
+
retryAfter: true,
|
|
63
|
+
statusCodes: [
|
|
64
|
+
HTTP_STATUS_TOO_MANY_REQUESTS,
|
|
65
|
+
HTTP_STATUS_SERVICE_UNAVAILABLE,
|
|
66
|
+
],
|
|
67
|
+
},
|
|
68
|
+
timeout: 3e5,
|
|
69
|
+
};
|
|
70
|
+
|
|
38
71
|
export default async function rekwest(url, options = {}) {
|
|
39
72
|
url = options.url = new URL(url);
|
|
40
73
|
if (!options.redirected) {
|
|
41
|
-
options = merge(rekwest.defaults,
|
|
74
|
+
options = merge(rekwest.defaults, options);
|
|
42
75
|
}
|
|
43
76
|
|
|
44
77
|
if (options.body && [
|
|
@@ -48,7 +81,7 @@ export default async function rekwest(url, options = {}) {
|
|
|
48
81
|
throw new TypeError(`Request with ${ HTTP2_METHOD_GET }/${ HTTP2_METHOD_HEAD } method cannot have body.`);
|
|
49
82
|
}
|
|
50
83
|
|
|
51
|
-
if (
|
|
84
|
+
if (options.follow === 0) {
|
|
52
85
|
throw new RequestError(`Maximum redirect reached at: ${ url.href }`);
|
|
53
86
|
}
|
|
54
87
|
|
|
@@ -81,30 +114,22 @@ export default async function rekwest(url, options = {}) {
|
|
|
81
114
|
req = request(url, options);
|
|
82
115
|
}
|
|
83
116
|
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
117
|
+
affix(client, req, options);
|
|
118
|
+
req.once('error', reject);
|
|
119
|
+
req.once('frameError', reject);
|
|
120
|
+
req.once('goaway', reject);
|
|
121
|
+
req.once('response', (res) => {
|
|
122
|
+
let headers;
|
|
87
123
|
|
|
124
|
+
if (h2) {
|
|
125
|
+
headers = res;
|
|
88
126
|
res = req;
|
|
89
|
-
|
|
90
|
-
Reflect.defineProperty(res, 'headers', {
|
|
91
|
-
enumerable: true,
|
|
92
|
-
value: headers,
|
|
93
|
-
});
|
|
94
|
-
|
|
95
|
-
Reflect.defineProperty(res, 'httpVersion', {
|
|
96
|
-
enumerable: true,
|
|
97
|
-
value: `${ h2 + 1 }.0`,
|
|
98
|
-
});
|
|
99
|
-
|
|
100
|
-
Reflect.defineProperty(res, 'statusCode', {
|
|
101
|
-
enumerable: true,
|
|
102
|
-
value: headers[HTTP2_HEADER_STATUS],
|
|
103
|
-
});
|
|
104
127
|
} else {
|
|
105
|
-
res.
|
|
128
|
+
res.once('error', reject);
|
|
106
129
|
}
|
|
107
130
|
|
|
131
|
+
admix(res, headers, options);
|
|
132
|
+
|
|
108
133
|
if (cookies !== false && res.headers[HTTP2_HEADER_SET_COOKIE]) {
|
|
109
134
|
if (Cookies.jar.has(url.origin)) {
|
|
110
135
|
new Cookies(res.headers[HTTP2_HEADER_SET_COOKIE]).forEach(function (val, key) {
|
|
@@ -145,39 +170,27 @@ export default async function rekwest(url, options = {}) {
|
|
|
145
170
|
|
|
146
171
|
Reflect.set(options, 'redirected', true);
|
|
147
172
|
|
|
173
|
+
if (res.statusCode === HTTP_STATUS_MOVED_PERMANENTLY && res.headers[HTTP2_HEADER_RETRY_AFTER]) {
|
|
174
|
+
let interval = res.headers[HTTP2_HEADER_RETRY_AFTER];
|
|
175
|
+
|
|
176
|
+
interval = Number(interval) * 1000 || new Date(interval) - Date.now();
|
|
177
|
+
|
|
178
|
+
if (interval > options.maxRetryAfter) {
|
|
179
|
+
res.emit('error', maxRetryAfterError(interval, { cause: mixin(res, options) }));
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
return setTimeoutPromise(interval).then(() => rekwest(options.url, options).then(resolve, reject));
|
|
183
|
+
}
|
|
184
|
+
|
|
148
185
|
return rekwest(options.url, options).then(resolve, reject);
|
|
149
186
|
}
|
|
150
187
|
}
|
|
151
188
|
|
|
152
|
-
Reflect.defineProperty(res, 'ok', {
|
|
153
|
-
enumerable: true,
|
|
154
|
-
value: /^2\d{2}$/.test(res.statusCode),
|
|
155
|
-
});
|
|
156
|
-
|
|
157
|
-
Reflect.defineProperty(res, 'redirected', {
|
|
158
|
-
enumerable: true,
|
|
159
|
-
value: options.redirected,
|
|
160
|
-
});
|
|
161
|
-
|
|
162
189
|
if (res.statusCode >= HTTP_STATUS_BAD_REQUEST) {
|
|
163
|
-
return reject(
|
|
190
|
+
return reject(mixin(res, options));
|
|
164
191
|
}
|
|
165
192
|
|
|
166
|
-
resolve(
|
|
167
|
-
});
|
|
168
|
-
|
|
169
|
-
req.on('end', () => {
|
|
170
|
-
client?.close();
|
|
171
|
-
});
|
|
172
|
-
req.on('error', reject);
|
|
173
|
-
req.on('frameError', reject);
|
|
174
|
-
req.on('goaway', reject);
|
|
175
|
-
req.on('timeout', req.destroy);
|
|
176
|
-
req.on('trailers', (trailers) => {
|
|
177
|
-
Reflect.defineProperty(req, 'trailers', {
|
|
178
|
-
enumerable: true,
|
|
179
|
-
value: trailers,
|
|
180
|
-
});
|
|
193
|
+
resolve(mixin(res, options));
|
|
181
194
|
});
|
|
182
195
|
|
|
183
196
|
dispatch(req, { ...options, body });
|
|
@@ -192,6 +205,27 @@ export default async function rekwest(url, options = {}) {
|
|
|
192
205
|
|
|
193
206
|
return res;
|
|
194
207
|
} catch (ex) {
|
|
208
|
+
const { maxRetryAfter, retry } = options;
|
|
209
|
+
|
|
210
|
+
if (retry?.attempts && retry?.statusCodes.includes(ex.statusCode)) {
|
|
211
|
+
let { interval } = retry;
|
|
212
|
+
|
|
213
|
+
if (retry.retryAfter && ex.headers[HTTP2_HEADER_RETRY_AFTER]) {
|
|
214
|
+
interval = ex.headers[HTTP2_HEADER_RETRY_AFTER];
|
|
215
|
+
interval = Number(interval) * 1000 || new Date(interval) - Date.now();
|
|
216
|
+
if (interval > maxRetryAfter) {
|
|
217
|
+
throw maxRetryAfterError(interval, { cause: ex });
|
|
218
|
+
}
|
|
219
|
+
} else {
|
|
220
|
+
interval = new Function('interval', `return Math.ceil(${ retry.backoffStrategy });`)(interval);
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
retry.attempts--;
|
|
224
|
+
retry.interval = interval;
|
|
225
|
+
|
|
226
|
+
return setTimeoutPromise(interval).then(() => rekwest(url, options));
|
|
227
|
+
}
|
|
228
|
+
|
|
195
229
|
if (digest && !redirected && ex.body) {
|
|
196
230
|
ex.body = await ex.body();
|
|
197
231
|
}
|
|
@@ -215,26 +249,35 @@ Reflect.defineProperty(rekwest, 'stream', {
|
|
|
215
249
|
redirect: redirects.manual,
|
|
216
250
|
});
|
|
217
251
|
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
req.on('end', () => {
|
|
223
|
-
client.close();
|
|
224
|
-
});
|
|
252
|
+
const { h2, url: { protocol } } = options;
|
|
253
|
+
const { request } = (protocol === 'http:' ? http : https);
|
|
254
|
+
let client, req;
|
|
225
255
|
|
|
226
|
-
|
|
256
|
+
if (h2) {
|
|
257
|
+
client = http2.connect(url.origin, options);
|
|
258
|
+
req = client.request(options.headers, options);
|
|
259
|
+
} else {
|
|
260
|
+
req = request(options.url, options);
|
|
227
261
|
}
|
|
228
262
|
|
|
229
|
-
|
|
230
|
-
|
|
263
|
+
affix(client, req, options);
|
|
264
|
+
req.once('response', (res) => {
|
|
265
|
+
let headers;
|
|
266
|
+
|
|
267
|
+
if (h2) {
|
|
268
|
+
headers = res;
|
|
269
|
+
res = req;
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
admix(res, headers, options);
|
|
273
|
+
});
|
|
231
274
|
|
|
232
|
-
return
|
|
275
|
+
return req;
|
|
233
276
|
},
|
|
234
277
|
});
|
|
235
278
|
|
|
236
279
|
Reflect.defineProperty(rekwest, 'defaults', {
|
|
237
280
|
enumerable: true,
|
|
238
|
-
|
|
239
|
-
|
|
281
|
+
get() { return defaults; },
|
|
282
|
+
set(value) { defaults = merge(defaults, value); },
|
|
240
283
|
});
|