@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 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(),
@@ -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, statusMessage, headers)
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, statusMessage, headers)
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, statusMessage, headers, body } = entry
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, statusMessage, headers)
184
+ handler.onHeaders(statusCode, null, resume, null, headers)
185
185
  if (aborted) {
186
186
  return true
187
187
  }
@@ -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
- const records = await resolve4(origin.hostname)
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)
@@ -0,0 +1,9 @@
1
+ import { test } from 'tap'
2
+ import { request } from '../index.js'
3
+
4
+ test('retry destroy pre response', async (t) => {
5
+ const { body, statusCode } = await request(`http://google.com`)
6
+ await body.dump()
7
+ t.equal(statusCode, 200)
8
+ t.end()
9
+ })
@@ -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, statusMessage, headers)
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
+ }
@@ -50,7 +50,7 @@ class Handler extends DecoratorHandler {
50
50
  [],
51
51
  ),
52
52
  resume,
53
- statusMessage,
53
+ null,
54
54
  )
55
55
  }
56
56
  }
@@ -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, statusText, headers = parseHeaders(rawHeaders)) {
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, statusText, headers)
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, statusText, headers)
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, statusMessage, headers)
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, statusMessage, headers)
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, statusMessage, headers)
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, statusMessage, headers)
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, statusMessage, headers)
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, statusMessage, headers)
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, statusMessage, headers)
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
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nxtedition/nxt-undici",
3
- "version": "5.1.6",
3
+ "version": "5.1.8",
4
4
  "license": "MIT",
5
5
  "author": "Robert Nagy <robert.nagy@boffins.se>",
6
6
  "main": "lib/index.js",