@nxtedition/nxt-undici 3.2.2 → 3.3.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/lib/index.js +29 -23
- 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,6 +20,31 @@ export const interceptors = {
|
|
|
20
20
|
export { parseHeaders } from './utils.js'
|
|
21
21
|
export { Client, Pool, Agent, getGlobalDispatcher, setGlobalDispatcher } from '@nxtedition/undici'
|
|
22
22
|
|
|
23
|
+
function wrapdDispatcher(dispatcher) {
|
|
24
|
+
let wrappedDispatcher = dispatcherCache.get(dispatcher)
|
|
25
|
+
if (wrappedDispatcher == null) {
|
|
26
|
+
wrappedDispatcher = dispatcher.compose(
|
|
27
|
+
interceptors.responseError(),
|
|
28
|
+
interceptors.requestBodyFactory(),
|
|
29
|
+
interceptors.log(),
|
|
30
|
+
interceptors.dns(),
|
|
31
|
+
interceptors.lookup(),
|
|
32
|
+
interceptors.requestId(),
|
|
33
|
+
interceptors.responseRetry(),
|
|
34
|
+
interceptors.responseVerify(),
|
|
35
|
+
interceptors.redirect(),
|
|
36
|
+
interceptors.cache(),
|
|
37
|
+
interceptors.proxy(),
|
|
38
|
+
)
|
|
39
|
+
dispatcherCache.set(dispatcher, wrappedDispatcher)
|
|
40
|
+
}
|
|
41
|
+
return wrappedDispatcher
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export function dispatch(dispatcher, opts, handler) {
|
|
45
|
+
return wrapdDispatcher(opts.dispatcher ?? undici.getGlobalDispatcher()).dispatch(opts, handler)
|
|
46
|
+
}
|
|
47
|
+
|
|
23
48
|
export async function request(url, opts) {
|
|
24
49
|
// TODO (fix): More argument validation...
|
|
25
50
|
|
|
@@ -55,30 +80,11 @@ export async function request(url, opts) {
|
|
|
55
80
|
headers['user-agent'] = userAgent
|
|
56
81
|
}
|
|
57
82
|
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
let dispatcher = dispatcherCache.get(baseDispatcher)
|
|
61
|
-
if (dispatcher == null) {
|
|
62
|
-
dispatcher = baseDispatcher.compose(
|
|
63
|
-
interceptors.responseError(),
|
|
64
|
-
interceptors.requestBodyFactory(),
|
|
65
|
-
interceptors.log(),
|
|
66
|
-
interceptors.dns(),
|
|
67
|
-
interceptors.lookup(),
|
|
68
|
-
interceptors.requestId(),
|
|
69
|
-
interceptors.responseRetry(),
|
|
70
|
-
interceptors.responseVerify(),
|
|
71
|
-
interceptors.redirect(),
|
|
72
|
-
interceptors.cache(),
|
|
73
|
-
interceptors.proxy(),
|
|
74
|
-
)
|
|
75
|
-
dispatcherCache.set(baseDispatcher, dispatcher)
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
return await undici.request(url, {
|
|
83
|
+
return await wrapdDispatcher(opts.dispatcher ?? undici.getGlobalDispatcher()).request({
|
|
79
84
|
id: opts.id,
|
|
85
|
+
origin: url.origin,
|
|
86
|
+
path: url.path ?? (url.search ? `${url.pathname}${url.search}` : url.pathname),
|
|
80
87
|
method,
|
|
81
|
-
dispatcher,
|
|
82
88
|
body: opts.body,
|
|
83
89
|
query: opts.query,
|
|
84
90
|
headers,
|
|
@@ -97,6 +103,6 @@ export async function request(url, opts) {
|
|
|
97
103
|
verify: opts.verify ?? true,
|
|
98
104
|
logger: opts.logger ?? null,
|
|
99
105
|
lookup: opts.lookup ?? null,
|
|
100
|
-
dns: opts.dns ??
|
|
106
|
+
dns: opts.dns ?? true,
|
|
101
107
|
})
|
|
102
108
|
}
|
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
|
+
}
|