rekwest 2.3.5 → 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 +30 -20
- package/dist/errors.js +8 -4
- package/dist/helpers.js +126 -70
- package/dist/index.js +142 -69
- 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 -61
- package/src/index.mjs +115 -66
package/src/index.mjs
CHANGED
|
@@ -1,13 +1,18 @@
|
|
|
1
|
+
import http from 'http';
|
|
1
2
|
import http2 from 'http2';
|
|
2
|
-
import
|
|
3
|
+
import https from 'https';
|
|
4
|
+
import { setTimeout as setTimeoutPromise } from 'timers/promises';
|
|
3
5
|
import { ackn } from './ackn.mjs';
|
|
4
6
|
import { Cookies } from './cookies.mjs';
|
|
5
7
|
import { RequestError } from './errors.mjs';
|
|
6
8
|
import {
|
|
9
|
+
admix,
|
|
10
|
+
affix,
|
|
7
11
|
dispatch,
|
|
8
12
|
merge,
|
|
13
|
+
mixin,
|
|
9
14
|
preflight,
|
|
10
|
-
|
|
15
|
+
redirects,
|
|
11
16
|
transform,
|
|
12
17
|
} from './helpers.mjs';
|
|
13
18
|
import { APPLICATION_OCTET_STREAM } from './mediatypes.mjs';
|
|
@@ -25,28 +30,59 @@ const {
|
|
|
25
30
|
HTTP2_HEADER_CONTENT_LENGTH,
|
|
26
31
|
HTTP2_HEADER_CONTENT_TYPE,
|
|
27
32
|
HTTP2_HEADER_LOCATION,
|
|
33
|
+
HTTP2_HEADER_RETRY_AFTER,
|
|
28
34
|
HTTP2_HEADER_SET_COOKIE,
|
|
29
35
|
HTTP2_HEADER_STATUS,
|
|
30
36
|
HTTP2_METHOD_GET,
|
|
31
37
|
HTTP2_METHOD_HEAD,
|
|
32
38
|
HTTP_STATUS_BAD_REQUEST,
|
|
39
|
+
HTTP_STATUS_MOVED_PERMANENTLY,
|
|
33
40
|
HTTP_STATUS_SEE_OTHER,
|
|
41
|
+
HTTP_STATUS_SERVICE_UNAVAILABLE,
|
|
42
|
+
HTTP_STATUS_TOO_MANY_REQUESTS,
|
|
34
43
|
} = http2.constants;
|
|
35
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
|
+
|
|
36
72
|
export default async function rekwest(url, options = {}) {
|
|
37
73
|
url = options.url = new URL(url);
|
|
38
74
|
if (!options.redirected) {
|
|
39
|
-
options = merge(rekwest.defaults,
|
|
75
|
+
options = merge(rekwest.defaults, options);
|
|
40
76
|
}
|
|
41
77
|
|
|
42
78
|
if (options.body && [
|
|
43
79
|
HTTP2_METHOD_GET,
|
|
44
80
|
HTTP2_METHOD_HEAD,
|
|
45
81
|
].includes(options.method)) {
|
|
46
|
-
throw new TypeError(`Request with ${ HTTP2_METHOD_GET }/${ HTTP2_METHOD_HEAD } method cannot have body
|
|
82
|
+
throw new TypeError(`Request with ${ HTTP2_METHOD_GET }/${ HTTP2_METHOD_HEAD } method cannot have body.`);
|
|
47
83
|
}
|
|
48
84
|
|
|
49
|
-
if (
|
|
85
|
+
if (options.follow === 0) {
|
|
50
86
|
throw new RequestError(`Maximum redirect reached at: ${ url.href }`);
|
|
51
87
|
}
|
|
52
88
|
|
|
@@ -63,7 +99,8 @@ export default async function rekwest(url, options = {}) {
|
|
|
63
99
|
|
|
64
100
|
options = preflight(options);
|
|
65
101
|
|
|
66
|
-
const { cookies, digest, follow, h2, redirect, redirected, thenable } = options;
|
|
102
|
+
const { cookies, digest, follow, h2, redirect, redirected, thenable, url: { protocol } } = options;
|
|
103
|
+
const { request } = (protocol === 'http:' ? http : https);
|
|
67
104
|
let { body } = options;
|
|
68
105
|
|
|
69
106
|
const promise = new Promise((resolve, reject) => {
|
|
@@ -78,30 +115,22 @@ export default async function rekwest(url, options = {}) {
|
|
|
78
115
|
req = request(url, options);
|
|
79
116
|
}
|
|
80
117
|
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
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;
|
|
84
124
|
|
|
125
|
+
if (h2) {
|
|
126
|
+
headers = res;
|
|
85
127
|
res = req;
|
|
86
|
-
|
|
87
|
-
Reflect.defineProperty(res, 'headers', {
|
|
88
|
-
enumerable: true,
|
|
89
|
-
value: headers,
|
|
90
|
-
});
|
|
91
|
-
|
|
92
|
-
Reflect.defineProperty(res, 'httpVersion', {
|
|
93
|
-
enumerable: true,
|
|
94
|
-
value: `${ h2 + 1 }.0`,
|
|
95
|
-
});
|
|
96
|
-
|
|
97
|
-
Reflect.defineProperty(res, 'statusCode', {
|
|
98
|
-
enumerable: true,
|
|
99
|
-
value: headers[HTTP2_HEADER_STATUS],
|
|
100
|
-
});
|
|
101
128
|
} else {
|
|
102
|
-
res.
|
|
129
|
+
res.once('error', reject);
|
|
103
130
|
}
|
|
104
131
|
|
|
132
|
+
admix(res, headers, options);
|
|
133
|
+
|
|
105
134
|
if (cookies !== false && res.headers[HTTP2_HEADER_SET_COOKIE]) {
|
|
106
135
|
if (Cookies.jar.has(url.origin)) {
|
|
107
136
|
new Cookies(res.headers[HTTP2_HEADER_SET_COOKIE]).forEach(function (val, key) {
|
|
@@ -120,16 +149,16 @@ export default async function rekwest(url, options = {}) {
|
|
|
120
149
|
});
|
|
121
150
|
|
|
122
151
|
if (follow && /^3\d{2}$/.test(res.statusCode) && res.headers[HTTP2_HEADER_LOCATION]) {
|
|
123
|
-
if (redirect ===
|
|
124
|
-
res.emit('error', new RequestError(`Unexpected redirect, redirect mode is set to '${ redirect }'
|
|
152
|
+
if (redirect === redirects.error) {
|
|
153
|
+
res.emit('error', new RequestError(`Unexpected redirect, redirect mode is set to '${ redirect }'.`));
|
|
125
154
|
}
|
|
126
155
|
|
|
127
|
-
if (redirect ===
|
|
156
|
+
if (redirect === redirects.follow) {
|
|
128
157
|
options.url = new URL(res.headers[HTTP2_HEADER_LOCATION], url).href;
|
|
129
158
|
|
|
130
159
|
if (res.statusCode !== HTTP_STATUS_SEE_OTHER
|
|
131
160
|
&& body === Object(body) && body.pipe?.constructor === Function) {
|
|
132
|
-
res.emit('error', new RequestError(`Unable to ${ redirect } redirect with body as readable stream
|
|
161
|
+
res.emit('error', new RequestError(`Unable to ${ redirect } redirect with body as readable stream.`));
|
|
133
162
|
}
|
|
134
163
|
|
|
135
164
|
options.follow--;
|
|
@@ -142,39 +171,27 @@ export default async function rekwest(url, options = {}) {
|
|
|
142
171
|
|
|
143
172
|
Reflect.set(options, 'redirected', true);
|
|
144
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
|
+
|
|
145
186
|
return rekwest(options.url, options).then(resolve, reject);
|
|
146
187
|
}
|
|
147
188
|
}
|
|
148
189
|
|
|
149
|
-
Reflect.defineProperty(res, 'ok', {
|
|
150
|
-
enumerable: true,
|
|
151
|
-
value: /^2\d{2}$/.test(res.statusCode),
|
|
152
|
-
});
|
|
153
|
-
|
|
154
|
-
Reflect.defineProperty(res, 'redirected', {
|
|
155
|
-
enumerable: true,
|
|
156
|
-
value: options.redirected,
|
|
157
|
-
});
|
|
158
|
-
|
|
159
190
|
if (res.statusCode >= HTTP_STATUS_BAD_REQUEST) {
|
|
160
|
-
return reject(
|
|
191
|
+
return reject(mixin(res, options));
|
|
161
192
|
}
|
|
162
193
|
|
|
163
|
-
resolve(
|
|
164
|
-
});
|
|
165
|
-
|
|
166
|
-
req.on('end', () => {
|
|
167
|
-
client?.close();
|
|
168
|
-
});
|
|
169
|
-
req.on('error', reject);
|
|
170
|
-
req.on('frameError', reject);
|
|
171
|
-
req.on('goaway', reject);
|
|
172
|
-
req.on('timeout', req.destroy);
|
|
173
|
-
req.on('trailers', (trailers) => {
|
|
174
|
-
Reflect.defineProperty(req, 'trailers', {
|
|
175
|
-
enumerable: true,
|
|
176
|
-
value: trailers,
|
|
177
|
-
});
|
|
194
|
+
resolve(mixin(res, options));
|
|
178
195
|
});
|
|
179
196
|
|
|
180
197
|
dispatch(req, { ...options, body });
|
|
@@ -189,6 +206,25 @@ export default async function rekwest(url, options = {}) {
|
|
|
189
206
|
|
|
190
207
|
return res;
|
|
191
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
|
+
|
|
192
228
|
if (digest && !redirected && ex.body) {
|
|
193
229
|
ex.body = await ex.body();
|
|
194
230
|
}
|
|
@@ -209,25 +245,38 @@ Reflect.defineProperty(rekwest, 'stream', {
|
|
|
209
245
|
...merge(rekwest.defaults, {
|
|
210
246
|
headers: { [HTTP2_HEADER_CONTENT_TYPE]: APPLICATION_OCTET_STREAM },
|
|
211
247
|
}, options),
|
|
248
|
+
redirect: redirects.manual,
|
|
212
249
|
});
|
|
213
250
|
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
req.on('end', () => {
|
|
219
|
-
client.close();
|
|
220
|
-
});
|
|
251
|
+
const { h2, url: { protocol } } = options;
|
|
252
|
+
const { request } = (protocol === 'http:' ? http : https);
|
|
253
|
+
let client, req;
|
|
221
254
|
|
|
222
|
-
|
|
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);
|
|
223
260
|
}
|
|
224
261
|
|
|
225
|
-
|
|
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
|
+
});
|
|
273
|
+
|
|
274
|
+
return req;
|
|
226
275
|
},
|
|
227
276
|
});
|
|
228
277
|
|
|
229
278
|
Reflect.defineProperty(rekwest, 'defaults', {
|
|
230
279
|
enumerable: true,
|
|
231
|
-
|
|
232
|
-
|
|
280
|
+
get() { return defaults; },
|
|
281
|
+
set(value) { defaults = merge(defaults, value); },
|
|
233
282
|
});
|