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