@nxtedition/nxt-undici 2.2.7 → 3.0.1
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 +41 -181
- package/lib/interceptor/cache.js +2 -3
- package/lib/interceptor/dns.js +68 -0
- package/lib/interceptor/log.js +2 -4
- package/lib/interceptor/proxy.js +20 -3
- package/lib/interceptor/redirect.js +2 -3
- package/lib/interceptor/request-body-factory.js +19 -20
- package/lib/interceptor/request-id.js +2 -2
- package/lib/interceptor/response-error.js +2 -3
- package/lib/interceptor/response-retry.js +9 -5
- package/lib/interceptor/response-verify.js +2 -3
- package/lib/utils.js +45 -83
- package/package.json +4 -4
- package/lib/readable.js +0 -382
package/lib/index.js
CHANGED
|
@@ -1,10 +1,5 @@
|
|
|
1
|
-
import
|
|
2
|
-
import
|
|
3
|
-
import undici from 'undici'
|
|
4
|
-
import { parseHeaders, AbortError, isStream } from './utils.js'
|
|
5
|
-
import { BodyReadable } from './readable.js'
|
|
6
|
-
|
|
7
|
-
export { parseHeaders } from './utils.js'
|
|
1
|
+
import undici from '@nxtedition/undici'
|
|
2
|
+
import { parseHeaders } from './utils.js'
|
|
8
3
|
|
|
9
4
|
const dispatcherCache = new WeakMap()
|
|
10
5
|
|
|
@@ -18,6 +13,7 @@ export const interceptors = {
|
|
|
18
13
|
proxy: (await import('./interceptor/proxy.js')).default,
|
|
19
14
|
cache: (await import('./interceptor/cache.js')).default,
|
|
20
15
|
requestId: (await import('./interceptor/request-id.js')).default,
|
|
16
|
+
dns: (await import('./interceptor/dns.js')).default,
|
|
21
17
|
}
|
|
22
18
|
|
|
23
19
|
export async function request(url, opts) {
|
|
@@ -55,180 +51,44 @@ export async function request(url, opts) {
|
|
|
55
51
|
headers['user-agent'] = userAgent
|
|
56
52
|
}
|
|
57
53
|
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
(
|
|
75
|
-
(method === 'HEAD' || method === 'GET')
|
|
76
|
-
) {
|
|
77
|
-
throw new createError.BadRequest('HEAD and GET cannot have body')
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
const expectsPayload = opts.method === 'PUT' || opts.method === 'POST' || opts.method === 'PATCH'
|
|
81
|
-
|
|
82
|
-
if (headers != null && headers['content-length'] === '0' && !expectsPayload) {
|
|
83
|
-
// https://tools.ietf.org/html/rfc7230#section-3.3.2
|
|
84
|
-
// A user agent SHOULD NOT send a Content-Length header field when
|
|
85
|
-
// the request message does not contain a payload body and the method
|
|
86
|
-
// semantics do not anticipate such a body.
|
|
87
|
-
|
|
88
|
-
// undici will error if provided an unexpected content-length: 0 header.
|
|
89
|
-
delete headers['content-length']
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
if (isStream(opts.body)) {
|
|
93
|
-
// TODO (fix): Remove this somehow?
|
|
94
|
-
// Workaround: https://github.com/nodejs/undici/pull/2497
|
|
95
|
-
opts.body.on('error', () => {})
|
|
54
|
+
const baseDispatcher = opts.dispatcher ?? undici.getGlobalDispatcher()
|
|
55
|
+
|
|
56
|
+
let dispatcher = dispatcherCache.get(baseDispatcher)
|
|
57
|
+
if (dispatcher == null) {
|
|
58
|
+
dispatcher = baseDispatcher.compose(
|
|
59
|
+
interceptors.responseError(),
|
|
60
|
+
interceptors.requestBodyFactory(),
|
|
61
|
+
interceptors.log(),
|
|
62
|
+
interceptors.dns(),
|
|
63
|
+
interceptors.requestId(),
|
|
64
|
+
interceptors.responseRetry(),
|
|
65
|
+
interceptors.responseVerify(),
|
|
66
|
+
interceptors.redirect(),
|
|
67
|
+
interceptors.cache(),
|
|
68
|
+
interceptors.proxy(),
|
|
69
|
+
)
|
|
70
|
+
dispatcherCache.set(baseDispatcher, dispatcher)
|
|
96
71
|
}
|
|
97
72
|
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
url,
|
|
120
|
-
method,
|
|
121
|
-
body: opts.body,
|
|
122
|
-
query: opts.query,
|
|
123
|
-
headers,
|
|
124
|
-
origin: url.origin,
|
|
125
|
-
path: url.path ?? (url.search ? `${url.pathname}${url.search ?? ''}` : url.pathname),
|
|
126
|
-
reset: opts.reset ?? false,
|
|
127
|
-
blocking: opts.blocking ?? false,
|
|
128
|
-
headersTimeout: opts.headersTimeout,
|
|
129
|
-
bodyTimeout: opts.bodyTimeout,
|
|
130
|
-
idempotent: opts.idempotent,
|
|
131
|
-
retry: opts.retry ?? 8,
|
|
132
|
-
proxy: opts.proxy ?? false,
|
|
133
|
-
cache: opts.cache ?? false,
|
|
134
|
-
upgrade: opts.upgrade ?? false,
|
|
135
|
-
follow: opts.follow ?? 8,
|
|
136
|
-
error: opts.error ?? true,
|
|
137
|
-
verify: opts.verify ?? true,
|
|
138
|
-
logger: opts.logger ?? null,
|
|
139
|
-
startTime: performance.now(),
|
|
140
|
-
},
|
|
141
|
-
{
|
|
142
|
-
resolve,
|
|
143
|
-
reject,
|
|
144
|
-
method,
|
|
145
|
-
highWaterMark: opts.highWaterMark ?? 128 * 1024,
|
|
146
|
-
logger: opts.logger,
|
|
147
|
-
signal: opts.signal,
|
|
148
|
-
/** @type {Function | null} */ abort: null,
|
|
149
|
-
/** @type {stream.Readable | null} */ body: null,
|
|
150
|
-
onConnect(abort) {
|
|
151
|
-
if (this.signal?.aborted) {
|
|
152
|
-
abort(this.signal.reason)
|
|
153
|
-
} else {
|
|
154
|
-
this.abort = abort
|
|
155
|
-
|
|
156
|
-
if (this.signal) {
|
|
157
|
-
this.onAbort = () => {
|
|
158
|
-
if (this.body) {
|
|
159
|
-
this.body.destroy(this.signal.reason ?? new AbortError())
|
|
160
|
-
} else {
|
|
161
|
-
this.abort(this.signal.reason)
|
|
162
|
-
}
|
|
163
|
-
}
|
|
164
|
-
this.signal.addEventListener('abort', this.onAbort)
|
|
165
|
-
}
|
|
166
|
-
}
|
|
167
|
-
},
|
|
168
|
-
onUpgrade(statusCode, rawHeaders, socket, headers = parseHeaders(rawHeaders)) {
|
|
169
|
-
if (statusCode !== 101) {
|
|
170
|
-
this.abort(createError(statusCode, { headers }))
|
|
171
|
-
} else {
|
|
172
|
-
this.resolve({ headers, socket })
|
|
173
|
-
this.resolve = null
|
|
174
|
-
}
|
|
175
|
-
},
|
|
176
|
-
onHeaders(
|
|
177
|
-
statusCode,
|
|
178
|
-
rawHeaders,
|
|
179
|
-
resume,
|
|
180
|
-
statusMessage,
|
|
181
|
-
headers = parseHeaders(rawHeaders),
|
|
182
|
-
) {
|
|
183
|
-
assert(statusCode >= 200)
|
|
184
|
-
|
|
185
|
-
const contentLength = headers['content-length']
|
|
186
|
-
const contentType = headers['content-type']
|
|
187
|
-
|
|
188
|
-
this.body = new BodyReadable(this, {
|
|
189
|
-
resume,
|
|
190
|
-
abort: this.abort,
|
|
191
|
-
highWaterMark: this.highWaterMark,
|
|
192
|
-
method: this.method,
|
|
193
|
-
statusCode,
|
|
194
|
-
statusMessage,
|
|
195
|
-
contentType: typeof contentType === 'string' ? contentType : undefined,
|
|
196
|
-
headers,
|
|
197
|
-
size: Number.isFinite(contentLength) ? contentLength : null,
|
|
198
|
-
})
|
|
199
|
-
|
|
200
|
-
if (this.signal) {
|
|
201
|
-
this.body.on('close', () => {
|
|
202
|
-
this.signal?.removeEventListener('abort', this.onAbort)
|
|
203
|
-
this.signal = null
|
|
204
|
-
})
|
|
205
|
-
}
|
|
206
|
-
|
|
207
|
-
this.resolve(this.body)
|
|
208
|
-
this.resolve = null
|
|
209
|
-
this.reject = null
|
|
210
|
-
|
|
211
|
-
return true
|
|
212
|
-
},
|
|
213
|
-
onData(chunk) {
|
|
214
|
-
return this.body.push(chunk)
|
|
215
|
-
},
|
|
216
|
-
onComplete() {
|
|
217
|
-
this.body.push(null)
|
|
218
|
-
},
|
|
219
|
-
onError(err) {
|
|
220
|
-
this.signal?.removeEventListener('abort', this.onAbort)
|
|
221
|
-
this.signal = null
|
|
222
|
-
|
|
223
|
-
if (this.body) {
|
|
224
|
-
this.body.destroy(err)
|
|
225
|
-
} else {
|
|
226
|
-
this.reject(err)
|
|
227
|
-
this.resolve = null
|
|
228
|
-
this.reject = null
|
|
229
|
-
}
|
|
230
|
-
},
|
|
231
|
-
},
|
|
232
|
-
),
|
|
233
|
-
)
|
|
73
|
+
return await undici.request(url, {
|
|
74
|
+
method,
|
|
75
|
+
dispatcher,
|
|
76
|
+
body: opts.body,
|
|
77
|
+
query: opts.query,
|
|
78
|
+
headers,
|
|
79
|
+
signal: opts.signal,
|
|
80
|
+
reset: opts.reset ?? false,
|
|
81
|
+
blocking: opts.blocking ?? false,
|
|
82
|
+
headersTimeout: opts.headersTimeout,
|
|
83
|
+
bodyTimeout: opts.bodyTimeout,
|
|
84
|
+
idempotent: opts.idempotent,
|
|
85
|
+
retry: opts.retry ?? 8,
|
|
86
|
+
proxy: opts.proxy ?? false,
|
|
87
|
+
cache: opts.cache ?? false,
|
|
88
|
+
upgrade: opts.upgrade ?? false,
|
|
89
|
+
follow: opts.follow ?? 8,
|
|
90
|
+
error: opts.error ?? true,
|
|
91
|
+
verify: opts.verify ?? true,
|
|
92
|
+
logger: opts.logger ?? null,
|
|
93
|
+
})
|
|
234
94
|
}
|
package/lib/interceptor/cache.js
CHANGED
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import assert from 'node:assert'
|
|
2
2
|
import { LRUCache } from 'lru-cache'
|
|
3
|
-
import { parseHeaders, parseCacheControl } from '../utils.js'
|
|
4
|
-
import { DecoratorHandler } from 'undici'
|
|
3
|
+
import { DecoratorHandler, parseHeaders, parseCacheControl } from '../utils.js'
|
|
5
4
|
|
|
6
5
|
class CacheHandler extends DecoratorHandler {
|
|
7
6
|
#handler
|
|
@@ -121,7 +120,7 @@ function makeKey(opts) {
|
|
|
121
120
|
|
|
122
121
|
const DEFAULT_CACHE_STORE = new CacheStore({ maxSize: 128 * 1024, maxEntrySize: 1024 })
|
|
123
122
|
|
|
124
|
-
export default (dispatch) => (opts, handler) => {
|
|
123
|
+
export default (opts) => (dispatch) => (opts, handler) => {
|
|
125
124
|
if (!opts.cache || opts.upgrade) {
|
|
126
125
|
return dispatch(opts, handler)
|
|
127
126
|
}
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import assert from 'node:assert'
|
|
2
|
+
import { DecoratorHandler } from '../utils.js'
|
|
3
|
+
import CacheableLookup from 'cacheable-lookup'
|
|
4
|
+
|
|
5
|
+
let DEFAULT_DNS
|
|
6
|
+
|
|
7
|
+
class Handler extends DecoratorHandler {
|
|
8
|
+
#handler
|
|
9
|
+
#opts
|
|
10
|
+
#hostname
|
|
11
|
+
|
|
12
|
+
constructor(opts, hostname, { handler }) {
|
|
13
|
+
super(handler)
|
|
14
|
+
|
|
15
|
+
this.#handler = handler
|
|
16
|
+
this.#opts = opts
|
|
17
|
+
this.#hostname = hostname
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
onError(err) {
|
|
21
|
+
if (
|
|
22
|
+
err.code &&
|
|
23
|
+
[
|
|
24
|
+
'ECONNRESET',
|
|
25
|
+
'ECONNREFUSED',
|
|
26
|
+
'ENOTFOUND',
|
|
27
|
+
'ENETDOWN',
|
|
28
|
+
'ENETUNREACH',
|
|
29
|
+
'EHOSTDOWN',
|
|
30
|
+
'EHOSTUNREACH',
|
|
31
|
+
'EPIPE',
|
|
32
|
+
].includes(err.code)
|
|
33
|
+
) {
|
|
34
|
+
this.#opts.dns.clear(this.#hostname)
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
return this.#handler.onError(err)
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export default (opts) => (dispatch) => (opts, handler) => {
|
|
42
|
+
const dns = opts.dns ?? (DEFAULT_DNS ??= new CacheableLookup())
|
|
43
|
+
|
|
44
|
+
if (!dns) {
|
|
45
|
+
dispatch(opts, handler)
|
|
46
|
+
return
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
try {
|
|
50
|
+
assert(typeof dns.lookup === 'function')
|
|
51
|
+
assert(typeof dns.clear === 'function')
|
|
52
|
+
|
|
53
|
+
const url = new URL(opts.origin)
|
|
54
|
+
const hostname = url.hostname
|
|
55
|
+
dns.lookup(hostname, (err, address) => {
|
|
56
|
+
if (err) {
|
|
57
|
+
handler.onConnect(() => {})
|
|
58
|
+
handler.onError(err)
|
|
59
|
+
} else {
|
|
60
|
+
url.hostname = address
|
|
61
|
+
dispatch({ ...opts, origin: url.origin }, new Handler(opts, hostname, { handler }))
|
|
62
|
+
}
|
|
63
|
+
})
|
|
64
|
+
} catch (err) {
|
|
65
|
+
handler.onConnect(() => {})
|
|
66
|
+
handler.onError(err)
|
|
67
|
+
}
|
|
68
|
+
}
|
package/lib/interceptor/log.js
CHANGED
|
@@ -1,6 +1,4 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { parseHeaders } from '../utils.js'
|
|
3
|
-
import { DecoratorHandler } from 'undici'
|
|
1
|
+
import { DecoratorHandler, parseHeaders } from '../utils.js'
|
|
4
2
|
|
|
5
3
|
class Handler extends DecoratorHandler {
|
|
6
4
|
#handler
|
|
@@ -114,5 +112,5 @@ class Handler extends DecoratorHandler {
|
|
|
114
112
|
}
|
|
115
113
|
}
|
|
116
114
|
|
|
117
|
-
export default (dispatch) => (opts, handler) =>
|
|
115
|
+
export default (opts) => (dispatch) => (opts, handler) =>
|
|
118
116
|
opts.logger ? dispatch(opts, new Handler(opts, { handler })) : dispatch(opts, handler)
|
package/lib/interceptor/proxy.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import net from 'node:net'
|
|
2
2
|
import createError from 'http-errors'
|
|
3
|
-
import { DecoratorHandler } from '
|
|
3
|
+
import { DecoratorHandler } from '../utils.js'
|
|
4
4
|
|
|
5
5
|
class Handler extends DecoratorHandler {
|
|
6
6
|
#handler
|
|
@@ -55,11 +55,17 @@ class Handler extends DecoratorHandler {
|
|
|
55
55
|
}
|
|
56
56
|
}
|
|
57
57
|
|
|
58
|
-
export default (dispatch) => (opts, handler) => {
|
|
58
|
+
export default (opts) => (dispatch) => (opts, handler) => {
|
|
59
59
|
if (!opts.proxy) {
|
|
60
60
|
return dispatch(opts, handler)
|
|
61
61
|
}
|
|
62
62
|
|
|
63
|
+
const expectsPayload =
|
|
64
|
+
opts.method === 'PUT' ||
|
|
65
|
+
opts.method === 'POST' ||
|
|
66
|
+
opts.method === 'PATCH' ||
|
|
67
|
+
opts.method === 'QUERY'
|
|
68
|
+
|
|
63
69
|
const headers = reduceHeaders(
|
|
64
70
|
{
|
|
65
71
|
headers: opts.headers ?? {},
|
|
@@ -68,7 +74,18 @@ export default (dispatch) => (opts, handler) => {
|
|
|
68
74
|
proxyName: opts.proxy.name,
|
|
69
75
|
},
|
|
70
76
|
(obj, key, val) => {
|
|
71
|
-
|
|
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
|
+
}
|
|
72
89
|
return obj
|
|
73
90
|
},
|
|
74
91
|
{},
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import assert from 'node:assert'
|
|
2
|
-
import { isDisturbed, parseHeaders, parseURL } from '../utils.js'
|
|
3
|
-
import { DecoratorHandler } from 'undici'
|
|
2
|
+
import { DecoratorHandler, isDisturbed, parseHeaders, parseURL } from '../utils.js'
|
|
4
3
|
|
|
5
4
|
const redirectableStatusCodes = [300, 301, 302, 303, 307, 308]
|
|
6
5
|
|
|
@@ -185,5 +184,5 @@ function cleanRequestHeaders(headers, removeContent, unknownOrigin) {
|
|
|
185
184
|
return ret
|
|
186
185
|
}
|
|
187
186
|
|
|
188
|
-
export default (dispatch) => (opts, handler) =>
|
|
187
|
+
export default (opts) => (dispatch) => (opts, handler) =>
|
|
189
188
|
opts.follow ? dispatch(opts, new Handler(opts, { handler, dispatch })) : dispatch(opts, handler)
|
|
@@ -1,27 +1,26 @@
|
|
|
1
|
-
export default (dispatch) => (opts, handler) => {
|
|
1
|
+
export default (opts) => (dispatch) => (opts, handler) => {
|
|
2
2
|
if (typeof opts.body !== 'function') {
|
|
3
3
|
return dispatch(opts, handler)
|
|
4
4
|
}
|
|
5
5
|
|
|
6
|
-
|
|
7
|
-
|
|
6
|
+
try {
|
|
7
|
+
const body = opts.body({ signal: opts.signal })
|
|
8
8
|
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
)
|
|
24
|
-
|
|
25
|
-
dispatch({ ...opts, body }, handler)
|
|
9
|
+
if (typeof body?.then === 'function') {
|
|
10
|
+
body.then(
|
|
11
|
+
(body) => {
|
|
12
|
+
dispatch({ ...opts, body }, handler)
|
|
13
|
+
},
|
|
14
|
+
(err) => {
|
|
15
|
+
handler.onConnect(() => {})
|
|
16
|
+
handler.onError(err)
|
|
17
|
+
},
|
|
18
|
+
)
|
|
19
|
+
} else {
|
|
20
|
+
dispatch({ ...opts, body }, handler)
|
|
21
|
+
}
|
|
22
|
+
} catch (err) {
|
|
23
|
+
handler.onConnect(() => {})
|
|
24
|
+
handler.onError(err)
|
|
26
25
|
}
|
|
27
26
|
}
|
|
@@ -11,8 +11,8 @@ function genReqId() {
|
|
|
11
11
|
return `req-${nextReqId.toString(36)}`
|
|
12
12
|
}
|
|
13
13
|
|
|
14
|
-
export default (dispatch) => (opts, handler) => {
|
|
15
|
-
let id = opts.
|
|
14
|
+
export default (opts) => (dispatch) => (opts, handler) => {
|
|
15
|
+
let id = opts.headers?.['request-id']
|
|
16
16
|
id = id ? `${id},${genReqId()}` : genReqId()
|
|
17
17
|
|
|
18
18
|
return dispatch(
|
|
@@ -1,6 +1,5 @@
|
|
|
1
|
-
import { parseHeaders } from '../utils.js'
|
|
2
1
|
import createHttpError from 'http-errors'
|
|
3
|
-
import { DecoratorHandler } from '
|
|
2
|
+
import { DecoratorHandler, parseHeaders } from '../utils.js'
|
|
4
3
|
|
|
5
4
|
class Handler extends DecoratorHandler {
|
|
6
5
|
#handler
|
|
@@ -95,7 +94,7 @@ class Handler extends DecoratorHandler {
|
|
|
95
94
|
}
|
|
96
95
|
}
|
|
97
96
|
|
|
98
|
-
export default (dispatch) => (opts, handler) =>
|
|
97
|
+
export default (opts) => (dispatch) => (opts, handler) =>
|
|
99
98
|
opts.error !== false && opts.throwOnError !== false
|
|
100
99
|
? dispatch(opts, new Handler(opts, { handler }))
|
|
101
100
|
: dispatch(opts, handler)
|
|
@@ -1,6 +1,11 @@
|
|
|
1
1
|
import assert from 'node:assert'
|
|
2
|
-
import {
|
|
3
|
-
|
|
2
|
+
import {
|
|
3
|
+
DecoratorHandler,
|
|
4
|
+
isDisturbed,
|
|
5
|
+
parseHeaders,
|
|
6
|
+
parseRangeHeader,
|
|
7
|
+
retry as retryFn,
|
|
8
|
+
} from '../utils.js'
|
|
4
9
|
|
|
5
10
|
// TODO (fix): What about onUpgrade?
|
|
6
11
|
class Handler extends DecoratorHandler {
|
|
@@ -200,9 +205,8 @@ class Handler extends DecoratorHandler {
|
|
|
200
205
|
}
|
|
201
206
|
}
|
|
202
207
|
|
|
203
|
-
export default (dispatch) => (opts, handler) =>
|
|
208
|
+
export default (opts) => (dispatch) => (opts, handler) =>
|
|
204
209
|
// TODO (fix): HEAD, PUT, PATCH, DELETE, OPTIONS?
|
|
205
|
-
|
|
210
|
+
opts.retry !== false && opts.method === 'GET' && !opts.upgrade
|
|
206
211
|
? dispatch(opts, new Handler(opts, { handler, dispatch }))
|
|
207
212
|
: dispatch(opts, handler)
|
|
208
|
-
}
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import crypto from 'node:crypto'
|
|
2
2
|
import assert from 'node:assert'
|
|
3
|
-
import { parseHeaders } from '../utils.js'
|
|
4
|
-
import { DecoratorHandler } from 'undici'
|
|
3
|
+
import { DecoratorHandler, parseHeaders } from '../utils.js'
|
|
5
4
|
|
|
6
5
|
const DEFAULT_OPTS = { hash: null }
|
|
7
6
|
|
|
@@ -83,7 +82,7 @@ class Handler extends DecoratorHandler {
|
|
|
83
82
|
}
|
|
84
83
|
}
|
|
85
84
|
|
|
86
|
-
export default (dispatch) => (opts, handler) =>
|
|
85
|
+
export default (opts) => (dispatch) => (opts, handler) =>
|
|
87
86
|
!opts.upgrade && opts.verify !== false
|
|
88
87
|
? dispatch(opts, new Handler(opts, { handler }))
|
|
89
88
|
: dispatch(opts, handler)
|
package/lib/utils.js
CHANGED
|
@@ -1,16 +1,12 @@
|
|
|
1
1
|
import tp from 'node:timers/promises'
|
|
2
2
|
import cacheControlParser from 'cache-control-parser'
|
|
3
3
|
import stream from 'node:stream'
|
|
4
|
-
import { util } from 'undici'
|
|
4
|
+
import { util } from '@nxtedition/undici'
|
|
5
5
|
|
|
6
6
|
export function parseCacheControl(str) {
|
|
7
7
|
return str ? cacheControlParser.parse(str) : null
|
|
8
8
|
}
|
|
9
9
|
|
|
10
|
-
export function headerNameToString(name) {
|
|
11
|
-
return util.headerNameToString(name)
|
|
12
|
-
}
|
|
13
|
-
|
|
14
10
|
// Parsed accordingly to RFC 9110
|
|
15
11
|
// https://www.rfc-editor.org/rfc/rfc9110#field.content-range
|
|
16
12
|
export function parseRangeHeader(range) {
|
|
@@ -204,84 +200,7 @@ export function parseOrigin(url) {
|
|
|
204
200
|
* @returns {Record<string, string | string[]>}
|
|
205
201
|
*/
|
|
206
202
|
export function parseHeaders(headers, obj) {
|
|
207
|
-
|
|
208
|
-
obj = {}
|
|
209
|
-
} else {
|
|
210
|
-
// TODO (fix): assert obj values type?
|
|
211
|
-
}
|
|
212
|
-
|
|
213
|
-
if (Array.isArray(headers)) {
|
|
214
|
-
for (let i = 0; i < headers.length; i += 2) {
|
|
215
|
-
const key2 = headers[i]
|
|
216
|
-
const val2 = headers[i + 1]
|
|
217
|
-
|
|
218
|
-
// TODO (fix): assert key2 type?
|
|
219
|
-
// TODO (fix): assert val2 type?
|
|
220
|
-
|
|
221
|
-
if (val2 == null) {
|
|
222
|
-
continue
|
|
223
|
-
}
|
|
224
|
-
|
|
225
|
-
const key = headerNameToString(key2)
|
|
226
|
-
let val = obj[key]
|
|
227
|
-
|
|
228
|
-
if (val) {
|
|
229
|
-
if (!Array.isArray(val)) {
|
|
230
|
-
val = [val]
|
|
231
|
-
obj[key] = val
|
|
232
|
-
}
|
|
233
|
-
|
|
234
|
-
if (Array.isArray(val2)) {
|
|
235
|
-
val.push(...val2.filter((x) => x != null).map((x) => `${x}`))
|
|
236
|
-
} else {
|
|
237
|
-
val.push(`${val2}`)
|
|
238
|
-
}
|
|
239
|
-
} else {
|
|
240
|
-
obj[key] = Array.isArray(val2)
|
|
241
|
-
? val2.filter((x) => x != null).map((x) => `${x}`)
|
|
242
|
-
: `${val2}`
|
|
243
|
-
}
|
|
244
|
-
}
|
|
245
|
-
} else if (typeof headers === 'object' && headers !== null) {
|
|
246
|
-
for (const key2 of Object.keys(headers)) {
|
|
247
|
-
const val2 = headers[key2]
|
|
248
|
-
|
|
249
|
-
// TODO (fix): assert key2 type?
|
|
250
|
-
// TODO (fix): assert val2 type?
|
|
251
|
-
|
|
252
|
-
if (val2 == null) {
|
|
253
|
-
continue
|
|
254
|
-
}
|
|
255
|
-
|
|
256
|
-
const key = headerNameToString(key2)
|
|
257
|
-
let val = obj[key]
|
|
258
|
-
|
|
259
|
-
if (val) {
|
|
260
|
-
if (!Array.isArray(val)) {
|
|
261
|
-
val = [val]
|
|
262
|
-
obj[key] = val
|
|
263
|
-
}
|
|
264
|
-
if (Array.isArray(val2)) {
|
|
265
|
-
val.push(...val2.filter((x) => x != null).map((x) => `${x}`))
|
|
266
|
-
} else {
|
|
267
|
-
val.push(`${val2}`)
|
|
268
|
-
}
|
|
269
|
-
} else if (val2 != null) {
|
|
270
|
-
obj[key] = Array.isArray(val2)
|
|
271
|
-
? val2.filter((x) => x != null).map((x) => `${x}`)
|
|
272
|
-
: `${val2}`
|
|
273
|
-
}
|
|
274
|
-
}
|
|
275
|
-
} else if (headers != null) {
|
|
276
|
-
throw new Error('invalid argument: headers')
|
|
277
|
-
}
|
|
278
|
-
|
|
279
|
-
// See https://github.com/nodejs/node/pull/46528
|
|
280
|
-
if ('content-length' in obj && 'content-disposition' in obj) {
|
|
281
|
-
obj['content-disposition'] = Buffer.from(obj['content-disposition']).toString('latin1')
|
|
282
|
-
}
|
|
283
|
-
|
|
284
|
-
return obj
|
|
203
|
+
return util.parseHeaders(headers, obj)
|
|
285
204
|
}
|
|
286
205
|
|
|
287
206
|
export class AbortError extends Error {
|
|
@@ -328,3 +247,46 @@ export function bodyLength(body) {
|
|
|
328
247
|
|
|
329
248
|
return null
|
|
330
249
|
}
|
|
250
|
+
|
|
251
|
+
export class DecoratorHandler {
|
|
252
|
+
#handler
|
|
253
|
+
|
|
254
|
+
constructor(handler) {
|
|
255
|
+
if (typeof handler !== 'object' || handler === null) {
|
|
256
|
+
throw new TypeError('handler must be an object')
|
|
257
|
+
}
|
|
258
|
+
this.#handler = handler
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
onConnect(...args) {
|
|
262
|
+
return this.#handler.onConnect?.(...args)
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
onError(...args) {
|
|
266
|
+
return this.#handler.onError?.(...args)
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
onUpgrade(...args) {
|
|
270
|
+
return this.#handler.onUpgrade?.(...args)
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
onResponseStarted(...args) {
|
|
274
|
+
return this.#handler.onResponseStarted?.(...args)
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
onHeaders(...args) {
|
|
278
|
+
return this.#handler.onHeaders?.(...args)
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
onData(...args) {
|
|
282
|
+
return this.#handler.onData?.(...args)
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
onComplete(...args) {
|
|
286
|
+
return this.#handler.onComplete?.(...args)
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
onBodySent(...args) {
|
|
290
|
+
return this.#handler.onBodySent?.(...args)
|
|
291
|
+
}
|
|
292
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@nxtedition/nxt-undici",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "3.0.1",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"author": "Robert Nagy <robert.nagy@boffins.se>",
|
|
6
6
|
"main": "lib/index.js",
|
|
@@ -9,14 +9,14 @@
|
|
|
9
9
|
"lib/*"
|
|
10
10
|
],
|
|
11
11
|
"dependencies": {
|
|
12
|
+
"@nxtedition/undici": "^7.0.1",
|
|
12
13
|
"cache-control-parser": "^2.0.6",
|
|
13
14
|
"cacheable-lookup": "^7.0.0",
|
|
14
15
|
"http-errors": "^2.0.0",
|
|
15
|
-
"lru-cache": "^10.2.0"
|
|
16
|
-
"undici": "^6.18.2"
|
|
16
|
+
"lru-cache": "^10.2.0"
|
|
17
17
|
},
|
|
18
18
|
"devDependencies": {
|
|
19
|
-
"@types/node": "^20.14.
|
|
19
|
+
"@types/node": "^20.14.8",
|
|
20
20
|
"eslint": "^8.0.0",
|
|
21
21
|
"eslint-config-prettier": "^9.1.0",
|
|
22
22
|
"eslint-config-standard": "^17.0.0",
|
package/lib/readable.js
DELETED
|
@@ -1,382 +0,0 @@
|
|
|
1
|
-
import assert from 'node:assert'
|
|
2
|
-
import { Readable } from 'node:stream'
|
|
3
|
-
import { errors as undiciErrors } from 'undici'
|
|
4
|
-
import { isDisturbed } from './utils.js'
|
|
5
|
-
|
|
6
|
-
const { RequestAbortedError, NotSupportedError, InvalidArgumentError } = undiciErrors
|
|
7
|
-
|
|
8
|
-
const kConsume = Symbol('kConsume')
|
|
9
|
-
const kReading = Symbol('kReading')
|
|
10
|
-
const kBody = Symbol('kBody')
|
|
11
|
-
const kAbort = Symbol('abort')
|
|
12
|
-
const kContentType = Symbol('kContentType')
|
|
13
|
-
const kReadLength = Symbol('kReadLength')
|
|
14
|
-
|
|
15
|
-
const kStatusCode = Symbol('kStatusCode')
|
|
16
|
-
const kStatusMessage = Symbol('kStatusMessage')
|
|
17
|
-
const kHeaders = Symbol('kHeaders')
|
|
18
|
-
const kSize = Symbol('kSize')
|
|
19
|
-
const kHandler = Symbol('kHandler')
|
|
20
|
-
const kMethod = Symbol('kMethod')
|
|
21
|
-
|
|
22
|
-
const noop = () => {}
|
|
23
|
-
|
|
24
|
-
let ABORT_ERROR
|
|
25
|
-
|
|
26
|
-
export class BodyReadable extends Readable {
|
|
27
|
-
constructor(
|
|
28
|
-
handler,
|
|
29
|
-
{
|
|
30
|
-
contentType = '',
|
|
31
|
-
method,
|
|
32
|
-
statusCode,
|
|
33
|
-
statusMessage,
|
|
34
|
-
headers,
|
|
35
|
-
size,
|
|
36
|
-
abort,
|
|
37
|
-
highWaterMark,
|
|
38
|
-
resume,
|
|
39
|
-
},
|
|
40
|
-
) {
|
|
41
|
-
super({
|
|
42
|
-
autoDestroy: true,
|
|
43
|
-
read: resume,
|
|
44
|
-
highWaterMark,
|
|
45
|
-
})
|
|
46
|
-
|
|
47
|
-
this._readableState.dataEmitted = false
|
|
48
|
-
|
|
49
|
-
this[kHandler] = handler
|
|
50
|
-
this[kStatusCode] = statusCode
|
|
51
|
-
this[kStatusMessage] = statusMessage
|
|
52
|
-
this[kHeaders] = headers
|
|
53
|
-
this[kSize] = Number.isFinite(size) ? size : null
|
|
54
|
-
this[kAbort] = abort
|
|
55
|
-
this[kReadLength] = 0
|
|
56
|
-
|
|
57
|
-
this[kConsume] = null
|
|
58
|
-
this[kBody] = null
|
|
59
|
-
this[kContentType] = contentType
|
|
60
|
-
this[kMethod] = method
|
|
61
|
-
|
|
62
|
-
// Is stream being consumed through Readable API?
|
|
63
|
-
// This is an optimization so that we avoid checking
|
|
64
|
-
// for 'data' and 'readable' listeners in the hot path
|
|
65
|
-
// inside push().
|
|
66
|
-
this[kReading] = false
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
get statusCode() {
|
|
70
|
-
return this[kStatusCode]
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
get statusMessage() {
|
|
74
|
-
return this[kStatusMessage]
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
get headers() {
|
|
78
|
-
return this[kHeaders]
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
get size() {
|
|
82
|
-
return this[kSize]
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
get body() {
|
|
86
|
-
return this
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
_destroy(err, callback) {
|
|
90
|
-
if (!err && !this._readableState.endEmitted) {
|
|
91
|
-
err = ABORT_ERROR ??= new RequestAbortedError()
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
if (err) {
|
|
95
|
-
this[kAbort]()
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
if (this[kHandler].signal) {
|
|
99
|
-
this[kHandler].signal.removeEventListener('abort', this[kHandler].onAbort)
|
|
100
|
-
this[kHandler].signal = null
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
if (!this[kReading]) {
|
|
104
|
-
// Workaround for Node "bug". If the stream is destroyed in same
|
|
105
|
-
// tick as it is created, then a user who is waiting for a
|
|
106
|
-
// promise (i.e micro tick) for installing a 'error' listener will
|
|
107
|
-
// never get a chance and will always encounter an unhandled exception.
|
|
108
|
-
// - tick => process.nextTick(fn)
|
|
109
|
-
// - micro tick => queueMicrotask(fn)
|
|
110
|
-
setImmediate(() => callback(err))
|
|
111
|
-
} else {
|
|
112
|
-
callback(err)
|
|
113
|
-
}
|
|
114
|
-
}
|
|
115
|
-
|
|
116
|
-
on(ev, ...args) {
|
|
117
|
-
if (ev === 'data' || ev === 'readable') {
|
|
118
|
-
this[kReading] = true
|
|
119
|
-
}
|
|
120
|
-
return super.on(ev, ...args)
|
|
121
|
-
}
|
|
122
|
-
|
|
123
|
-
addListener(ev, ...args) {
|
|
124
|
-
return this.on(ev, ...args)
|
|
125
|
-
}
|
|
126
|
-
|
|
127
|
-
off(ev, ...args) {
|
|
128
|
-
const ret = super.off(ev, ...args)
|
|
129
|
-
if (ev === 'data' || ev === 'readable') {
|
|
130
|
-
this[kReading] = this.listenerCount('data') > 0 || this.listenerCount('readable') > 0
|
|
131
|
-
}
|
|
132
|
-
return ret
|
|
133
|
-
}
|
|
134
|
-
|
|
135
|
-
removeListener(ev, ...args) {
|
|
136
|
-
return this.off(ev, ...args)
|
|
137
|
-
}
|
|
138
|
-
|
|
139
|
-
setEncoding(encoding) {
|
|
140
|
-
if (encoding) {
|
|
141
|
-
throw new Error('not supported')
|
|
142
|
-
}
|
|
143
|
-
return super.setEncoding(encoding)
|
|
144
|
-
}
|
|
145
|
-
|
|
146
|
-
push(chunk) {
|
|
147
|
-
if (chunk != null) {
|
|
148
|
-
this[kReadLength] += chunk.length
|
|
149
|
-
}
|
|
150
|
-
|
|
151
|
-
if (this[kConsume] && chunk !== null) {
|
|
152
|
-
consumePush(this[kConsume], chunk)
|
|
153
|
-
return this[kReading] ? super.push(chunk) : true
|
|
154
|
-
}
|
|
155
|
-
|
|
156
|
-
return super.push(chunk)
|
|
157
|
-
}
|
|
158
|
-
|
|
159
|
-
// https://fetch.spec.whatwg.org/#dom-body-text
|
|
160
|
-
async text() {
|
|
161
|
-
return consume(this, 'text')
|
|
162
|
-
}
|
|
163
|
-
|
|
164
|
-
// https://fetch.spec.whatwg.org/#dom-body-json
|
|
165
|
-
async json() {
|
|
166
|
-
return consume(this, 'json')
|
|
167
|
-
}
|
|
168
|
-
|
|
169
|
-
// https://fetch.spec.whatwg.org/#dom-body-blob
|
|
170
|
-
async blob() {
|
|
171
|
-
return consume(this, 'blob')
|
|
172
|
-
}
|
|
173
|
-
|
|
174
|
-
// https://fetch.spec.whatwg.org/#dom-body-arraybuffer
|
|
175
|
-
async arrayBuffer() {
|
|
176
|
-
return consume(this, 'arrayBuffer')
|
|
177
|
-
}
|
|
178
|
-
|
|
179
|
-
// https://fetch.spec.whatwg.org/#dom-body-formdata
|
|
180
|
-
async formData() {
|
|
181
|
-
// TODO: Implement.
|
|
182
|
-
throw new NotSupportedError()
|
|
183
|
-
}
|
|
184
|
-
|
|
185
|
-
// https://fetch.spec.whatwg.org/#dom-body-bodyused
|
|
186
|
-
get bodyUsed() {
|
|
187
|
-
return isDisturbed(this)
|
|
188
|
-
}
|
|
189
|
-
|
|
190
|
-
async dump(opts) {
|
|
191
|
-
let limit = Number.isFinite(opts?.limit) ? opts.limit : 128 * 1024
|
|
192
|
-
const signal = opts?.signal
|
|
193
|
-
|
|
194
|
-
if (this[kSize] != null && this[kSize] - this[kReadLength] > limit) {
|
|
195
|
-
this.destroy(signal.reason ?? new RequestAbortedError())
|
|
196
|
-
}
|
|
197
|
-
|
|
198
|
-
if (signal != null && (typeof signal !== 'object' || !('aborted' in signal))) {
|
|
199
|
-
throw new InvalidArgumentError('signal must be an AbortSignal')
|
|
200
|
-
}
|
|
201
|
-
|
|
202
|
-
signal?.throwIfAborted()
|
|
203
|
-
|
|
204
|
-
if (this._readableState.closeEmitted) {
|
|
205
|
-
return null
|
|
206
|
-
}
|
|
207
|
-
|
|
208
|
-
const contentLength = this.headers['content-length']
|
|
209
|
-
? Number(this.headers['content-length'])
|
|
210
|
-
: null
|
|
211
|
-
|
|
212
|
-
if (this[kMethod] !== 'HEAD' && contentLength != null && contentLength >= limit) {
|
|
213
|
-
this.on('error', () => {}).destroy()
|
|
214
|
-
return
|
|
215
|
-
}
|
|
216
|
-
|
|
217
|
-
return await new Promise((resolve, reject) => {
|
|
218
|
-
const onAbort = () => {
|
|
219
|
-
this.destroy(signal.reason ?? new RequestAbortedError())
|
|
220
|
-
}
|
|
221
|
-
signal?.addEventListener('abort', onAbort)
|
|
222
|
-
|
|
223
|
-
this.on('close', function () {
|
|
224
|
-
signal?.removeEventListener('abort', onAbort)
|
|
225
|
-
if (signal?.aborted) {
|
|
226
|
-
reject(signal.reason ?? new RequestAbortedError())
|
|
227
|
-
} else {
|
|
228
|
-
resolve(null)
|
|
229
|
-
}
|
|
230
|
-
})
|
|
231
|
-
.on('error', noop)
|
|
232
|
-
.on('data', function (chunk) {
|
|
233
|
-
limit -= chunk.length
|
|
234
|
-
if (limit <= 0) {
|
|
235
|
-
this.destroy()
|
|
236
|
-
}
|
|
237
|
-
})
|
|
238
|
-
.resume()
|
|
239
|
-
})
|
|
240
|
-
}
|
|
241
|
-
}
|
|
242
|
-
|
|
243
|
-
// https://streams.spec.whatwg.org/#readablestream-locked
|
|
244
|
-
function isLocked(self) {
|
|
245
|
-
// Consume is an implicit lock.
|
|
246
|
-
return (self[kBody] && self[kBody].locked === true) || self[kConsume]
|
|
247
|
-
}
|
|
248
|
-
|
|
249
|
-
// https://fetch.spec.whatwg.org/#body-unusable
|
|
250
|
-
function isUnusable(self) {
|
|
251
|
-
return isDisturbed(self) || isLocked(self)
|
|
252
|
-
}
|
|
253
|
-
|
|
254
|
-
async function consume(stream, type) {
|
|
255
|
-
assert(!stream[kConsume])
|
|
256
|
-
|
|
257
|
-
return new Promise((resolve, reject) => {
|
|
258
|
-
if (isUnusable(stream)) {
|
|
259
|
-
const rState = stream._readableState
|
|
260
|
-
if (rState.destroyed && rState.closeEmitted === false) {
|
|
261
|
-
stream
|
|
262
|
-
.on('error', (err) => {
|
|
263
|
-
reject(err)
|
|
264
|
-
})
|
|
265
|
-
.on('close', () => {
|
|
266
|
-
reject(new TypeError('unusable'))
|
|
267
|
-
})
|
|
268
|
-
} else {
|
|
269
|
-
reject(rState.errored ?? new TypeError('unusable'))
|
|
270
|
-
}
|
|
271
|
-
} else {
|
|
272
|
-
queueMicrotask(() => {
|
|
273
|
-
stream[kConsume] = {
|
|
274
|
-
type,
|
|
275
|
-
stream,
|
|
276
|
-
resolve,
|
|
277
|
-
reject,
|
|
278
|
-
length: 0,
|
|
279
|
-
body: [],
|
|
280
|
-
}
|
|
281
|
-
|
|
282
|
-
stream
|
|
283
|
-
.on('error', function (err) {
|
|
284
|
-
consumeFinish(this[kConsume], err)
|
|
285
|
-
})
|
|
286
|
-
.on('close', function () {
|
|
287
|
-
if (this[kConsume].body !== null) {
|
|
288
|
-
consumeFinish(this[kConsume], new RequestAbortedError())
|
|
289
|
-
}
|
|
290
|
-
})
|
|
291
|
-
|
|
292
|
-
consumeStart(stream[kConsume])
|
|
293
|
-
})
|
|
294
|
-
}
|
|
295
|
-
})
|
|
296
|
-
}
|
|
297
|
-
|
|
298
|
-
function consumeStart(consume) {
|
|
299
|
-
if (consume.body === null) {
|
|
300
|
-
return
|
|
301
|
-
}
|
|
302
|
-
|
|
303
|
-
const { _readableState: state } = consume.stream
|
|
304
|
-
|
|
305
|
-
if (state.bufferIndex) {
|
|
306
|
-
const start = state.bufferIndex
|
|
307
|
-
const end = state.buffer.length
|
|
308
|
-
for (let n = start; n < end; n++) {
|
|
309
|
-
consumePush(consume, state.buffer[n])
|
|
310
|
-
}
|
|
311
|
-
} else {
|
|
312
|
-
for (const chunk of state.buffer) {
|
|
313
|
-
consumePush(consume, chunk)
|
|
314
|
-
}
|
|
315
|
-
}
|
|
316
|
-
|
|
317
|
-
if (state.endEmitted) {
|
|
318
|
-
consumeEnd(this[kConsume])
|
|
319
|
-
} else {
|
|
320
|
-
consume.stream.on('end', function () {
|
|
321
|
-
consumeEnd(this[kConsume])
|
|
322
|
-
})
|
|
323
|
-
}
|
|
324
|
-
|
|
325
|
-
consume.stream.resume()
|
|
326
|
-
|
|
327
|
-
while (consume.stream.read() != null) {
|
|
328
|
-
// Loop
|
|
329
|
-
}
|
|
330
|
-
}
|
|
331
|
-
|
|
332
|
-
function consumeEnd(consume) {
|
|
333
|
-
const { type, body, resolve, stream, length } = consume
|
|
334
|
-
|
|
335
|
-
try {
|
|
336
|
-
if (type === 'text') {
|
|
337
|
-
resolve(Buffer.concat(body).toString().toWellFormed())
|
|
338
|
-
} else if (type === 'json') {
|
|
339
|
-
resolve(JSON.parse(Buffer.concat(body)))
|
|
340
|
-
} else if (type === 'arrayBuffer') {
|
|
341
|
-
const dst = new Uint8Array(length)
|
|
342
|
-
|
|
343
|
-
let pos = 0
|
|
344
|
-
for (const buf of body) {
|
|
345
|
-
dst.set(buf, pos)
|
|
346
|
-
pos += buf.byteLength
|
|
347
|
-
}
|
|
348
|
-
|
|
349
|
-
resolve(dst.buffer)
|
|
350
|
-
} else if (type === 'blob') {
|
|
351
|
-
resolve(new Blob(body, { type: stream[kContentType] }))
|
|
352
|
-
}
|
|
353
|
-
|
|
354
|
-
consumeFinish(consume)
|
|
355
|
-
} catch (err) {
|
|
356
|
-
stream.destroy(err)
|
|
357
|
-
}
|
|
358
|
-
}
|
|
359
|
-
|
|
360
|
-
function consumePush(consume, chunk) {
|
|
361
|
-
consume.length += chunk.length
|
|
362
|
-
consume.body.push(chunk)
|
|
363
|
-
}
|
|
364
|
-
|
|
365
|
-
function consumeFinish(consume, err) {
|
|
366
|
-
if (consume.body === null) {
|
|
367
|
-
return
|
|
368
|
-
}
|
|
369
|
-
|
|
370
|
-
if (err) {
|
|
371
|
-
consume.reject(err)
|
|
372
|
-
} else {
|
|
373
|
-
consume.resolve()
|
|
374
|
-
}
|
|
375
|
-
|
|
376
|
-
consume.type = null
|
|
377
|
-
consume.stream = null
|
|
378
|
-
consume.resolve = null
|
|
379
|
-
consume.reject = null
|
|
380
|
-
consume.length = 0
|
|
381
|
-
consume.body = null
|
|
382
|
-
}
|