@nxtedition/nxt-undici 5.1.7 → 5.2.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/cache/sqlite-cache-store.js +439 -0
- package/lib/cache/util.js +51 -0
- package/lib/index.js +10 -7
- package/lib/interceptor/cache.js +100 -119
- package/lib/interceptor/dns.js +30 -4
- package/lib/interceptor/dns.test.js +9 -0
- package/lib/interceptor/log.js +6 -8
- package/lib/interceptor/proxy.js +3 -5
- package/lib/interceptor/redirect.js +10 -12
- package/lib/interceptor/response-error.js +19 -27
- package/lib/interceptor/response-retry.js +16 -12
- package/lib/interceptor/response-verify.js +8 -25
- package/lib/request.js +2 -2
- package/lib/utils.js +11 -1
- package/package.json +5 -6
- package/lib/readable.js +0 -523
package/lib/interceptor/cache.js
CHANGED
|
@@ -1,134 +1,116 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { parseHeaders, parseCacheControl } from '../utils.js'
|
|
1
|
+
import { SqliteCacheStore } from '../cache/sqlite-cache-store.js'
|
|
2
|
+
import { DecoratorHandler, parseHeaders, parseCacheControl } from '../utils.js'
|
|
3
3
|
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
#key
|
|
4
|
+
const DEFAULT_STORE = new SqliteCacheStore({ location: ':memory:' })
|
|
5
|
+
|
|
6
|
+
class CacheHandler extends DecoratorHandler {
|
|
8
7
|
#value
|
|
8
|
+
#opts
|
|
9
|
+
#store
|
|
9
10
|
|
|
10
|
-
constructor({
|
|
11
|
-
|
|
12
|
-
|
|
11
|
+
constructor(opts, { store, handler }) {
|
|
12
|
+
super(handler)
|
|
13
|
+
|
|
14
|
+
this.#opts = opts
|
|
13
15
|
this.#store = store
|
|
14
16
|
}
|
|
15
17
|
|
|
16
18
|
onConnect(abort) {
|
|
17
19
|
this.#value = null
|
|
18
20
|
|
|
19
|
-
|
|
21
|
+
super.onConnect(abort)
|
|
20
22
|
}
|
|
21
23
|
|
|
22
24
|
onHeaders(statusCode, rawHeaders, resume, statusMessage, headers = parseHeaders(rawHeaders)) {
|
|
23
25
|
if (statusCode !== 307) {
|
|
24
|
-
|
|
26
|
+
// Only cache redirects...
|
|
27
|
+
return super.onHeaders(statusCode, null, resume, null, headers)
|
|
25
28
|
}
|
|
26
29
|
|
|
27
|
-
|
|
28
|
-
|
|
30
|
+
if (headers.vary === '*') {
|
|
31
|
+
// Not cacheble...
|
|
32
|
+
return super.onHeaders(statusCode, null, resume, null, headers)
|
|
33
|
+
}
|
|
29
34
|
|
|
35
|
+
const cacheControl = parseCacheControl(headers['cache-control'])
|
|
30
36
|
const contentLength = headers['content-length'] ? Number(headers['content-length']) : Infinity
|
|
31
|
-
|
|
37
|
+
|
|
38
|
+
if (contentLength) {
|
|
39
|
+
// We don't support caching responses with body...
|
|
40
|
+
return super.onHeaders(statusCode, null, resume, null, headers)
|
|
41
|
+
}
|
|
32
42
|
|
|
33
43
|
if (
|
|
34
|
-
|
|
35
|
-
cacheControl
|
|
36
|
-
cacheControl.
|
|
37
|
-
|
|
38
|
-
!cacheControl['no-store'] &&
|
|
44
|
+
!cacheControl ||
|
|
45
|
+
!cacheControl.public ||
|
|
46
|
+
cacheControl.private ||
|
|
47
|
+
cacheControl['no-store'] ||
|
|
39
48
|
// TODO (fix): Support all cache control directives...
|
|
40
|
-
//
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
49
|
+
// cacheControl['no-transform'] ||
|
|
50
|
+
cacheControl['no-cache'] ||
|
|
51
|
+
cacheControl['must-understand'] ||
|
|
52
|
+
cacheControl['must-revalidate'] ||
|
|
53
|
+
cacheControl['proxy-revalidate']
|
|
45
54
|
) {
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
if (ttl > 0) {
|
|
50
|
-
this.#value = {
|
|
51
|
-
statusCode,
|
|
52
|
-
statusMessage,
|
|
53
|
-
headers,
|
|
54
|
-
body: [],
|
|
55
|
-
size: 256, // TODO (fix): Measure headers size...
|
|
56
|
-
ttl: ttl * 1e3,
|
|
57
|
-
}
|
|
58
|
-
}
|
|
55
|
+
// Not cacheble...
|
|
56
|
+
return super.onHeaders(statusCode, null, resume, null, headers)
|
|
59
57
|
}
|
|
60
58
|
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
this.#value.data.body.push(chunk)
|
|
59
|
+
const vary = {}
|
|
60
|
+
if (headers.vary) {
|
|
61
|
+
for (const key of [headers.vary]
|
|
62
|
+
.flat()
|
|
63
|
+
.flatMap((vary) => vary.split(',').map((key) => key.trim().toLowerCase()))) {
|
|
64
|
+
const val = this.#opts.headers?.[key]
|
|
65
|
+
if (!val) {
|
|
66
|
+
// Expect vary headers to be present...
|
|
67
|
+
return super.onHeaders(statusCode, null, resume, null, headers)
|
|
68
|
+
}
|
|
69
|
+
vary[key] = val
|
|
73
70
|
}
|
|
71
|
+
|
|
72
|
+
// Unexpected vary header type...
|
|
73
|
+
return super.onHeaders(statusCode, null, resume, null, headers)
|
|
74
74
|
}
|
|
75
|
-
return this.#handler.onData(chunk)
|
|
76
|
-
}
|
|
77
75
|
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
statusCode: this.#value.statusCode,
|
|
84
|
-
statusMessage: this.#value.statusMessage,
|
|
85
|
-
headers: this.#value.headers,
|
|
86
|
-
body: Buffer.concat(this.#value.body),
|
|
87
|
-
},
|
|
88
|
-
{ ttl: this.#value.ttl, size: this.#value.size },
|
|
89
|
-
)
|
|
76
|
+
const ttl = cacheControl.immutable
|
|
77
|
+
? 31556952
|
|
78
|
+
: Number(cacheControl['s-max-age'] ?? cacheControl['max-age'])
|
|
79
|
+
if (!ttl || !Number.isFinite(ttl) || ttl <= 0) {
|
|
80
|
+
return super.onHeaders(statusCode, null, resume, null, headers)
|
|
90
81
|
}
|
|
91
|
-
return this.#handler.onComplete()
|
|
92
|
-
}
|
|
93
82
|
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
83
|
+
const cachedAt = Date.now()
|
|
84
|
+
|
|
85
|
+
this.#value = {
|
|
86
|
+
body: null,
|
|
87
|
+
deleteAt: cachedAt + ttl * 1e3,
|
|
88
|
+
statusCode,
|
|
89
|
+
statusMessage: '',
|
|
90
|
+
headers,
|
|
91
|
+
cacheControlDirectives: '',
|
|
92
|
+
etag: '',
|
|
93
|
+
vary,
|
|
94
|
+
cachedAt,
|
|
95
|
+
staleAt: 0,
|
|
96
|
+
}
|
|
98
97
|
|
|
99
|
-
|
|
100
|
-
constructor({ maxSize = 1024 * 1024, maxEntrySize = 128 * 1024, maxTTL = 48 * 3600e3 }) {
|
|
101
|
-
this.maxSize = maxSize
|
|
102
|
-
this.maxEntrySize = maxEntrySize
|
|
103
|
-
this.maxTTL = maxTTL
|
|
104
|
-
this.cache = new LRUCache({ maxSize })
|
|
98
|
+
return super.onHeaders(statusCode, null, resume, null, headers)
|
|
105
99
|
}
|
|
106
100
|
|
|
107
|
-
|
|
108
|
-
this
|
|
109
|
-
|
|
110
|
-
value,
|
|
111
|
-
opts
|
|
112
|
-
? {
|
|
113
|
-
ttl: opts.ttl ? Math.min(opts.ttl, this.maxTTL) : undefined,
|
|
114
|
-
size: opts.size,
|
|
115
|
-
}
|
|
116
|
-
: undefined,
|
|
117
|
-
)
|
|
101
|
+
onData(chunk) {
|
|
102
|
+
this.#value = null
|
|
103
|
+
return super.onData(chunk)
|
|
118
104
|
}
|
|
119
105
|
|
|
120
|
-
|
|
121
|
-
|
|
106
|
+
onComplete() {
|
|
107
|
+
if (this.#value) {
|
|
108
|
+
this.#store.set(this.#opts, this.#value)
|
|
109
|
+
}
|
|
110
|
+
super.onComplete()
|
|
122
111
|
}
|
|
123
112
|
}
|
|
124
113
|
|
|
125
|
-
function makeKey(opts) {
|
|
126
|
-
// NOTE: Ignores headers...
|
|
127
|
-
return `${opts.origin}:${opts.method}:${opts.path}`
|
|
128
|
-
}
|
|
129
|
-
|
|
130
|
-
const DEFAULT_CACHE_STORE = new MemoryCacheStore({ maxSize: 128 * 1024, maxEntrySize: 1024 })
|
|
131
|
-
|
|
132
114
|
export default () => (dispatch) => (opts, handler) => {
|
|
133
115
|
if (!opts.cache || opts.upgrade) {
|
|
134
116
|
return dispatch(opts, handler)
|
|
@@ -154,48 +136,47 @@ export default () => (dispatch) => (opts, handler) => {
|
|
|
154
136
|
// Dump body...
|
|
155
137
|
opts.body?.on('error', () => {}).resume()
|
|
156
138
|
|
|
157
|
-
const store = opts.
|
|
158
|
-
|
|
159
|
-
if (!store) {
|
|
160
|
-
throw new Error(`Cache store not provided.`)
|
|
161
|
-
}
|
|
162
|
-
|
|
163
|
-
const key = makeKey(opts)
|
|
164
|
-
const entry = store.get(key)
|
|
165
|
-
|
|
139
|
+
const store = opts.store ?? DEFAULT_STORE
|
|
140
|
+
const entry = store.get(opts)
|
|
166
141
|
if (!entry) {
|
|
167
|
-
return dispatch(opts, new CacheHandler({
|
|
142
|
+
return dispatch(opts, new CacheHandler(opts.headers, { store, handler }))
|
|
168
143
|
}
|
|
169
144
|
|
|
170
|
-
const { statusCode, statusMessage, headers, body } = entry
|
|
171
|
-
|
|
172
145
|
let aborted = false
|
|
173
|
-
|
|
174
|
-
|
|
146
|
+
let paused = false
|
|
147
|
+
const abort = (reason) => {
|
|
148
|
+
if (!aborted) {
|
|
149
|
+
aborted = true
|
|
150
|
+
handler.onError(reason)
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
const resume = () => {
|
|
154
|
+
if (paused && !aborted) {
|
|
155
|
+
handler.onComplete()
|
|
156
|
+
paused = false
|
|
157
|
+
}
|
|
175
158
|
}
|
|
176
|
-
const resume = () => {}
|
|
177
159
|
|
|
160
|
+
const { statusCode, headers } = entry
|
|
178
161
|
try {
|
|
179
162
|
handler.onConnect(abort)
|
|
180
163
|
if (aborted) {
|
|
181
164
|
return true
|
|
182
165
|
}
|
|
183
166
|
|
|
184
|
-
handler.onHeaders(statusCode, null, resume,
|
|
167
|
+
if (handler.onHeaders(statusCode, null, resume, null, headers) === false) {
|
|
168
|
+
paused = true
|
|
169
|
+
}
|
|
170
|
+
|
|
185
171
|
if (aborted) {
|
|
186
172
|
return true
|
|
187
173
|
}
|
|
188
174
|
|
|
189
|
-
if (
|
|
190
|
-
handler.
|
|
191
|
-
if (aborted) {
|
|
192
|
-
return true
|
|
193
|
-
}
|
|
175
|
+
if (!paused) {
|
|
176
|
+
handler.onComplete()
|
|
194
177
|
}
|
|
195
|
-
|
|
196
|
-
handler.onComplete()
|
|
197
178
|
} catch (err) {
|
|
198
|
-
|
|
179
|
+
abort(err)
|
|
199
180
|
}
|
|
200
181
|
|
|
201
182
|
return true
|
package/lib/interceptor/dns.js
CHANGED
|
@@ -1,9 +1,12 @@
|
|
|
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 cache = new Map()
|
|
7
|
+
|
|
5
8
|
return async (opts, handler) => {
|
|
6
|
-
if (!opts.dns) {
|
|
9
|
+
if (!opts || !opts.dns || !opts.origin) {
|
|
7
10
|
return dispatch(opts, handler)
|
|
8
11
|
}
|
|
9
12
|
|
|
@@ -13,9 +16,32 @@ export default () => (dispatch) => {
|
|
|
13
16
|
return dispatch(opts, handler)
|
|
14
17
|
}
|
|
15
18
|
|
|
16
|
-
const
|
|
17
|
-
const
|
|
18
|
-
|
|
19
|
+
const now = getFastNow()
|
|
20
|
+
const { host, hostname } = origin
|
|
21
|
+
|
|
22
|
+
const promiseOrRecords = cache.get(hostname)
|
|
23
|
+
|
|
24
|
+
let records = promiseOrRecords?.then ? await promiseOrRecords : promiseOrRecords
|
|
25
|
+
|
|
26
|
+
records = records.filter(({ expires }) => expires > now)
|
|
27
|
+
if (records == null || records.length === 0) {
|
|
28
|
+
const promise = resolve4(hostname, { ttl: true }).then((records) =>
|
|
29
|
+
records.map(({ address, ttl }) => ({ address, expires: now + 1e3 * ttl })),
|
|
30
|
+
)
|
|
31
|
+
cache.set(hostname, promise)
|
|
32
|
+
records = await promise
|
|
33
|
+
cache.set(hostname, records)
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
if (records == null || records.length === 0) {
|
|
37
|
+
throw Object.assign(new Error('No DNS records found for the specified hostname.'), {
|
|
38
|
+
code: 'ENOTFOUND',
|
|
39
|
+
hostname: origin.hostname,
|
|
40
|
+
})
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
const addresses = records.map(({ address }) => address)
|
|
44
|
+
origin.hostname = addresses[Math.floor(Math.random() * addresses.length)]
|
|
19
45
|
|
|
20
46
|
return dispatch({ ...opts, origin, headers: { ...opts.headers, host } }, handler)
|
|
21
47
|
}
|
package/lib/interceptor/log.js
CHANGED
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import { DecoratorHandler, parseHeaders } from '../utils.js'
|
|
2
2
|
|
|
3
3
|
class Handler extends DecoratorHandler {
|
|
4
|
-
#handler
|
|
5
4
|
#opts
|
|
6
5
|
#logger
|
|
7
6
|
|
|
@@ -23,7 +22,6 @@ class Handler extends DecoratorHandler {
|
|
|
23
22
|
constructor(opts, { handler }) {
|
|
24
23
|
super(handler)
|
|
25
24
|
|
|
26
|
-
this.#handler = handler
|
|
27
25
|
this.#opts = opts
|
|
28
26
|
this.#logger = opts.logger.child({ ureq: opts })
|
|
29
27
|
|
|
@@ -41,7 +39,7 @@ class Handler extends DecoratorHandler {
|
|
|
41
39
|
|
|
42
40
|
this.#logger.debug('upstream request started')
|
|
43
41
|
|
|
44
|
-
|
|
42
|
+
super.onConnect((reason) => {
|
|
45
43
|
this.#aborted = true
|
|
46
44
|
this.#abort(reason)
|
|
47
45
|
})
|
|
@@ -62,7 +60,7 @@ class Handler extends DecoratorHandler {
|
|
|
62
60
|
this.#logger.debug('upstream request socket closed')
|
|
63
61
|
})
|
|
64
62
|
|
|
65
|
-
|
|
63
|
+
super.onUpgrade(statusCode, null, socket, headers)
|
|
66
64
|
}
|
|
67
65
|
|
|
68
66
|
onHeaders(statusCode, rawHeaders, resume, statusMessage, headers = parseHeaders(rawHeaders)) {
|
|
@@ -71,7 +69,7 @@ class Handler extends DecoratorHandler {
|
|
|
71
69
|
this.#statusCode = statusCode
|
|
72
70
|
this.#headers = headers
|
|
73
71
|
|
|
74
|
-
return
|
|
72
|
+
return super.onHeaders(statusCode, null, resume, null, headers)
|
|
75
73
|
}
|
|
76
74
|
|
|
77
75
|
onData(chunk) {
|
|
@@ -81,7 +79,7 @@ class Handler extends DecoratorHandler {
|
|
|
81
79
|
|
|
82
80
|
this.#pos += chunk.length
|
|
83
81
|
|
|
84
|
-
return
|
|
82
|
+
return super.onData(chunk)
|
|
85
83
|
}
|
|
86
84
|
|
|
87
85
|
onComplete() {
|
|
@@ -101,7 +99,7 @@ class Handler extends DecoratorHandler {
|
|
|
101
99
|
'upstream request completed',
|
|
102
100
|
)
|
|
103
101
|
|
|
104
|
-
|
|
102
|
+
super.onComplete()
|
|
105
103
|
}
|
|
106
104
|
|
|
107
105
|
onError(err) {
|
|
@@ -126,7 +124,7 @@ class Handler extends DecoratorHandler {
|
|
|
126
124
|
this.#logger.error(data, 'upstream request failed')
|
|
127
125
|
}
|
|
128
126
|
|
|
129
|
-
|
|
127
|
+
super.onError(err)
|
|
130
128
|
}
|
|
131
129
|
}
|
|
132
130
|
|
package/lib/interceptor/proxy.js
CHANGED
|
@@ -3,18 +3,16 @@ import createError from 'http-errors'
|
|
|
3
3
|
import { DecoratorHandler, parseHeaders } from '../utils.js'
|
|
4
4
|
|
|
5
5
|
class Handler extends DecoratorHandler {
|
|
6
|
-
#handler
|
|
7
6
|
#opts
|
|
8
7
|
|
|
9
8
|
constructor(proxyOpts, { handler }) {
|
|
10
9
|
super(handler)
|
|
11
10
|
|
|
12
|
-
this.#handler = handler
|
|
13
11
|
this.#opts = proxyOpts
|
|
14
12
|
}
|
|
15
13
|
|
|
16
14
|
onUpgrade(statusCode, rawHeaders, socket, headers = parseHeaders(rawHeaders)) {
|
|
17
|
-
|
|
15
|
+
super.onUpgrade(
|
|
18
16
|
statusCode,
|
|
19
17
|
reduceHeaders(
|
|
20
18
|
{
|
|
@@ -34,7 +32,7 @@ class Handler extends DecoratorHandler {
|
|
|
34
32
|
}
|
|
35
33
|
|
|
36
34
|
onHeaders(statusCode, rawHeaders, resume, statusMessage, headers = parseHeaders(rawHeaders)) {
|
|
37
|
-
return
|
|
35
|
+
return super.onHeaders(
|
|
38
36
|
statusCode,
|
|
39
37
|
reduceHeaders(
|
|
40
38
|
{
|
|
@@ -50,7 +48,7 @@ class Handler extends DecoratorHandler {
|
|
|
50
48
|
[],
|
|
51
49
|
),
|
|
52
50
|
resume,
|
|
53
|
-
|
|
51
|
+
null,
|
|
54
52
|
)
|
|
55
53
|
}
|
|
56
54
|
}
|
|
@@ -5,27 +5,25 @@ const redirectableStatusCodes = [300, 301, 302, 303, 307, 308]
|
|
|
5
5
|
|
|
6
6
|
class Handler extends DecoratorHandler {
|
|
7
7
|
#dispatch
|
|
8
|
-
#handler
|
|
9
8
|
#opts
|
|
10
9
|
#maxCount
|
|
11
10
|
|
|
12
|
-
#abort
|
|
11
|
+
#abort
|
|
13
12
|
#aborted = false
|
|
14
13
|
#reason = null
|
|
15
14
|
#headersSent = false
|
|
16
15
|
#count = 0
|
|
17
|
-
#location
|
|
16
|
+
#location
|
|
18
17
|
#history = []
|
|
19
18
|
|
|
20
19
|
constructor(opts, { dispatch, handler }) {
|
|
21
20
|
super(handler)
|
|
22
21
|
|
|
23
22
|
this.#dispatch = dispatch
|
|
24
|
-
this.#handler = handler
|
|
25
23
|
this.#opts = opts
|
|
26
24
|
this.#maxCount = Number.isFinite(opts.follow) ? opts.follow : (opts.follow?.count ?? 0)
|
|
27
25
|
|
|
28
|
-
|
|
26
|
+
super.onConnect((reason) => {
|
|
29
27
|
this.#aborted = true
|
|
30
28
|
if (this.#abort) {
|
|
31
29
|
this.#abort(reason)
|
|
@@ -44,14 +42,14 @@ class Handler extends DecoratorHandler {
|
|
|
44
42
|
}
|
|
45
43
|
|
|
46
44
|
onUpgrade(statusCode, rawHeaders, socket, headers) {
|
|
47
|
-
|
|
45
|
+
super.onUpgrade(statusCode, rawHeaders, socket, headers)
|
|
48
46
|
}
|
|
49
47
|
|
|
50
|
-
onHeaders(statusCode, rawHeaders, resume,
|
|
48
|
+
onHeaders(statusCode, rawHeaders, resume, statusMessage, headers = parseHeaders(rawHeaders)) {
|
|
51
49
|
if (redirectableStatusCodes.indexOf(statusCode) === -1) {
|
|
52
50
|
assert(!this.#headersSent)
|
|
53
51
|
this.#headersSent = true
|
|
54
|
-
return
|
|
52
|
+
return super.onHeaders(statusCode, null, resume, null, headers)
|
|
55
53
|
}
|
|
56
54
|
|
|
57
55
|
if (isDisturbed(this.#opts.body)) {
|
|
@@ -71,7 +69,7 @@ class Handler extends DecoratorHandler {
|
|
|
71
69
|
if (!this.#opts.follow(this.#location, this.#count, this.#opts)) {
|
|
72
70
|
assert(!this.#headersSent)
|
|
73
71
|
this.#headersSent = true
|
|
74
|
-
return
|
|
72
|
+
return super.onHeaders(statusCode, null, resume, null, headers)
|
|
75
73
|
}
|
|
76
74
|
} else {
|
|
77
75
|
if (this.#count >= this.#maxCount) {
|
|
@@ -128,7 +126,7 @@ class Handler extends DecoratorHandler {
|
|
|
128
126
|
servers and browsers implementors, we ignore the body as there is no specified way to eventually parse it.
|
|
129
127
|
*/
|
|
130
128
|
} else {
|
|
131
|
-
return
|
|
129
|
+
return super.onData(chunk)
|
|
132
130
|
}
|
|
133
131
|
}
|
|
134
132
|
|
|
@@ -147,12 +145,12 @@ class Handler extends DecoratorHandler {
|
|
|
147
145
|
|
|
148
146
|
this.#dispatch(this.#opts, this)
|
|
149
147
|
} else {
|
|
150
|
-
|
|
148
|
+
super.onComplete(trailers)
|
|
151
149
|
}
|
|
152
150
|
}
|
|
153
151
|
|
|
154
152
|
onError(error) {
|
|
155
|
-
|
|
153
|
+
super.onError(error)
|
|
156
154
|
}
|
|
157
155
|
}
|
|
158
156
|
|
|
@@ -2,21 +2,21 @@ import createHttpError from 'http-errors'
|
|
|
2
2
|
import { DecoratorHandler, parseHeaders } from '../utils.js'
|
|
3
3
|
|
|
4
4
|
class Handler extends DecoratorHandler {
|
|
5
|
-
#handler
|
|
6
|
-
|
|
7
5
|
#statusCode = 0
|
|
8
6
|
#contentType
|
|
9
7
|
#decoder
|
|
10
8
|
#headers
|
|
11
9
|
#body = ''
|
|
12
10
|
#opts
|
|
13
|
-
#errored = false
|
|
14
11
|
|
|
15
12
|
constructor(opts, { handler }) {
|
|
16
13
|
super(handler)
|
|
17
14
|
|
|
18
15
|
this.#opts = opts
|
|
19
|
-
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
#checkContentType(contentType) {
|
|
19
|
+
return (this.#contentType ?? '').indexOf(contentType) === 0
|
|
20
20
|
}
|
|
21
21
|
|
|
22
22
|
onConnect(abort) {
|
|
@@ -25,9 +25,8 @@ class Handler extends DecoratorHandler {
|
|
|
25
25
|
this.#decoder = null
|
|
26
26
|
this.#headers = null
|
|
27
27
|
this.#body = ''
|
|
28
|
-
this.#errored = false
|
|
29
28
|
|
|
30
|
-
|
|
29
|
+
super.onConnect(abort)
|
|
31
30
|
}
|
|
32
31
|
|
|
33
32
|
onHeaders(statusCode, rawHeaders, resume, statusMessage, headers = parseHeaders(rawHeaders)) {
|
|
@@ -36,28 +35,27 @@ class Handler extends DecoratorHandler {
|
|
|
36
35
|
this.#contentType = headers['content-type']
|
|
37
36
|
|
|
38
37
|
if (this.#statusCode < 400) {
|
|
39
|
-
return
|
|
38
|
+
return super.onHeaders(statusCode, null, resume, null, headers)
|
|
40
39
|
}
|
|
41
40
|
|
|
42
|
-
|
|
43
|
-
if (this.#contentType === 'application/json' || this.#contentType === 'text/plain') {
|
|
41
|
+
if (this.#checkContentType('application/json') || this.#checkContentType('text/plain')) {
|
|
44
42
|
this.#decoder = new TextDecoder('utf-8')
|
|
45
43
|
}
|
|
46
44
|
}
|
|
47
45
|
|
|
48
46
|
onData(chunk) {
|
|
49
|
-
if (this.#statusCode
|
|
50
|
-
|
|
51
|
-
} else {
|
|
52
|
-
return this.#handler.onData(chunk)
|
|
47
|
+
if (this.#statusCode < 400) {
|
|
48
|
+
return super.onData(chunk)
|
|
53
49
|
}
|
|
50
|
+
|
|
51
|
+
this.#body += this.#decoder?.decode(chunk, { stream: true }) ?? ''
|
|
54
52
|
}
|
|
55
53
|
|
|
56
54
|
onComplete(rawTrailers) {
|
|
57
55
|
if (this.#statusCode >= 400) {
|
|
58
56
|
this.#body += this.#decoder?.decode(undefined, { stream: false }) ?? ''
|
|
59
57
|
|
|
60
|
-
if (this.#
|
|
58
|
+
if (this.#checkContentType('application/json')) {
|
|
61
59
|
try {
|
|
62
60
|
this.#body = JSON.parse(this.#body)
|
|
63
61
|
} catch {
|
|
@@ -65,10 +63,7 @@ class Handler extends DecoratorHandler {
|
|
|
65
63
|
}
|
|
66
64
|
}
|
|
67
65
|
|
|
68
|
-
this.#errored = true
|
|
69
|
-
|
|
70
66
|
let err
|
|
71
|
-
|
|
72
67
|
const stackTraceLimit = Error.stackTraceLimit
|
|
73
68
|
Error.stackTraceLimit = 0
|
|
74
69
|
try {
|
|
@@ -76,18 +71,15 @@ class Handler extends DecoratorHandler {
|
|
|
76
71
|
} finally {
|
|
77
72
|
Error.stackTraceLimit = stackTraceLimit
|
|
78
73
|
}
|
|
79
|
-
|
|
74
|
+
|
|
75
|
+
super.onError(this.#decorateError(err))
|
|
80
76
|
} else {
|
|
81
|
-
|
|
77
|
+
super.onComplete(rawTrailers)
|
|
82
78
|
}
|
|
83
79
|
}
|
|
84
80
|
|
|
85
81
|
onError(err) {
|
|
86
|
-
|
|
87
|
-
// Do nothing...
|
|
88
|
-
} else {
|
|
89
|
-
this.#handler.onError(this.#decorateError(err))
|
|
90
|
-
}
|
|
82
|
+
super.onError(this.#decorateError(err))
|
|
91
83
|
}
|
|
92
84
|
|
|
93
85
|
#decorateError(err) {
|
|
@@ -123,13 +115,13 @@ class Handler extends DecoratorHandler {
|
|
|
123
115
|
}
|
|
124
116
|
|
|
125
117
|
return err
|
|
126
|
-
} catch {
|
|
127
|
-
return err
|
|
118
|
+
} catch (er) {
|
|
119
|
+
return new AggregateError([er, err])
|
|
128
120
|
}
|
|
129
121
|
}
|
|
130
122
|
}
|
|
131
123
|
|
|
132
124
|
export default () => (dispatch) => (opts, handler) =>
|
|
133
|
-
opts.
|
|
125
|
+
opts.throwOnError !== false
|
|
134
126
|
? dispatch(opts, new Handler(opts, { handler }))
|
|
135
127
|
: dispatch(opts, handler)
|