rekwest 6.2.0 → 7.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 +11 -10
- package/dist/codecs.cjs +55 -0
- package/dist/{config.js → config.cjs} +15 -19
- package/dist/{cookies.js → cookies.cjs} +2 -2
- package/dist/{formdata.js → formdata.cjs} +8 -11
- package/dist/{index.js → index.cjs} +27 -15
- package/dist/{mixin.js → mixin.cjs} +22 -17
- package/dist/postflight.cjs +60 -0
- package/dist/{preflight.js → preflight.cjs} +11 -9
- package/dist/redirects.cjs +63 -0
- package/dist/retries.cjs +57 -0
- package/dist/{transfer.js → transfer.cjs} +13 -40
- package/dist/transform.cjs +105 -0
- package/dist/utils.cjs +140 -0
- package/dist/{validation.js → validation.cjs} +1 -1
- package/package.json +15 -14
- package/src/{ackn.mjs → ackn.js} +33 -33
- package/src/codecs.js +55 -0
- package/src/{config.mjs → config.js} +88 -93
- package/src/{constants.mjs → constants.js} +29 -29
- package/src/{cookies.mjs → cookies.js} +100 -100
- package/src/{formdata.mjs → formdata.js} +8 -14
- package/src/{index.mjs → index.js} +22 -22
- package/src/{mediatypes.mjs → mediatypes.js} +6 -6
- package/src/{mixin.mjs → mixin.js} +25 -26
- package/src/postflight.js +56 -0
- package/src/{preflight.mjs → preflight.js} +100 -91
- package/src/redirects.js +79 -0
- package/src/retries.js +51 -0
- package/src/transfer.js +92 -0
- package/src/transform.js +109 -0
- package/src/utils.js +152 -0
- package/src/{validation.mjs → validation.js} +33 -33
- package/dist/postflight.js +0 -117
- package/dist/transform.js +0 -79
- package/dist/utils.js +0 -188
- package/src/postflight.mjs +0 -136
- package/src/transfer.mjs +0 -121
- package/src/transform.mjs +0 -82
- package/src/utils.mjs +0 -205
- /package/dist/{ackn.js → ackn.cjs} +0 -0
- /package/dist/{constants.js → constants.cjs} +0 -0
- /package/dist/{errors.js → errors.cjs} +0 -0
- /package/dist/{mediatypes.js → mediatypes.cjs} +0 -0
- /package/src/{errors.mjs → errors.js} +0 -0
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import http2 from 'node:http2';
|
|
2
|
+
import { Cookies } from './cookies.js';
|
|
3
|
+
import { mixin } from './mixin.js';
|
|
4
|
+
import { redirects } from './redirects.js';
|
|
5
|
+
import { augment } from './utils.js';
|
|
6
|
+
|
|
7
|
+
const {
|
|
8
|
+
HTTP2_HEADER_SET_COOKIE,
|
|
9
|
+
HTTP_STATUS_BAD_REQUEST,
|
|
10
|
+
} = http2.constants;
|
|
11
|
+
|
|
12
|
+
export const postflight = (req, res, options, { reject, resolve }) => {
|
|
13
|
+
const { cookies, h2, url } = options;
|
|
14
|
+
let headers;
|
|
15
|
+
|
|
16
|
+
if (h2) {
|
|
17
|
+
headers = res;
|
|
18
|
+
res = req;
|
|
19
|
+
} else {
|
|
20
|
+
res.once('error', reject);
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
augment(res, headers, options);
|
|
24
|
+
|
|
25
|
+
if (cookies !== false && res.headers[HTTP2_HEADER_SET_COOKIE]) {
|
|
26
|
+
if (Cookies.jar.has(url.origin)) {
|
|
27
|
+
const cookie = new Cookies(res.headers[HTTP2_HEADER_SET_COOKIE], options);
|
|
28
|
+
|
|
29
|
+
Cookies.jar.get(url.origin).forEach((val, key) => {
|
|
30
|
+
if (!cookie.has(key)) {
|
|
31
|
+
cookie.set(key, val);
|
|
32
|
+
}
|
|
33
|
+
});
|
|
34
|
+
Cookies.jar.set(url.origin, cookie);
|
|
35
|
+
} else {
|
|
36
|
+
Cookies.jar.set(url.origin, new Cookies(res.headers[HTTP2_HEADER_SET_COOKIE], options));
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
Reflect.defineProperty(res, 'cookies', {
|
|
41
|
+
enumerable: true,
|
|
42
|
+
value: cookies !== false && Cookies.jar.has(url.origin) ? Cookies.jar.get(url.origin) : void 0,
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
const willRedirect = redirects(res, options);
|
|
46
|
+
|
|
47
|
+
if (Object(willRedirect) === willRedirect) {
|
|
48
|
+
return willRedirect.then(resolve, reject);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
if (res.statusCode >= HTTP_STATUS_BAD_REQUEST) {
|
|
52
|
+
return reject(mixin(res, options));
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
resolve(mixin(res, options));
|
|
56
|
+
};
|
|
@@ -1,91 +1,100 @@
|
|
|
1
|
-
import http2 from 'node:http2';
|
|
2
|
-
import { isZstdSupported } from './config.
|
|
3
|
-
import { requestCredentials } from './constants.
|
|
4
|
-
import { Cookies } from './cookies.
|
|
5
|
-
|
|
6
|
-
const {
|
|
7
|
-
HTTP2_HEADER_ACCEPT_ENCODING,
|
|
8
|
-
HTTP2_HEADER_AUTHORITY,
|
|
9
|
-
HTTP2_HEADER_AUTHORIZATION,
|
|
10
|
-
HTTP2_HEADER_COOKIE,
|
|
11
|
-
HTTP2_HEADER_METHOD,
|
|
12
|
-
HTTP2_HEADER_PATH,
|
|
13
|
-
HTTP2_HEADER_SCHEME,
|
|
14
|
-
HTTP2_METHOD_GET,
|
|
15
|
-
HTTP2_METHOD_HEAD,
|
|
16
|
-
} = http2.constants;
|
|
17
|
-
|
|
18
|
-
export const preflight = (options) => {
|
|
19
|
-
const { cookies, credentials, h2, headers, method, url } = options;
|
|
20
|
-
|
|
21
|
-
if (h2) {
|
|
22
|
-
options.endStream = [
|
|
23
|
-
HTTP2_METHOD_GET,
|
|
24
|
-
HTTP2_METHOD_HEAD,
|
|
25
|
-
].includes(method);
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
if (cookies !== false && credentials !== requestCredentials.omit) {
|
|
29
|
-
let cookie = Cookies.jar.has(url.origin);
|
|
30
|
-
|
|
31
|
-
if (cookies ===
|
|
32
|
-
requestCredentials.include,
|
|
33
|
-
requestCredentials.sameOrigin,
|
|
34
|
-
].includes(credentials)) {
|
|
35
|
-
if (cookie) {
|
|
36
|
-
cookie = new Cookies(cookies, options);
|
|
37
|
-
|
|
38
|
-
Cookies.jar.get(url.origin).forEach((val, key) => {
|
|
39
|
-
if (!cookie.has(key)) {
|
|
40
|
-
cookie.set(key, val);
|
|
41
|
-
}
|
|
42
|
-
});
|
|
43
|
-
Cookies.jar.set(url.origin, cookie);
|
|
44
|
-
} else {
|
|
45
|
-
cookie = new Cookies(cookies, options);
|
|
46
|
-
Cookies.jar.set(url.origin, cookie);
|
|
47
|
-
}
|
|
48
|
-
} else {
|
|
49
|
-
cookie &&= Cookies.jar.get(url.origin);
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
options.headers = {
|
|
53
|
-
...cookie && { [HTTP2_HEADER_COOKIE]: cookie },
|
|
54
|
-
...headers,
|
|
55
|
-
};
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
if (credentials === requestCredentials.omit) {
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
1
|
+
import http2 from 'node:http2';
|
|
2
|
+
import { isZstdSupported } from './config.js';
|
|
3
|
+
import { requestCredentials } from './constants.js';
|
|
4
|
+
import { Cookies } from './cookies.js';
|
|
5
|
+
|
|
6
|
+
const {
|
|
7
|
+
HTTP2_HEADER_ACCEPT_ENCODING,
|
|
8
|
+
HTTP2_HEADER_AUTHORITY,
|
|
9
|
+
HTTP2_HEADER_AUTHORIZATION,
|
|
10
|
+
HTTP2_HEADER_COOKIE,
|
|
11
|
+
HTTP2_HEADER_METHOD,
|
|
12
|
+
HTTP2_HEADER_PATH,
|
|
13
|
+
HTTP2_HEADER_SCHEME,
|
|
14
|
+
HTTP2_METHOD_GET,
|
|
15
|
+
HTTP2_METHOD_HEAD,
|
|
16
|
+
} = http2.constants;
|
|
17
|
+
|
|
18
|
+
export const preflight = (options) => {
|
|
19
|
+
const { cookies, credentials, h2, headers, method, url } = options;
|
|
20
|
+
|
|
21
|
+
if (h2) {
|
|
22
|
+
options.endStream = [
|
|
23
|
+
HTTP2_METHOD_GET,
|
|
24
|
+
HTTP2_METHOD_HEAD,
|
|
25
|
+
].includes(method);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
if (cookies !== false && credentials !== requestCredentials.omit) {
|
|
29
|
+
let cookie = Cookies.jar.has(url.origin);
|
|
30
|
+
|
|
31
|
+
if (Object(cookies) === cookies && [
|
|
32
|
+
requestCredentials.include,
|
|
33
|
+
requestCredentials.sameOrigin,
|
|
34
|
+
].includes(credentials)) {
|
|
35
|
+
if (cookie) {
|
|
36
|
+
cookie = new Cookies(cookies, options);
|
|
37
|
+
|
|
38
|
+
Cookies.jar.get(url.origin).forEach((val, key) => {
|
|
39
|
+
if (!cookie.has(key)) {
|
|
40
|
+
cookie.set(key, val);
|
|
41
|
+
}
|
|
42
|
+
});
|
|
43
|
+
Cookies.jar.set(url.origin, cookie);
|
|
44
|
+
} else {
|
|
45
|
+
cookie = new Cookies(cookies, options);
|
|
46
|
+
Cookies.jar.set(url.origin, cookie);
|
|
47
|
+
}
|
|
48
|
+
} else {
|
|
49
|
+
cookie &&= Cookies.jar.get(url.origin);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
options.headers = {
|
|
53
|
+
...cookie && { [HTTP2_HEADER_COOKIE]: cookie },
|
|
54
|
+
...headers,
|
|
55
|
+
};
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
if (credentials === requestCredentials.omit) {
|
|
59
|
+
[
|
|
60
|
+
HTTP2_HEADER_AUTHORIZATION,
|
|
61
|
+
HTTP2_HEADER_COOKIE,
|
|
62
|
+
].forEach((key) => Reflect.deleteProperty(options.headers, key));
|
|
63
|
+
options.cookies = false;
|
|
64
|
+
url.password = url.username = '';
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
if (!h2) {
|
|
68
|
+
[
|
|
69
|
+
HTTP2_HEADER_AUTHORITY,
|
|
70
|
+
HTTP2_HEADER_METHOD,
|
|
71
|
+
HTTP2_HEADER_PATH,
|
|
72
|
+
HTTP2_HEADER_SCHEME,
|
|
73
|
+
].forEach((key) => Reflect.deleteProperty(options.headers, key));
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
options.headers = {
|
|
77
|
+
...Object.entries(options.headers ?? {})
|
|
78
|
+
.reduce((acc, [key, val]) => {
|
|
79
|
+
acc[key.toLowerCase()] = val;
|
|
80
|
+
const rex = /\s?zstd,?/gi;
|
|
81
|
+
|
|
82
|
+
if (acc[HTTP2_HEADER_ACCEPT_ENCODING]?.match(rex) && !isZstdSupported) {
|
|
83
|
+
acc[HTTP2_HEADER_ACCEPT_ENCODING] = val.replace(rex, '').trim();
|
|
84
|
+
if (!acc[HTTP2_HEADER_ACCEPT_ENCODING]) {
|
|
85
|
+
Reflect.deleteProperty(acc, HTTP2_HEADER_ACCEPT_ENCODING);
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
return acc;
|
|
90
|
+
}, {}),
|
|
91
|
+
...h2 && {
|
|
92
|
+
[HTTP2_HEADER_AUTHORITY]: url.host,
|
|
93
|
+
[HTTP2_HEADER_METHOD]: method,
|
|
94
|
+
[HTTP2_HEADER_PATH]: `${ url.pathname }${ url.search }`,
|
|
95
|
+
[HTTP2_HEADER_SCHEME]: url.protocol.replace(/\p{Punctuation}/gu, ''),
|
|
96
|
+
},
|
|
97
|
+
};
|
|
98
|
+
|
|
99
|
+
return options;
|
|
100
|
+
};
|
package/src/redirects.js
ADDED
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
import http2 from 'node:http2';
|
|
2
|
+
import { isReadable } from 'node:stream';
|
|
3
|
+
import {
|
|
4
|
+
requestCredentials,
|
|
5
|
+
requestRedirect,
|
|
6
|
+
requestRedirectCodes,
|
|
7
|
+
} from './constants.js';
|
|
8
|
+
import { RequestError } from './errors.js';
|
|
9
|
+
import rekwest from './index.js';
|
|
10
|
+
import {
|
|
11
|
+
isPipeStream,
|
|
12
|
+
sameOrigin,
|
|
13
|
+
} from './utils.js';
|
|
14
|
+
|
|
15
|
+
const {
|
|
16
|
+
HTTP2_HEADER_LOCATION,
|
|
17
|
+
HTTP2_METHOD_GET,
|
|
18
|
+
HTTP2_METHOD_HEAD,
|
|
19
|
+
HTTP2_METHOD_POST,
|
|
20
|
+
HTTP_STATUS_FOUND,
|
|
21
|
+
HTTP_STATUS_MOVED_PERMANENTLY,
|
|
22
|
+
HTTP_STATUS_PERMANENT_REDIRECT,
|
|
23
|
+
HTTP_STATUS_SEE_OTHER,
|
|
24
|
+
HTTP_STATUS_TEMPORARY_REDIRECT,
|
|
25
|
+
} = http2.constants;
|
|
26
|
+
|
|
27
|
+
export const redirects = (res, options) => {
|
|
28
|
+
const { credentials, follow, redirect, url } = options;
|
|
29
|
+
|
|
30
|
+
if (follow && /3\d{2}/.test(res.statusCode) && res.headers[HTTP2_HEADER_LOCATION]) {
|
|
31
|
+
if (!requestRedirectCodes.includes(res.statusCode)) {
|
|
32
|
+
return res.emit('error', new RangeError(`Invalid status code: ${ res.statusCode }`));
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
if (redirect === requestRedirect.error) {
|
|
36
|
+
return res.emit('error', new RequestError(`Unexpected redirect, redirect mode is set to '${ redirect }'.`));
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
if (redirect === requestRedirect.follow) {
|
|
40
|
+
const location = new URL(res.headers[HTTP2_HEADER_LOCATION], url);
|
|
41
|
+
|
|
42
|
+
if (!/^https?:/i.test(location.protocol)) {
|
|
43
|
+
return res.emit('error', new RequestError('URL scheme must be "http" or "https".'));
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
if (!sameOrigin(location, url)) {
|
|
47
|
+
if (credentials !== requestCredentials.include) {
|
|
48
|
+
options.credentials = requestCredentials.omit;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
options.h2 = false;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
if ([
|
|
55
|
+
HTTP_STATUS_PERMANENT_REDIRECT,
|
|
56
|
+
HTTP_STATUS_TEMPORARY_REDIRECT,
|
|
57
|
+
].includes(res.statusCode) && isPipeStream(options.body) && !isReadable(options.body)) {
|
|
58
|
+
return res.emit('error', new RequestError(`Unable to ${ redirect } redirect with streamable body.`));
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
if (([
|
|
62
|
+
HTTP_STATUS_MOVED_PERMANENTLY,
|
|
63
|
+
HTTP_STATUS_FOUND,
|
|
64
|
+
].includes(res.statusCode) && options.method === HTTP2_METHOD_POST)
|
|
65
|
+
|| (res.statusCode === HTTP_STATUS_SEE_OTHER && ![
|
|
66
|
+
HTTP2_METHOD_GET,
|
|
67
|
+
HTTP2_METHOD_HEAD,
|
|
68
|
+
].includes(options.method))) {
|
|
69
|
+
options.body = null;
|
|
70
|
+
options.method = HTTP2_METHOD_GET;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
options.follow--;
|
|
74
|
+
options.redirected = true;
|
|
75
|
+
|
|
76
|
+
return rekwest(location, options);
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
};
|
package/src/retries.js
ADDED
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import http2 from 'node:http2';
|
|
2
|
+
import { isReadable } from 'node:stream';
|
|
3
|
+
import { setTimeout as setTimeoutPromise } from 'node:timers/promises';
|
|
4
|
+
import { RequestError } from './errors.js';
|
|
5
|
+
import rekwest from './index.js';
|
|
6
|
+
import { isPipeStream } from './utils.js';
|
|
7
|
+
|
|
8
|
+
const {
|
|
9
|
+
HTTP2_HEADER_RETRY_AFTER,
|
|
10
|
+
HTTP2_METHOD_GET,
|
|
11
|
+
HTTP2_METHOD_HEAD,
|
|
12
|
+
} = http2.constants;
|
|
13
|
+
|
|
14
|
+
export const retries = (ex, options) => {
|
|
15
|
+
const { body, maxRetryAfter, method, retry, url } = options;
|
|
16
|
+
|
|
17
|
+
if (retry?.attempts > 0) {
|
|
18
|
+
if (![
|
|
19
|
+
HTTP2_METHOD_GET,
|
|
20
|
+
HTTP2_METHOD_HEAD,
|
|
21
|
+
].includes(method) && isPipeStream(body) && !isReadable(body)) {
|
|
22
|
+
throw new RequestError('Request stream already read.', { cause: ex });
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
if (retry.errorCodes?.includes(ex.code) || retry.statusCodes?.includes(ex.statusCode)) {
|
|
26
|
+
let { interval } = retry;
|
|
27
|
+
|
|
28
|
+
if (retry.retryAfter && ex.headers?.[HTTP2_HEADER_RETRY_AFTER]) {
|
|
29
|
+
interval = ex.headers[HTTP2_HEADER_RETRY_AFTER];
|
|
30
|
+
interval = Math.abs(Number(interval) * 1e3 || new Date(interval) - Date.now()) || 0;
|
|
31
|
+
if (interval > maxRetryAfter) {
|
|
32
|
+
throw new RequestError(
|
|
33
|
+
`Maximum '${ HTTP2_HEADER_RETRY_AFTER }' limit exceeded: ${ interval } ms.`,
|
|
34
|
+
{ cause: ex },
|
|
35
|
+
);
|
|
36
|
+
}
|
|
37
|
+
} else {
|
|
38
|
+
interval = new Function('interval', `return Math.ceil(${ retry.backoffStrategy });`)(interval);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
if (interval < 0) {
|
|
42
|
+
interval = 0;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
retry.attempts--;
|
|
46
|
+
retry.interval = interval;
|
|
47
|
+
|
|
48
|
+
return setTimeoutPromise(interval).then(() => rekwest(url, options));
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
};
|
package/src/transfer.js
ADDED
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
import http from 'node:http';
|
|
2
|
+
import http2 from 'node:http2';
|
|
3
|
+
import https from 'node:https';
|
|
4
|
+
import { ackn } from './ackn.js';
|
|
5
|
+
import { RequestError } from './errors.js';
|
|
6
|
+
import { postflight } from './postflight.js';
|
|
7
|
+
import { preflight } from './preflight.js';
|
|
8
|
+
import { retries } from './retries.js';
|
|
9
|
+
import { transform } from './transform.js';
|
|
10
|
+
import {
|
|
11
|
+
dispatch,
|
|
12
|
+
snoop,
|
|
13
|
+
} from './utils.js';
|
|
14
|
+
|
|
15
|
+
export const transfer = async (options) => {
|
|
16
|
+
const { digest, redirected, thenable, url } = options;
|
|
17
|
+
|
|
18
|
+
if (options.follow === 0) {
|
|
19
|
+
throw new RequestError(`Maximum redirect reached at: ${ url.href }`);
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
if (url.protocol === 'https:') {
|
|
23
|
+
options = !options.h2 ? await ackn(options) : {
|
|
24
|
+
...options, createConnection: null, protocol: url.protocol,
|
|
25
|
+
};
|
|
26
|
+
} else if (Reflect.has(options, 'alpnProtocol')) {
|
|
27
|
+
for (const it of [
|
|
28
|
+
'alpnProtocol',
|
|
29
|
+
'createConnection',
|
|
30
|
+
'h2',
|
|
31
|
+
'protocol',
|
|
32
|
+
]) { Reflect.deleteProperty(options, it); }
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
try {
|
|
36
|
+
options = await transform(preflight(options));
|
|
37
|
+
} catch (ex) {
|
|
38
|
+
options.createConnection?.().destroy();
|
|
39
|
+
throw ex;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
const promise = new Promise((resolve, reject) => {
|
|
43
|
+
let client, req;
|
|
44
|
+
|
|
45
|
+
if (options.h2) {
|
|
46
|
+
client = http2.connect(url.origin, options);
|
|
47
|
+
req = client.request(options.headers, options);
|
|
48
|
+
} else {
|
|
49
|
+
const { request } = url.protocol === 'http:' ? http : https;
|
|
50
|
+
|
|
51
|
+
req = request(url, options);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
snoop(client, req, options);
|
|
55
|
+
|
|
56
|
+
req.once('aborted', reject);
|
|
57
|
+
req.once('error', reject);
|
|
58
|
+
req.once('frameError', reject);
|
|
59
|
+
req.once('goaway', reject);
|
|
60
|
+
req.once('response', (res) => postflight(req, res, options, {
|
|
61
|
+
reject, resolve,
|
|
62
|
+
}));
|
|
63
|
+
|
|
64
|
+
dispatch(req, options);
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
try {
|
|
68
|
+
const res = await promise;
|
|
69
|
+
|
|
70
|
+
if (digest && !redirected) {
|
|
71
|
+
res.body = await res.body();
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
return res;
|
|
75
|
+
} catch (ex) {
|
|
76
|
+
const willRetry = retries(ex, options);
|
|
77
|
+
|
|
78
|
+
if (willRetry) {
|
|
79
|
+
return willRetry;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
if (digest && !redirected && ex.body) {
|
|
83
|
+
ex.body = await ex.body();
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
if (!thenable) {
|
|
87
|
+
throw ex;
|
|
88
|
+
} else {
|
|
89
|
+
return ex;
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
};
|
package/src/transform.js
ADDED
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
import http2 from 'node:http2';
|
|
2
|
+
import {
|
|
3
|
+
isReadable,
|
|
4
|
+
Readable,
|
|
5
|
+
} from 'node:stream';
|
|
6
|
+
import { buffer } from 'node:stream/consumers';
|
|
7
|
+
import { types } from 'node:util';
|
|
8
|
+
import { encode } from './codecs.js';
|
|
9
|
+
import { FormData } from './formdata.js';
|
|
10
|
+
import {
|
|
11
|
+
APPLICATION_FORM_URLENCODED,
|
|
12
|
+
APPLICATION_JSON,
|
|
13
|
+
APPLICATION_OCTET_STREAM,
|
|
14
|
+
} from './mediatypes.js';
|
|
15
|
+
import {
|
|
16
|
+
isFileLike,
|
|
17
|
+
isReadableStream,
|
|
18
|
+
} from './utils.js';
|
|
19
|
+
|
|
20
|
+
const {
|
|
21
|
+
HTTP2_HEADER_CONTENT_ENCODING,
|
|
22
|
+
HTTP2_HEADER_CONTENT_LENGTH,
|
|
23
|
+
HTTP2_HEADER_CONTENT_TYPE,
|
|
24
|
+
} = http2.constants;
|
|
25
|
+
|
|
26
|
+
export const transform = async (options) => {
|
|
27
|
+
let { body, headers } = options;
|
|
28
|
+
|
|
29
|
+
if (!body) {
|
|
30
|
+
return options;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
if (!Buffer.isBuffer(body)) {
|
|
34
|
+
switch (true) {
|
|
35
|
+
case isFileLike(body): {
|
|
36
|
+
headers = {
|
|
37
|
+
[HTTP2_HEADER_CONTENT_LENGTH]: body.size,
|
|
38
|
+
[HTTP2_HEADER_CONTENT_TYPE]: body.type || APPLICATION_OCTET_STREAM,
|
|
39
|
+
};
|
|
40
|
+
body = body.stream();
|
|
41
|
+
break;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
case FormData.alike(body): {
|
|
45
|
+
body = FormData.actuate(body);
|
|
46
|
+
headers = { [HTTP2_HEADER_CONTENT_TYPE]: body.contentType };
|
|
47
|
+
break;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
case types.isAnyArrayBuffer(body): {
|
|
51
|
+
body = Buffer.from(body);
|
|
52
|
+
break;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
case types.isArrayBufferView(body): {
|
|
56
|
+
body = Buffer.from(body.buffer, body.byteOffset, body.byteLength);
|
|
57
|
+
break;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
case Object(body) === body && !Reflect.has(body, Symbol.asyncIterator): {
|
|
61
|
+
if (body.constructor === URLSearchParams) {
|
|
62
|
+
headers = { [HTTP2_HEADER_CONTENT_TYPE]: APPLICATION_FORM_URLENCODED };
|
|
63
|
+
body = body.toString();
|
|
64
|
+
} else if (!(!Array.isArray(body) && Reflect.has(body, Symbol.iterator))) {
|
|
65
|
+
headers = { [HTTP2_HEADER_CONTENT_TYPE]: APPLICATION_JSON };
|
|
66
|
+
body = JSON.stringify(body);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
break;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
default:
|
|
73
|
+
break;
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
const encodings = options.headers[HTTP2_HEADER_CONTENT_ENCODING];
|
|
78
|
+
|
|
79
|
+
if (Object(body) === body
|
|
80
|
+
&& (Reflect.has(body, Symbol.asyncIterator) || (!Array.isArray(body) && Reflect.has(body, Symbol.iterator)))) {
|
|
81
|
+
body = isReadable(body) ? (isReadableStream(body) ? Readable.fromWeb(body) : body) : Readable.from(body);
|
|
82
|
+
body = encodings ? encode(body, encodings, options) : body;
|
|
83
|
+
} else if (encodings) {
|
|
84
|
+
body = await buffer(encode(Readable.from(body), encodings, options));
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
if (options.bufferBody && Object(body) === body) {
|
|
88
|
+
if (isReadable(body)) {
|
|
89
|
+
body = await buffer(body);
|
|
90
|
+
} else if (Reflect.has(body, Symbol.asyncIterator)) {
|
|
91
|
+
body = await buffer(body);
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
Object.assign(options.headers, {
|
|
96
|
+
...headers,
|
|
97
|
+
...!body[Symbol.asyncIterator] && {
|
|
98
|
+
[HTTP2_HEADER_CONTENT_LENGTH]: Buffer.byteLength(body),
|
|
99
|
+
},
|
|
100
|
+
...options.headers[HTTP2_HEADER_CONTENT_TYPE] && {
|
|
101
|
+
[HTTP2_HEADER_CONTENT_TYPE]: options.headers[HTTP2_HEADER_CONTENT_TYPE],
|
|
102
|
+
},
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
return {
|
|
106
|
+
...options,
|
|
107
|
+
body,
|
|
108
|
+
};
|
|
109
|
+
};
|