@nxtedition/nxt-undici 5.1.6 → 5.1.8
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 +2 -0
- package/lib/interceptor/cache.js +4 -4
- package/lib/interceptor/dns.js +45 -2
- package/lib/interceptor/dns.test.js +9 -0
- package/lib/interceptor/log.js +1 -1
- package/lib/interceptor/lookup.js +29 -0
- package/lib/interceptor/proxy.js +1 -1
- package/lib/interceptor/redirect.js +3 -3
- package/lib/interceptor/response-error.js +1 -1
- package/lib/interceptor/response-retry.js +5 -5
- package/lib/interceptor/response-verify.js +1 -1
- package/lib/request.js +1 -1
- package/lib/utils.js +10 -0
- package/package.json +1 -1
package/lib/index.js
CHANGED
|
@@ -15,6 +15,7 @@ export const interceptors = {
|
|
|
15
15
|
cache: (await import('./interceptor/cache.js')).default,
|
|
16
16
|
requestId: (await import('./interceptor/request-id.js')).default,
|
|
17
17
|
dns: (await import('./interceptor/dns.js')).default,
|
|
18
|
+
lookup: (await import('./interceptor/lookup.js')).default,
|
|
18
19
|
}
|
|
19
20
|
|
|
20
21
|
export { parseHeaders } from './utils.js'
|
|
@@ -57,6 +58,7 @@ function wrapDispatch(dispatcher) {
|
|
|
57
58
|
interceptors.responseError(),
|
|
58
59
|
interceptors.requestBodyFactory(),
|
|
59
60
|
interceptors.dns(),
|
|
61
|
+
interceptors.lookup(),
|
|
60
62
|
interceptors.requestId(),
|
|
61
63
|
interceptors.responseRetry(),
|
|
62
64
|
interceptors.responseVerify(),
|
package/lib/interceptor/cache.js
CHANGED
|
@@ -21,7 +21,7 @@ class CacheHandler {
|
|
|
21
21
|
|
|
22
22
|
onHeaders(statusCode, rawHeaders, resume, statusMessage, headers = parseHeaders(rawHeaders)) {
|
|
23
23
|
if (statusCode !== 307) {
|
|
24
|
-
return this.#handler.onHeaders(statusCode, null, resume,
|
|
24
|
+
return this.#handler.onHeaders(statusCode, null, resume, null, headers)
|
|
25
25
|
}
|
|
26
26
|
|
|
27
27
|
// TODO (fix): Support vary header.
|
|
@@ -58,7 +58,7 @@ class CacheHandler {
|
|
|
58
58
|
}
|
|
59
59
|
}
|
|
60
60
|
|
|
61
|
-
return this.#handler.onHeaders(statusCode, null, resume,
|
|
61
|
+
return this.#handler.onHeaders(statusCode, null, resume, null, headers)
|
|
62
62
|
}
|
|
63
63
|
|
|
64
64
|
onData(chunk) {
|
|
@@ -167,7 +167,7 @@ export default () => (dispatch) => (opts, handler) => {
|
|
|
167
167
|
return dispatch(opts, new CacheHandler({ handler, store, key: makeKey(opts) }))
|
|
168
168
|
}
|
|
169
169
|
|
|
170
|
-
const { statusCode,
|
|
170
|
+
const { statusCode, headers, body } = entry
|
|
171
171
|
|
|
172
172
|
let aborted = false
|
|
173
173
|
const abort = () => {
|
|
@@ -181,7 +181,7 @@ export default () => (dispatch) => (opts, handler) => {
|
|
|
181
181
|
return true
|
|
182
182
|
}
|
|
183
183
|
|
|
184
|
-
handler.onHeaders(statusCode, null, resume,
|
|
184
|
+
handler.onHeaders(statusCode, null, resume, null, headers)
|
|
185
185
|
if (aborted) {
|
|
186
186
|
return true
|
|
187
187
|
}
|
package/lib/interceptor/dns.js
CHANGED
|
@@ -1,9 +1,43 @@
|
|
|
1
1
|
import net from 'node:net'
|
|
2
2
|
import { resolve4 } from 'node:dns/promises'
|
|
3
|
+
import { getFastNow } from '../utils.js'
|
|
3
4
|
|
|
4
5
|
export default () => (dispatch) => {
|
|
6
|
+
const active = new Map()
|
|
7
|
+
const cache = new Map()
|
|
8
|
+
|
|
9
|
+
async function _refresh(hostname, now) {
|
|
10
|
+
const records = await resolve4(hostname, { ttl: true })
|
|
11
|
+
const ret = records.map(({ address, ttl }) => ({ address, expires: now + 1e3 * ttl }))
|
|
12
|
+
|
|
13
|
+
cache.set(hostname, ret)
|
|
14
|
+
active.delete(hostname)
|
|
15
|
+
|
|
16
|
+
return ret
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
async function refresh(hostname, now) {
|
|
20
|
+
let promise = active.get(hostname)
|
|
21
|
+
if (!promise) {
|
|
22
|
+
promise = _refresh(hostname, now)
|
|
23
|
+
active.set(hostname, promise)
|
|
24
|
+
}
|
|
25
|
+
return promise
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
async function resolve(hostname) {
|
|
29
|
+
const now = getFastNow()
|
|
30
|
+
|
|
31
|
+
let records = cache.get(hostname)?.filter(({ expires }) => expires > now)
|
|
32
|
+
if (records == null || records.length === 0) {
|
|
33
|
+
records = await refresh(hostname, now)
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
return records.map(({ address }) => address)
|
|
37
|
+
}
|
|
38
|
+
|
|
5
39
|
return async (opts, handler) => {
|
|
6
|
-
if (!opts.dns) {
|
|
40
|
+
if (!opts || !opts.dns || !opts.origin) {
|
|
7
41
|
return dispatch(opts, handler)
|
|
8
42
|
}
|
|
9
43
|
|
|
@@ -13,8 +47,17 @@ export default () => (dispatch) => {
|
|
|
13
47
|
return dispatch(opts, handler)
|
|
14
48
|
}
|
|
15
49
|
|
|
50
|
+
const records = await resolve(origin.hostname)
|
|
51
|
+
|
|
52
|
+
if (records.length === 0) {
|
|
53
|
+
throw Object.assign(new Error('No DNS records found for the specified hostname.'), {
|
|
54
|
+
code: 'ENOTFOUND',
|
|
55
|
+
hostname: origin.hostname,
|
|
56
|
+
})
|
|
57
|
+
}
|
|
58
|
+
|
|
16
59
|
const host = origin.host
|
|
17
|
-
|
|
60
|
+
|
|
18
61
|
origin.hostname = records[Math.floor(Math.random() * records.length)]
|
|
19
62
|
|
|
20
63
|
return dispatch({ ...opts, origin, headers: { ...opts.headers, host } }, handler)
|
package/lib/interceptor/log.js
CHANGED
|
@@ -71,7 +71,7 @@ class Handler extends DecoratorHandler {
|
|
|
71
71
|
this.#statusCode = statusCode
|
|
72
72
|
this.#headers = headers
|
|
73
73
|
|
|
74
|
-
return this.#handler.onHeaders(statusCode, null, resume,
|
|
74
|
+
return this.#handler.onHeaders(statusCode, null, resume, null, headers)
|
|
75
75
|
}
|
|
76
76
|
|
|
77
77
|
onData(chunk) {
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
export default () => (dispatch) => (opts, handler) => {
|
|
2
|
+
const lookup = opts.lookup
|
|
3
|
+
|
|
4
|
+
if (!lookup) {
|
|
5
|
+
return dispatch(opts, handler)
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
const callback = (err, origin) => {
|
|
9
|
+
if (err) {
|
|
10
|
+
handler.onError(err)
|
|
11
|
+
} else {
|
|
12
|
+
dispatch({ ...opts, origin }, handler)
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
try {
|
|
17
|
+
const thenable = lookup(opts.origin, { signal: opts.signal }, callback)
|
|
18
|
+
if (typeof thenable?.then === 'function') {
|
|
19
|
+
thenable.then(
|
|
20
|
+
(val) => callback(null, val),
|
|
21
|
+
(err) => callback(err),
|
|
22
|
+
)
|
|
23
|
+
}
|
|
24
|
+
} catch (err) {
|
|
25
|
+
callback(err)
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
return true
|
|
29
|
+
}
|
package/lib/interceptor/proxy.js
CHANGED
|
@@ -47,11 +47,11 @@ class Handler extends DecoratorHandler {
|
|
|
47
47
|
return this.#handler.onUpgrade(statusCode, rawHeaders, socket, headers)
|
|
48
48
|
}
|
|
49
49
|
|
|
50
|
-
onHeaders(statusCode, rawHeaders, resume,
|
|
50
|
+
onHeaders(statusCode, rawHeaders, resume, statusMessage, headers = parseHeaders(rawHeaders)) {
|
|
51
51
|
if (redirectableStatusCodes.indexOf(statusCode) === -1) {
|
|
52
52
|
assert(!this.#headersSent)
|
|
53
53
|
this.#headersSent = true
|
|
54
|
-
return this.#handler.onHeaders(statusCode, null, resume,
|
|
54
|
+
return this.#handler.onHeaders(statusCode, null, resume, null, headers)
|
|
55
55
|
}
|
|
56
56
|
|
|
57
57
|
if (isDisturbed(this.#opts.body)) {
|
|
@@ -71,7 +71,7 @@ class Handler extends DecoratorHandler {
|
|
|
71
71
|
if (!this.#opts.follow(this.#location, this.#count, this.#opts)) {
|
|
72
72
|
assert(!this.#headersSent)
|
|
73
73
|
this.#headersSent = true
|
|
74
|
-
return this.#handler.onHeaders(statusCode, null, resume,
|
|
74
|
+
return this.#handler.onHeaders(statusCode, null, resume, null, headers)
|
|
75
75
|
}
|
|
76
76
|
} else {
|
|
77
77
|
if (this.#count >= this.#maxCount) {
|
|
@@ -36,7 +36,7 @@ class Handler extends DecoratorHandler {
|
|
|
36
36
|
this.#contentType = headers['content-type']
|
|
37
37
|
|
|
38
38
|
if (this.#statusCode < 400) {
|
|
39
|
-
return this.#handler.onHeaders(statusCode, null, resume,
|
|
39
|
+
return this.#handler.onHeaders(statusCode, null, resume, null, headers)
|
|
40
40
|
}
|
|
41
41
|
|
|
42
42
|
// TODO (fix): Check content length
|
|
@@ -77,18 +77,18 @@ class Handler extends DecoratorHandler {
|
|
|
77
77
|
assert(this.#headersSent === false)
|
|
78
78
|
|
|
79
79
|
if (headers.trailer) {
|
|
80
|
-
return this.#onHeaders(statusCode, null, resume,
|
|
80
|
+
return this.#onHeaders(statusCode, null, resume, null, headers)
|
|
81
81
|
}
|
|
82
82
|
|
|
83
83
|
const contentLength = headers['content-length'] ? Number(headers['content-length']) : null
|
|
84
84
|
if (contentLength != null && !Number.isFinite(contentLength)) {
|
|
85
|
-
return this.#onHeaders(statusCode, null, resume,
|
|
85
|
+
return this.#onHeaders(statusCode, null, resume, null, headers)
|
|
86
86
|
}
|
|
87
87
|
|
|
88
88
|
if (statusCode === 206) {
|
|
89
89
|
const range = parseRangeHeader(headers['content-range'])
|
|
90
90
|
if (!range) {
|
|
91
|
-
return this.#onHeaders(statusCode, null, resume,
|
|
91
|
+
return this.#onHeaders(statusCode, null, resume, null, headers)
|
|
92
92
|
}
|
|
93
93
|
|
|
94
94
|
const { start, size, end = size } = range
|
|
@@ -108,7 +108,7 @@ class Handler extends DecoratorHandler {
|
|
|
108
108
|
this.#end = contentLength
|
|
109
109
|
this.#etag = headers.etag
|
|
110
110
|
} else {
|
|
111
|
-
return this.#onHeaders(statusCode, null, resume,
|
|
111
|
+
return this.#onHeaders(statusCode, null, resume, null, headers)
|
|
112
112
|
}
|
|
113
113
|
|
|
114
114
|
// Weak etags are not useful for comparison nor cache
|
|
@@ -121,7 +121,7 @@ class Handler extends DecoratorHandler {
|
|
|
121
121
|
assert(Number.isFinite(this.#pos))
|
|
122
122
|
assert(this.#end == null || Number.isFinite(this.#end))
|
|
123
123
|
|
|
124
|
-
return this.#onHeaders(statusCode, null, resume,
|
|
124
|
+
return this.#onHeaders(statusCode, null, resume, null, headers)
|
|
125
125
|
} else if (statusCode === 206 || (this.#pos === 0 && statusCode === 200)) {
|
|
126
126
|
assert(this.#etag != null || !this.#pos)
|
|
127
127
|
|
|
@@ -39,7 +39,7 @@ class Handler extends DecoratorHandler {
|
|
|
39
39
|
this.#contentLength = this.#verifyOpts.hash ? headers['content-length'] : null
|
|
40
40
|
this.#hasher = this.#contentMD5 != null ? crypto.createHash('md5') : null
|
|
41
41
|
|
|
42
|
-
return this.#handler.onHeaders(statusCode, null, resume,
|
|
42
|
+
return this.#handler.onHeaders(statusCode, null, resume, null, headers)
|
|
43
43
|
}
|
|
44
44
|
|
|
45
45
|
onData(chunk) {
|
package/lib/request.js
CHANGED
|
@@ -70,7 +70,7 @@ export class RequestHandler {
|
|
|
70
70
|
this.abort = abort
|
|
71
71
|
}
|
|
72
72
|
|
|
73
|
-
onHeaders(statusCode, rawHeaders, resume, headers = parseHeaders(rawHeaders)) {
|
|
73
|
+
onHeaders(statusCode, rawHeaders, resume, statusMessage, headers = parseHeaders(rawHeaders)) {
|
|
74
74
|
const { resolve, abort, highWaterMark } = this
|
|
75
75
|
|
|
76
76
|
if (statusCode < 200) {
|
package/lib/utils.js
CHANGED
|
@@ -3,6 +3,16 @@ import cacheControlParser from 'cache-control-parser'
|
|
|
3
3
|
import stream from 'node:stream'
|
|
4
4
|
import { util } from 'undici'
|
|
5
5
|
|
|
6
|
+
let fastNow = Date.now()
|
|
7
|
+
|
|
8
|
+
setInterval(() => {
|
|
9
|
+
fastNow = Date.now()
|
|
10
|
+
}, 1e3).unref()
|
|
11
|
+
|
|
12
|
+
export function getFastNow() {
|
|
13
|
+
return fastNow
|
|
14
|
+
}
|
|
15
|
+
|
|
6
16
|
export function parseCacheControl(str) {
|
|
7
17
|
return str ? cacheControlParser.parse(str) : null
|
|
8
18
|
}
|