@nxtedition/nxt-undici 3.2.3 → 3.3.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/lib/index.js +66 -67
- package/lib/interceptor/cache.js +2 -4
- package/lib/interceptor/dns.js +39 -25
- package/lib/interceptor/lookup.js +2 -3
- package/lib/interceptor/proxy.js +39 -39
- package/package.json +1 -1
package/lib/index.js
CHANGED
|
@@ -20,46 +20,10 @@ export const interceptors = {
|
|
|
20
20
|
export { parseHeaders } from './utils.js'
|
|
21
21
|
export { Client, Pool, Agent, getGlobalDispatcher, setGlobalDispatcher } from '@nxtedition/undici'
|
|
22
22
|
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
url = new URL(url)
|
|
28
|
-
} else if (url instanceof URL) {
|
|
29
|
-
// Do nothing...
|
|
30
|
-
} else if (typeof url.origin === 'string' && typeof (url.path ?? url.pathname) === 'string') {
|
|
31
|
-
// Do nothing...
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
if (opts == null && typeof url === 'object' && url != null) {
|
|
35
|
-
opts = url
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
if (url) {
|
|
39
|
-
// Do nothing...
|
|
40
|
-
} else if (typeof opts.url === 'string') {
|
|
41
|
-
url = new URL(opts.url)
|
|
42
|
-
} else if (url.url instanceof URL) {
|
|
43
|
-
url = opts.url
|
|
44
|
-
} else if (typeof opts.origin === 'string' && typeof (opts.path ?? opts.pathname) === 'string') {
|
|
45
|
-
url = opts
|
|
46
|
-
} else {
|
|
47
|
-
throw new Error('missing url')
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
const method = opts.method ?? (opts.body ? 'POST' : 'GET')
|
|
51
|
-
const headers = parseHeaders(opts.headers)
|
|
52
|
-
|
|
53
|
-
const userAgent = opts.userAgent ?? globalThis.userAgent
|
|
54
|
-
if (userAgent && headers?.['user-agent'] !== userAgent) {
|
|
55
|
-
headers['user-agent'] = userAgent
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
const baseDispatcher = opts.dispatcher ?? undici.getGlobalDispatcher()
|
|
59
|
-
|
|
60
|
-
let dispatcher = dispatcherCache.get(baseDispatcher)
|
|
61
|
-
if (dispatcher == null) {
|
|
62
|
-
dispatcher = baseDispatcher.compose(
|
|
23
|
+
function wrapDispatcher(dispatcher) {
|
|
24
|
+
let wrappedDispatcher = dispatcherCache.get(dispatcher)
|
|
25
|
+
if (wrappedDispatcher == null) {
|
|
26
|
+
wrappedDispatcher = dispatcher.compose(
|
|
63
27
|
interceptors.responseError(),
|
|
64
28
|
interceptors.requestBodyFactory(),
|
|
65
29
|
interceptors.log(),
|
|
@@ -71,34 +35,69 @@ export async function request(url, opts) {
|
|
|
71
35
|
interceptors.redirect(),
|
|
72
36
|
interceptors.cache(),
|
|
73
37
|
interceptors.proxy(),
|
|
38
|
+
(dispatch) => (opts, handler) => {
|
|
39
|
+
const headers = parseHeaders(opts.headers)
|
|
40
|
+
|
|
41
|
+
const userAgent = opts.userAgent ?? globalThis.userAgent
|
|
42
|
+
if (userAgent && headers?.['user-agent'] !== userAgent) {
|
|
43
|
+
headers['user-agent'] = userAgent
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
const url = opts.url ? new URL(opts.url) : null
|
|
47
|
+
|
|
48
|
+
return dispatch(
|
|
49
|
+
{
|
|
50
|
+
id: opts.id,
|
|
51
|
+
origin: opts.origin ?? url?.origin,
|
|
52
|
+
path: opts.path ?? (url?.search ? `${url.pathname}${url.search}` : url?.pathname),
|
|
53
|
+
method: opts.method ?? (opts.body ? 'POST' : 'GET'),
|
|
54
|
+
body: opts.body,
|
|
55
|
+
query: opts.query,
|
|
56
|
+
headers,
|
|
57
|
+
signal: opts.signal,
|
|
58
|
+
reset: opts.reset ?? false,
|
|
59
|
+
blocking: opts.blocking ?? false,
|
|
60
|
+
headersTimeout: opts.headersTimeout,
|
|
61
|
+
bodyTimeout: opts.bodyTimeout,
|
|
62
|
+
idempotent: opts.idempotent,
|
|
63
|
+
retry: opts.retry ?? 8,
|
|
64
|
+
proxy: opts.proxy ?? false,
|
|
65
|
+
cache: opts.cache ?? false,
|
|
66
|
+
upgrade: opts.upgrade ?? false,
|
|
67
|
+
follow: opts.follow ?? 8,
|
|
68
|
+
error: opts.error ?? true,
|
|
69
|
+
verify: opts.verify ?? true,
|
|
70
|
+
logger: opts.logger ?? null,
|
|
71
|
+
lookup: opts.lookup ?? null,
|
|
72
|
+
dns: opts.dns ?? true,
|
|
73
|
+
},
|
|
74
|
+
handler,
|
|
75
|
+
)
|
|
76
|
+
},
|
|
74
77
|
)
|
|
75
|
-
dispatcherCache.set(
|
|
78
|
+
dispatcherCache.set(dispatcher, wrappedDispatcher)
|
|
79
|
+
}
|
|
80
|
+
return wrappedDispatcher
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
export function dispatch(dispatcher, opts, handler) {
|
|
84
|
+
return wrapDispatcher(dispatcher).dispatch(opts, handler)
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
export async function request(url, opts) {
|
|
88
|
+
// TODO (fix): More argument validation...
|
|
89
|
+
|
|
90
|
+
if (typeof url === 'string') {
|
|
91
|
+
opts = { url: new URL(url), ...opts }
|
|
92
|
+
} else if (url instanceof URL) {
|
|
93
|
+
opts = { url, ...opts }
|
|
94
|
+
} else if (typeof url.origin === 'string' && typeof (url.path ?? url.pathname) === 'string') {
|
|
95
|
+
opts = { url, ...opts }
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
if (opts == null && typeof url === 'object' && url != null) {
|
|
99
|
+
opts = url
|
|
76
100
|
}
|
|
77
101
|
|
|
78
|
-
return await dispatcher.request(
|
|
79
|
-
id: opts.id,
|
|
80
|
-
origin: url.origin,
|
|
81
|
-
path: url.path ?? (url.search ? `${url.pathname}${url.search}` : url.pathname),
|
|
82
|
-
method,
|
|
83
|
-
dispatcher,
|
|
84
|
-
body: opts.body,
|
|
85
|
-
query: opts.query,
|
|
86
|
-
headers,
|
|
87
|
-
signal: opts.signal,
|
|
88
|
-
reset: opts.reset ?? false,
|
|
89
|
-
blocking: opts.blocking ?? false,
|
|
90
|
-
headersTimeout: opts.headersTimeout,
|
|
91
|
-
bodyTimeout: opts.bodyTimeout,
|
|
92
|
-
idempotent: opts.idempotent,
|
|
93
|
-
retry: opts.retry ?? 8,
|
|
94
|
-
proxy: opts.proxy ?? false,
|
|
95
|
-
cache: opts.cache ?? false,
|
|
96
|
-
upgrade: opts.upgrade ?? false,
|
|
97
|
-
follow: opts.follow ?? 8,
|
|
98
|
-
error: opts.error ?? true,
|
|
99
|
-
verify: opts.verify ?? true,
|
|
100
|
-
logger: opts.logger ?? null,
|
|
101
|
-
lookup: opts.lookup ?? null,
|
|
102
|
-
dns: opts.dns ?? null,
|
|
103
|
-
})
|
|
102
|
+
return await wrapDispatcher(opts.dispatcher ?? undici.getGlobalDispatcher()).request(opts)
|
|
104
103
|
}
|
package/lib/interceptor/cache.js
CHANGED
|
@@ -127,8 +127,7 @@ export default (opts) => (dispatch) => (opts, handler) => {
|
|
|
127
127
|
|
|
128
128
|
// TODO (fix): Cache other methods?
|
|
129
129
|
if (opts.method !== 'GET' && opts.method !== 'HEAD') {
|
|
130
|
-
dispatch(opts, handler)
|
|
131
|
-
return
|
|
130
|
+
return dispatch(opts, handler)
|
|
132
131
|
}
|
|
133
132
|
|
|
134
133
|
if (opts.headers?.['cache-control'] || opts.headers?.authorization) {
|
|
@@ -141,8 +140,7 @@ export default (opts) => (dispatch) => (opts, handler) => {
|
|
|
141
140
|
// cacheControl['min-fresh']
|
|
142
141
|
// cacheControl['no-transform']
|
|
143
142
|
// cacheControl['only-if-cached']
|
|
144
|
-
dispatch(opts, handler)
|
|
145
|
-
return
|
|
143
|
+
return dispatch(opts, handler)
|
|
146
144
|
}
|
|
147
145
|
|
|
148
146
|
// TODO (fix): Support body...
|
package/lib/interceptor/dns.js
CHANGED
|
@@ -1,19 +1,20 @@
|
|
|
1
1
|
import assert from 'node:assert'
|
|
2
2
|
import { DecoratorHandler } from '../utils.js'
|
|
3
3
|
import CacheableLookup from 'cacheable-lookup'
|
|
4
|
+
import net from 'net'
|
|
4
5
|
|
|
5
|
-
|
|
6
|
+
const DEFAULT_RESOLVER = new CacheableLookup()
|
|
6
7
|
|
|
7
8
|
class Handler extends DecoratorHandler {
|
|
8
9
|
#handler
|
|
9
|
-
#
|
|
10
|
+
#resolver
|
|
10
11
|
#key
|
|
11
12
|
|
|
12
|
-
constructor({
|
|
13
|
+
constructor({ resolver, key }, { handler }) {
|
|
13
14
|
super(handler)
|
|
14
15
|
|
|
15
16
|
this.#handler = handler
|
|
16
|
-
this.#
|
|
17
|
+
this.#resolver = resolver
|
|
17
18
|
this.#key = key
|
|
18
19
|
}
|
|
19
20
|
|
|
@@ -28,53 +29,66 @@ class Handler extends DecoratorHandler {
|
|
|
28
29
|
'ENETUNREACH',
|
|
29
30
|
'EHOSTDOWN',
|
|
30
31
|
'EHOSTUNREACH',
|
|
32
|
+
'ENODATA',
|
|
31
33
|
'EPIPE',
|
|
32
34
|
].includes(err.code)
|
|
33
35
|
) {
|
|
34
|
-
this.#
|
|
36
|
+
this.#resolver.clear(this.#key)
|
|
35
37
|
}
|
|
36
38
|
|
|
37
39
|
return this.#handler.onError(err)
|
|
38
40
|
}
|
|
39
41
|
}
|
|
40
42
|
|
|
41
|
-
export default (
|
|
42
|
-
const dns = opts.dns
|
|
43
|
+
export default (interceptorOpts) => (dispatch) => (opts, handler) => {
|
|
44
|
+
const dns = opts.dns
|
|
43
45
|
|
|
44
46
|
if (!dns) {
|
|
45
|
-
dispatch(opts, handler)
|
|
46
|
-
return
|
|
47
|
+
return dispatch(opts, handler)
|
|
47
48
|
}
|
|
48
49
|
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
50
|
+
const {
|
|
51
|
+
resolver = interceptorOpts?.resolver ?? DEFAULT_RESOLVER,
|
|
52
|
+
family = interceptorOpts?.family,
|
|
53
|
+
hints = interceptorOpts?.hints,
|
|
54
|
+
order = interceptorOpts?.order ?? 'ipv4first',
|
|
55
|
+
all = interceptorOpts?.all ?? true,
|
|
56
|
+
} = dns
|
|
57
|
+
|
|
58
|
+
assert(typeof resolver.lookup === 'function')
|
|
52
59
|
|
|
53
|
-
|
|
60
|
+
const { hostname } = new URL(opts.origin)
|
|
54
61
|
|
|
55
|
-
|
|
62
|
+
if (net.isIP(hostname)) {
|
|
63
|
+
dispatch(opts, handler)
|
|
64
|
+
} else {
|
|
65
|
+
const callback = (err, val) => {
|
|
56
66
|
if (err) {
|
|
57
67
|
handler.onConnect(() => {})
|
|
58
68
|
handler.onError(err)
|
|
59
69
|
} else {
|
|
60
70
|
const url = new URL(opts.origin)
|
|
61
|
-
url.hostname =
|
|
71
|
+
url.hostname = Array.isArray(val)
|
|
72
|
+
? val[Math.floor(val.length * Math.random())].address
|
|
73
|
+
: val?.address ?? val
|
|
62
74
|
dispatch(
|
|
63
75
|
{ ...opts, origin: url.origin },
|
|
64
|
-
new Handler({
|
|
76
|
+
resolver.clear ? new Handler({ resolver, key: hostname }, { handler }) : handler,
|
|
65
77
|
)
|
|
66
78
|
}
|
|
67
79
|
}
|
|
68
80
|
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
thenable
|
|
72
|
-
(
|
|
73
|
-
|
|
74
|
-
|
|
81
|
+
try {
|
|
82
|
+
const thenable = resolver.lookup(hostname, { family, hints, order, all }, callback)
|
|
83
|
+
if (typeof thenable?.then === 'function') {
|
|
84
|
+
thenable.then(
|
|
85
|
+
(val) => callback(null, val),
|
|
86
|
+
(err) => callback(err),
|
|
87
|
+
)
|
|
88
|
+
}
|
|
89
|
+
} catch (err) {
|
|
90
|
+
handler.onConnect(() => {})
|
|
91
|
+
handler.onError(err)
|
|
75
92
|
}
|
|
76
|
-
} catch (err) {
|
|
77
|
-
handler.onConnect(() => {})
|
|
78
|
-
handler.onError(err)
|
|
79
93
|
}
|
|
80
94
|
}
|
|
@@ -2,8 +2,7 @@ export default (opts) => (dispatch) => (opts, handler) => {
|
|
|
2
2
|
const lookup = opts.lookup
|
|
3
3
|
|
|
4
4
|
if (!lookup) {
|
|
5
|
-
dispatch(opts, handler)
|
|
6
|
-
return
|
|
5
|
+
return dispatch(opts, handler)
|
|
7
6
|
}
|
|
8
7
|
|
|
9
8
|
const callback = (err, origin) => {
|
|
@@ -24,6 +23,6 @@ export default (opts) => (dispatch) => (opts, handler) => {
|
|
|
24
23
|
)
|
|
25
24
|
}
|
|
26
25
|
} catch (err) {
|
|
27
|
-
|
|
26
|
+
callback(err)
|
|
28
27
|
}
|
|
29
28
|
}
|
package/lib/interceptor/proxy.js
CHANGED
|
@@ -55,45 +55,6 @@ class Handler extends DecoratorHandler {
|
|
|
55
55
|
}
|
|
56
56
|
}
|
|
57
57
|
|
|
58
|
-
export default (opts) => (dispatch) => (opts, handler) => {
|
|
59
|
-
if (!opts.proxy) {
|
|
60
|
-
return dispatch(opts, handler)
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
const expectsPayload =
|
|
64
|
-
opts.method === 'PUT' ||
|
|
65
|
-
opts.method === 'POST' ||
|
|
66
|
-
opts.method === 'PATCH' ||
|
|
67
|
-
opts.method === 'QUERY'
|
|
68
|
-
|
|
69
|
-
const headers = reduceHeaders(
|
|
70
|
-
{
|
|
71
|
-
headers: opts.headers ?? {},
|
|
72
|
-
httpVersion: opts.proxy.httpVersion ?? opts.proxy.req?.httpVersion,
|
|
73
|
-
socket: opts.proxy.socket ?? opts.proxy.req?.socket,
|
|
74
|
-
proxyName: opts.proxy.name,
|
|
75
|
-
},
|
|
76
|
-
(obj, key, val) => {
|
|
77
|
-
if (key === 'content-length' && !expectsPayload) {
|
|
78
|
-
// https://tools.ietf.org/html/rfc7230#section-3.3.2
|
|
79
|
-
// A user agent SHOULD NOT send a Content-Length header field when
|
|
80
|
-
// the request message does not contain a payload body and the method
|
|
81
|
-
// semantics do not anticipate such a body.
|
|
82
|
-
// undici will error if provided an unexpected content-length: 0 header.
|
|
83
|
-
}
|
|
84
|
-
if (key === 'expect') {
|
|
85
|
-
// undici doesn't support expect header.
|
|
86
|
-
} else {
|
|
87
|
-
obj[key] = val
|
|
88
|
-
}
|
|
89
|
-
return obj
|
|
90
|
-
},
|
|
91
|
-
{},
|
|
92
|
-
)
|
|
93
|
-
|
|
94
|
-
return dispatch({ ...opts, headers }, new Handler(opts.proxy, { handler }))
|
|
95
|
-
}
|
|
96
|
-
|
|
97
58
|
// This expression matches hop-by-hop headers.
|
|
98
59
|
// These headers are meaningful only for a single transport-level connection,
|
|
99
60
|
// and must not be retransmitted by proxies or cached.
|
|
@@ -201,3 +162,42 @@ function printIp(address, port) {
|
|
|
201
162
|
}
|
|
202
163
|
return str
|
|
203
164
|
}
|
|
165
|
+
|
|
166
|
+
export default (opts) => (dispatch) => (opts, handler) => {
|
|
167
|
+
if (!opts.proxy) {
|
|
168
|
+
return dispatch(opts, handler)
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
const expectsPayload =
|
|
172
|
+
opts.method === 'PUT' ||
|
|
173
|
+
opts.method === 'POST' ||
|
|
174
|
+
opts.method === 'PATCH' ||
|
|
175
|
+
opts.method === 'QUERY'
|
|
176
|
+
|
|
177
|
+
const headers = reduceHeaders(
|
|
178
|
+
{
|
|
179
|
+
headers: opts.headers ?? {},
|
|
180
|
+
httpVersion: opts.proxy.httpVersion ?? opts.proxy.req?.httpVersion,
|
|
181
|
+
socket: opts.proxy.socket ?? opts.proxy.req?.socket,
|
|
182
|
+
proxyName: opts.proxy.name,
|
|
183
|
+
},
|
|
184
|
+
(obj, key, val) => {
|
|
185
|
+
if (key === 'content-length' && !expectsPayload) {
|
|
186
|
+
// https://tools.ietf.org/html/rfc7230#section-3.3.2
|
|
187
|
+
// A user agent SHOULD NOT send a Content-Length header field when
|
|
188
|
+
// the request message does not contain a payload body and the method
|
|
189
|
+
// semantics do not anticipate such a body.
|
|
190
|
+
// undici will error if provided an unexpected content-length: 0 header.
|
|
191
|
+
}
|
|
192
|
+
if (key === 'expect') {
|
|
193
|
+
// undici doesn't support expect header.
|
|
194
|
+
} else {
|
|
195
|
+
obj[key] = val
|
|
196
|
+
}
|
|
197
|
+
return obj
|
|
198
|
+
},
|
|
199
|
+
{},
|
|
200
|
+
)
|
|
201
|
+
|
|
202
|
+
return dispatch({ ...opts, headers }, new Handler(opts.proxy, { handler }))
|
|
203
|
+
}
|