@nxtedition/nxt-undici 6.2.11 → 6.2.13
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/interceptor/cache.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import undici from '@nxtedition/undici'
|
|
2
2
|
import { DecoratorHandler, parseCacheControl, parseContentRange } from '../utils.js'
|
|
3
|
-
import { SqliteCacheStore
|
|
3
|
+
import { SqliteCacheStore } from '../sqlite-cache-store.js'
|
|
4
4
|
|
|
5
5
|
const DEFAULT_STORE = new SqliteCacheStore({ location: ':memory:' })
|
|
6
6
|
const DEFAULT_MAX_ENTRY_SIZE = 128 * 1024
|
|
@@ -13,8 +13,6 @@ class CacheHandler extends DecoratorHandler {
|
|
|
13
13
|
#maxEntrySize
|
|
14
14
|
|
|
15
15
|
constructor(key, { store, handler, maxEntrySize }) {
|
|
16
|
-
assertCacheKey(key)
|
|
17
|
-
|
|
18
16
|
super(handler)
|
|
19
17
|
|
|
20
18
|
this.#key = key
|
|
@@ -211,7 +209,7 @@ export default () => (dispatch) => (opts, handler) => {
|
|
|
211
209
|
)
|
|
212
210
|
}
|
|
213
211
|
|
|
214
|
-
const { statusCode, headers, body } = entry ?? { statusCode: 504
|
|
212
|
+
const { statusCode, headers, trailers, body } = entry ?? { statusCode: 504 }
|
|
215
213
|
|
|
216
214
|
let aborted = false
|
|
217
215
|
const abort = (reason) => {
|
|
@@ -230,7 +228,7 @@ export default () => (dispatch) => (opts, handler) => {
|
|
|
230
228
|
return
|
|
231
229
|
}
|
|
232
230
|
|
|
233
|
-
handler.onHeaders(statusCode, headers, NOOP)
|
|
231
|
+
handler.onHeaders(statusCode, headers ?? {}, NOOP)
|
|
234
232
|
if (aborted) {
|
|
235
233
|
return
|
|
236
234
|
}
|
|
@@ -242,7 +240,7 @@ export default () => (dispatch) => (opts, handler) => {
|
|
|
242
240
|
}
|
|
243
241
|
}
|
|
244
242
|
|
|
245
|
-
handler.onComplete({})
|
|
243
|
+
handler.onComplete(trailers ?? {})
|
|
246
244
|
} catch (err) {
|
|
247
245
|
abort(err)
|
|
248
246
|
}
|
package/lib/interceptor/dns.js
CHANGED
|
@@ -35,15 +35,18 @@ export default () => (dispatch) => {
|
|
|
35
35
|
function resolve(hostname, { logger }) {
|
|
36
36
|
let promise = promises.get(hostname)
|
|
37
37
|
if (!promise) {
|
|
38
|
-
|
|
39
|
-
|
|
38
|
+
promise = new Promise((resolve) => {
|
|
39
|
+
logger?.debug({ dns: { hostname } }, 'lookup started')
|
|
40
40
|
dns.resolve4(hostname, { ttl: true }, (err, records) => {
|
|
41
41
|
promises.delete(hostname)
|
|
42
42
|
|
|
43
43
|
if (err) {
|
|
44
44
|
logger?.error({ err, dns: { hostname } }, 'lookup failed')
|
|
45
|
+
|
|
45
46
|
resolve([err, null])
|
|
46
47
|
} else {
|
|
48
|
+
logger?.debug({ dns: { hostname, records } }, 'lookup completed')
|
|
49
|
+
|
|
47
50
|
const now = getFastNow()
|
|
48
51
|
const val = records.map(({ address, ttl }) => ({
|
|
49
52
|
address,
|
|
@@ -53,14 +56,12 @@ export default () => (dispatch) => {
|
|
|
53
56
|
counter: 0,
|
|
54
57
|
}))
|
|
55
58
|
|
|
56
|
-
logger?.debug({ err, dns: { hostname, records } }, 'lookup completed')
|
|
57
|
-
|
|
58
59
|
cache.set(hostname, val)
|
|
59
60
|
|
|
60
61
|
resolve([null, val])
|
|
61
62
|
}
|
|
62
|
-
})
|
|
63
|
-
)
|
|
63
|
+
})
|
|
64
|
+
})
|
|
64
65
|
promises.set(hostname, promise)
|
|
65
66
|
}
|
|
66
67
|
return promise
|
|
@@ -1,11 +1,10 @@
|
|
|
1
|
-
import
|
|
2
|
-
import { DecoratorHandler } from '../utils.js'
|
|
1
|
+
import { DecoratorHandler, decorateError } from '../utils.js'
|
|
3
2
|
|
|
4
3
|
class Handler extends DecoratorHandler {
|
|
5
4
|
#statusCode = 0
|
|
6
|
-
#contentType
|
|
7
5
|
#decoder
|
|
8
6
|
#headers
|
|
7
|
+
#trailers
|
|
9
8
|
#body = ''
|
|
10
9
|
#opts
|
|
11
10
|
|
|
@@ -15,13 +14,8 @@ class Handler extends DecoratorHandler {
|
|
|
15
14
|
this.#opts = opts
|
|
16
15
|
}
|
|
17
16
|
|
|
18
|
-
#checkContentType(contentType) {
|
|
19
|
-
return (this.#contentType ?? '').indexOf(contentType) === 0
|
|
20
|
-
}
|
|
21
|
-
|
|
22
17
|
onConnect(abort) {
|
|
23
18
|
this.#statusCode = 0
|
|
24
|
-
this.#contentType = null
|
|
25
19
|
this.#decoder = null
|
|
26
20
|
this.#headers = null
|
|
27
21
|
this.#body = ''
|
|
@@ -32,14 +26,17 @@ class Handler extends DecoratorHandler {
|
|
|
32
26
|
onHeaders(statusCode, headers, resume) {
|
|
33
27
|
this.#statusCode = statusCode
|
|
34
28
|
this.#headers = headers
|
|
35
|
-
this.#contentType = headers['content-type']
|
|
36
29
|
|
|
37
30
|
if (this.#statusCode < 400) {
|
|
38
31
|
return super.onHeaders(statusCode, headers, resume)
|
|
39
32
|
}
|
|
40
33
|
|
|
41
|
-
if (
|
|
34
|
+
if (
|
|
35
|
+
this.#headers['content-type']?.startsWith('application/json') ||
|
|
36
|
+
this.#headers['content-type']?.startsWith('text/plain')
|
|
37
|
+
) {
|
|
42
38
|
this.#decoder = new TextDecoder('utf-8')
|
|
39
|
+
this.#body = ''
|
|
43
40
|
}
|
|
44
41
|
}
|
|
45
42
|
|
|
@@ -52,72 +49,33 @@ class Handler extends DecoratorHandler {
|
|
|
52
49
|
}
|
|
53
50
|
|
|
54
51
|
onComplete(trailers) {
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
try {
|
|
60
|
-
this.#body = JSON.parse(this.#body)
|
|
61
|
-
} catch {
|
|
62
|
-
// Do nothing...
|
|
63
|
-
}
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
let err
|
|
67
|
-
const stackTraceLimit = Error.stackTraceLimit
|
|
68
|
-
Error.stackTraceLimit = 0
|
|
69
|
-
try {
|
|
70
|
-
err = createHttpError(this.#statusCode)
|
|
71
|
-
} finally {
|
|
72
|
-
Error.stackTraceLimit = stackTraceLimit
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
super.onError(this.#decorateError(err))
|
|
76
|
-
} else {
|
|
77
|
-
super.onComplete(trailers)
|
|
52
|
+
this.#trailers = trailers
|
|
53
|
+
|
|
54
|
+
if (this.#statusCode < 400) {
|
|
55
|
+
return super.onComplete(trailers)
|
|
78
56
|
}
|
|
79
|
-
}
|
|
80
57
|
|
|
81
|
-
|
|
82
|
-
|
|
58
|
+
this.#body += this.#decoder?.decode(undefined, { stream: false }) ?? ''
|
|
59
|
+
|
|
60
|
+
super.onError(
|
|
61
|
+
decorateError(null, this.#opts, {
|
|
62
|
+
statusCode: this.#statusCode,
|
|
63
|
+
headers: this.#headers,
|
|
64
|
+
trailers: this.#trailers,
|
|
65
|
+
body: this.#body,
|
|
66
|
+
}),
|
|
67
|
+
)
|
|
83
68
|
}
|
|
84
69
|
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
err
|
|
88
|
-
|
|
89
|
-
err.req = {
|
|
90
|
-
method: this.#opts?.method,
|
|
91
|
-
headers: this.#opts?.headers,
|
|
92
|
-
body:
|
|
93
|
-
// TODO (fix): JSON.stringify POJO
|
|
94
|
-
typeof this.#opts?.body !== 'string' || this.#opts.body.length > 1024
|
|
95
|
-
? undefined
|
|
96
|
-
: this.#opts.body,
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
err.res = {
|
|
70
|
+
onError(err) {
|
|
71
|
+
super.onError(
|
|
72
|
+
decorateError(err, this.#opts, {
|
|
73
|
+
statusCode: this.#statusCode,
|
|
100
74
|
headers: this.#headers,
|
|
101
|
-
|
|
102
|
-
body:
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
if (this.#body) {
|
|
106
|
-
if (this.#body.reason != null) {
|
|
107
|
-
err.reason ??= this.#body.reason
|
|
108
|
-
}
|
|
109
|
-
if (this.#body.code != null) {
|
|
110
|
-
err.code ??= this.#body.code
|
|
111
|
-
}
|
|
112
|
-
if (this.#body.error != null) {
|
|
113
|
-
err.error ??= this.#body.error
|
|
114
|
-
}
|
|
115
|
-
}
|
|
116
|
-
|
|
117
|
-
return err
|
|
118
|
-
} catch (er) {
|
|
119
|
-
return new AggregateError([er, err])
|
|
120
|
-
}
|
|
75
|
+
trailers: this.#trailers,
|
|
76
|
+
body: null,
|
|
77
|
+
}),
|
|
78
|
+
)
|
|
121
79
|
}
|
|
122
80
|
}
|
|
123
81
|
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import assert from 'node:assert'
|
|
2
2
|
import tp from 'node:timers/promises'
|
|
3
|
-
import { DecoratorHandler, isDisturbed, parseRangeHeader } from '../utils.js'
|
|
4
|
-
import createHttpError from 'http-errors'
|
|
3
|
+
import { DecoratorHandler, isDisturbed, decorateError, parseRangeHeader } from '../utils.js'
|
|
5
4
|
|
|
6
5
|
function noop() {}
|
|
7
6
|
|
|
@@ -15,8 +14,10 @@ class Handler extends DecoratorHandler {
|
|
|
15
14
|
#errorSent = false
|
|
16
15
|
|
|
17
16
|
#statusCode = 0
|
|
18
|
-
#headers
|
|
19
|
-
#trailers
|
|
17
|
+
#headers
|
|
18
|
+
#trailers
|
|
19
|
+
#body = ''
|
|
20
|
+
#decoder
|
|
20
21
|
|
|
21
22
|
#abort
|
|
22
23
|
#aborted = false
|
|
@@ -48,6 +49,8 @@ class Handler extends DecoratorHandler {
|
|
|
48
49
|
this.#statusCode = 0
|
|
49
50
|
this.#headers = null
|
|
50
51
|
this.#trailers = null
|
|
52
|
+
this.#body = ''
|
|
53
|
+
this.#decoder = null
|
|
51
54
|
|
|
52
55
|
if (!this.#headersSent) {
|
|
53
56
|
this.#pos = null
|
|
@@ -117,6 +120,13 @@ class Handler extends DecoratorHandler {
|
|
|
117
120
|
this.#end = contentLength
|
|
118
121
|
this.#etag = headers.etag
|
|
119
122
|
} else if (statusCode >= 400) {
|
|
123
|
+
if (
|
|
124
|
+
this.#headers['content-type']?.startsWith('application/json') ||
|
|
125
|
+
this.#headers['content-type']?.startsWith('text/plain')
|
|
126
|
+
) {
|
|
127
|
+
this.#decoder = new TextDecoder('utf-8')
|
|
128
|
+
this.#body = ''
|
|
129
|
+
}
|
|
120
130
|
return true
|
|
121
131
|
} else {
|
|
122
132
|
return this.#onHeaders(statusCode, headers, resume)
|
|
@@ -161,29 +171,30 @@ class Handler extends DecoratorHandler {
|
|
|
161
171
|
}
|
|
162
172
|
|
|
163
173
|
onData(chunk) {
|
|
164
|
-
if (this.#statusCode >= 400) {
|
|
165
|
-
// TODO (fix): Limit the amount of data we read?
|
|
166
|
-
return true
|
|
167
|
-
}
|
|
168
|
-
|
|
169
174
|
if (this.#pos != null) {
|
|
170
175
|
this.#pos += chunk.byteLength
|
|
171
176
|
}
|
|
172
|
-
return super.onData(chunk)
|
|
173
|
-
}
|
|
174
177
|
|
|
175
|
-
|
|
176
|
-
|
|
178
|
+
if (this.#statusCode < 400) {
|
|
179
|
+
return super.onData(chunk)
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
this.#body += this.#decoder?.decode(chunk, { stream: true }) ?? ''
|
|
177
183
|
}
|
|
178
184
|
|
|
179
185
|
onComplete(trailers) {
|
|
180
186
|
this.#trailers = trailers
|
|
181
187
|
|
|
182
|
-
if (this.#statusCode
|
|
183
|
-
|
|
184
|
-
} else {
|
|
185
|
-
super.onComplete(trailers)
|
|
188
|
+
if (this.#statusCode < 400) {
|
|
189
|
+
return super.onComplete(trailers)
|
|
186
190
|
}
|
|
191
|
+
|
|
192
|
+
this.#body += this.#decoder?.decode(undefined, { stream: false }) ?? ''
|
|
193
|
+
this.#maybeRetry(null)
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
onError(err) {
|
|
197
|
+
this.#maybeRetry(err)
|
|
187
198
|
}
|
|
188
199
|
|
|
189
200
|
#maybeError(err) {
|
|
@@ -202,26 +213,29 @@ class Handler extends DecoratorHandler {
|
|
|
202
213
|
}
|
|
203
214
|
}
|
|
204
215
|
|
|
205
|
-
#maybeRetry(err
|
|
216
|
+
#maybeRetry(err) {
|
|
206
217
|
if (this.#aborted || isDisturbed(this.#opts.body) || (this.#pos && !this.#etag)) {
|
|
207
218
|
this.#maybeError(err)
|
|
208
219
|
return
|
|
209
220
|
}
|
|
210
221
|
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
}
|
|
220
|
-
}
|
|
222
|
+
this.#error =
|
|
223
|
+
err ??
|
|
224
|
+
decorateError(null, this.#opts, {
|
|
225
|
+
statusCode: this.#statusCode,
|
|
226
|
+
headers: this.#headers,
|
|
227
|
+
trailers: this.#trailers,
|
|
228
|
+
body: this.#body,
|
|
229
|
+
})
|
|
221
230
|
|
|
222
231
|
let retryPromise
|
|
223
232
|
try {
|
|
224
|
-
retryPromise =
|
|
233
|
+
retryPromise =
|
|
234
|
+
typeof this.#opts?.retry === 'function'
|
|
235
|
+
? this.#opts?.retry(this.#error, this.#retryCount, this.#opts, () =>
|
|
236
|
+
retryFn(this.#error, this.#retryCount, this.#opts),
|
|
237
|
+
)
|
|
238
|
+
: retryFn(this.#error, this.#retryCount, this.#opts)
|
|
225
239
|
} catch (err) {
|
|
226
240
|
retryPromise = Promise.reject(err)
|
|
227
241
|
}
|
|
@@ -231,8 +245,6 @@ class Handler extends DecoratorHandler {
|
|
|
231
245
|
return
|
|
232
246
|
}
|
|
233
247
|
|
|
234
|
-
this.#error = err
|
|
235
|
-
|
|
236
248
|
retryPromise
|
|
237
249
|
.then((opts) => {
|
|
238
250
|
if (this.#aborted) {
|
|
@@ -301,10 +313,6 @@ async function retryFn(err, retryCount, opts) {
|
|
|
301
313
|
throw err
|
|
302
314
|
}
|
|
303
315
|
|
|
304
|
-
if (typeof retryOpts === 'function') {
|
|
305
|
-
return retryOpts(err, retryCount, opts, () => retryFn(err, retryCount, opts))
|
|
306
|
-
}
|
|
307
|
-
|
|
308
316
|
if (typeof retryOpts === 'number') {
|
|
309
317
|
retryOpts = { count: retryOpts }
|
|
310
318
|
}
|
|
@@ -338,6 +346,7 @@ async function retryFn(err, retryCount, opts) {
|
|
|
338
346
|
'EHOSTDOWN',
|
|
339
347
|
'EHOSTUNREACH',
|
|
340
348
|
'EPIPE',
|
|
349
|
+
'ENODATA',
|
|
341
350
|
'UND_ERR_CONNECT_TIMEOUT',
|
|
342
351
|
].includes(err.code)
|
|
343
352
|
) {
|
|
@@ -410,7 +410,7 @@ function makeResult(value) {
|
|
|
410
410
|
/**
|
|
411
411
|
* @param {any} key
|
|
412
412
|
*/
|
|
413
|
-
|
|
413
|
+
function assertCacheKey(key) {
|
|
414
414
|
if (typeof key !== 'object') {
|
|
415
415
|
throw new TypeError(`expected key to be object, got ${typeof key}`)
|
|
416
416
|
}
|
|
@@ -429,7 +429,7 @@ export function assertCacheKey(key) {
|
|
|
429
429
|
/**
|
|
430
430
|
* @param {any} value
|
|
431
431
|
*/
|
|
432
|
-
|
|
432
|
+
function assertCacheValue(value) {
|
|
433
433
|
if (typeof value !== 'object') {
|
|
434
434
|
throw new TypeError(`expected value to be object, got ${typeof value}`)
|
|
435
435
|
}
|
package/lib/utils.js
CHANGED
|
@@ -2,6 +2,7 @@ import cacheControlParser from 'cache-control-parser'
|
|
|
2
2
|
import stream from 'node:stream'
|
|
3
3
|
import assert from 'node:assert'
|
|
4
4
|
import { util } from '@nxtedition/undici'
|
|
5
|
+
import createHttpError from 'http-errors'
|
|
5
6
|
|
|
6
7
|
let fastNow = Date.now()
|
|
7
8
|
|
|
@@ -351,9 +352,69 @@ export function parseHeaders(headers, obj) {
|
|
|
351
352
|
}
|
|
352
353
|
|
|
353
354
|
// See https://github.com/nodejs/node/pull/46528
|
|
354
|
-
if ('content-length' in obj && 'content-disposition' in obj) {
|
|
355
|
+
if (obj != null && 'content-length' in obj && 'content-disposition' in obj) {
|
|
355
356
|
obj['content-disposition'] = Buffer.from(obj['content-disposition']).toString('latin1')
|
|
356
357
|
}
|
|
357
358
|
|
|
358
359
|
return obj
|
|
359
360
|
}
|
|
361
|
+
|
|
362
|
+
export function decorateError(err, opts, { statusCode, headers, trailers, body }) {
|
|
363
|
+
try {
|
|
364
|
+
if (err == null) {
|
|
365
|
+
const stackTraceLimit = Error.stackTraceLimit
|
|
366
|
+
Error.stackTraceLimit = 0
|
|
367
|
+
try {
|
|
368
|
+
err = createHttpError(statusCode)
|
|
369
|
+
} finally {
|
|
370
|
+
Error.stackTraceLimit = stackTraceLimit
|
|
371
|
+
}
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
if (statusCode != null) {
|
|
375
|
+
err.statusCode = statusCode
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
err.url ??= opts.origin ? new URL(opts.path, opts.origin).href : null
|
|
379
|
+
|
|
380
|
+
err.req = {
|
|
381
|
+
method: opts?.method,
|
|
382
|
+
headers: opts?.headers,
|
|
383
|
+
body:
|
|
384
|
+
// TODO (fix): JSON.stringify POJO
|
|
385
|
+
typeof opts?.body !== 'string' || opts.body.length > 1024 ? undefined : opts.body,
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
if (headers?.['content-type']?.startsWith('application/json') && typeof body === 'string') {
|
|
389
|
+
try {
|
|
390
|
+
body = JSON.parse(body)
|
|
391
|
+
} catch {
|
|
392
|
+
// Do nothing...
|
|
393
|
+
}
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
err.res = {
|
|
397
|
+
headers,
|
|
398
|
+
trailers,
|
|
399
|
+
// TODO (fix): JSON.stringify POJO
|
|
400
|
+
body: typeof body !== 'string' || body.length < 1024 ? undefined : body,
|
|
401
|
+
statusCode,
|
|
402
|
+
}
|
|
403
|
+
|
|
404
|
+
if (body) {
|
|
405
|
+
if (body.reason != null) {
|
|
406
|
+
err.reason ??= body.reason
|
|
407
|
+
}
|
|
408
|
+
if (body.code != null) {
|
|
409
|
+
err.code ??= body.code
|
|
410
|
+
}
|
|
411
|
+
if (body.error != null) {
|
|
412
|
+
err.error ??= body.error
|
|
413
|
+
}
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
return err
|
|
417
|
+
} catch (er) {
|
|
418
|
+
return new AggregateError([er, err])
|
|
419
|
+
}
|
|
420
|
+
}
|