undici 6.19.8 → 7.0.0-alpha.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/README.md +5 -9
- package/docs/docs/api/Agent.md +0 -3
- package/docs/docs/api/Client.md +0 -2
- package/docs/docs/api/Dispatcher.md +204 -6
- package/docs/docs/api/EnvHttpProxyAgent.md +0 -1
- package/docs/docs/api/Fetch.md +1 -0
- package/docs/docs/api/Pool.md +0 -1
- package/docs/docs/api/RetryHandler.md +1 -1
- package/index.js +0 -4
- package/lib/api/api-connect.js +3 -1
- package/lib/api/api-pipeline.js +3 -4
- package/lib/api/api-request.js +29 -46
- package/lib/api/api-stream.js +36 -49
- package/lib/api/api-upgrade.js +5 -3
- package/lib/api/readable.js +71 -27
- package/lib/core/connect.js +39 -24
- package/lib/core/errors.js +17 -4
- package/lib/core/request.js +7 -5
- package/lib/core/symbols.js +0 -1
- package/lib/core/tree.js +6 -0
- package/lib/core/util.js +1 -11
- package/lib/dispatcher/agent.js +3 -17
- package/lib/dispatcher/balanced-pool.js +5 -8
- package/lib/dispatcher/client-h1.js +44 -39
- package/lib/dispatcher/client.js +3 -27
- package/lib/dispatcher/dispatcher-base.js +2 -34
- package/lib/dispatcher/dispatcher.js +3 -24
- package/lib/dispatcher/pool.js +3 -6
- package/lib/dispatcher/proxy-agent.js +3 -6
- package/lib/handler/decorator-handler.js +24 -0
- package/lib/handler/redirect-handler.js +9 -0
- package/lib/handler/retry-handler.js +22 -3
- package/lib/interceptor/dump.js +2 -2
- package/lib/interceptor/redirect.js +11 -14
- package/lib/interceptor/response-error.js +89 -0
- package/lib/llhttp/constants.d.ts +97 -0
- package/lib/llhttp/constants.js +412 -192
- package/lib/llhttp/constants.js.map +1 -0
- package/lib/llhttp/llhttp-wasm.js +11 -1
- package/lib/llhttp/llhttp_simd-wasm.js +11 -1
- package/lib/llhttp/utils.d.ts +2 -0
- package/lib/llhttp/utils.js +9 -9
- package/lib/llhttp/utils.js.map +1 -0
- package/lib/mock/mock-client.js +2 -2
- package/lib/mock/mock-pool.js +2 -2
- package/lib/mock/mock-symbols.js +1 -0
- package/lib/util/timers.js +324 -44
- package/lib/web/cookies/index.js +15 -13
- package/lib/web/cookies/parse.js +2 -2
- package/lib/web/eventsource/eventsource-stream.js +9 -8
- package/lib/web/eventsource/eventsource.js +10 -6
- package/lib/web/fetch/body.js +4 -6
- package/lib/web/fetch/data-url.js +1 -1
- package/lib/web/fetch/formdata-parser.js +1 -2
- package/lib/web/fetch/formdata.js +28 -37
- package/lib/web/fetch/headers.js +1 -1
- package/lib/web/fetch/index.js +7 -8
- package/lib/web/fetch/request.js +7 -24
- package/lib/web/fetch/response.js +9 -22
- package/lib/web/fetch/symbols.js +0 -1
- package/lib/web/fetch/util.js +3 -12
- package/lib/web/fetch/webidl.js +73 -62
- package/lib/web/websocket/connection.js +26 -174
- package/lib/web/websocket/constants.js +1 -1
- package/lib/web/websocket/frame.js +45 -3
- package/lib/web/websocket/receiver.js +28 -26
- package/lib/web/websocket/sender.js +18 -13
- package/lib/web/websocket/util.js +20 -74
- package/lib/web/websocket/websocket.js +294 -70
- package/package.json +16 -29
- package/scripts/strip-comments.js +3 -1
- package/types/agent.d.ts +7 -7
- package/types/api.d.ts +24 -24
- package/types/balanced-pool.d.ts +11 -11
- package/types/client.d.ts +11 -12
- package/types/diagnostics-channel.d.ts +10 -10
- package/types/dispatcher.d.ts +96 -97
- package/types/env-http-proxy-agent.d.ts +2 -2
- package/types/errors.d.ts +53 -47
- package/types/eventsource.d.ts +0 -2
- package/types/fetch.d.ts +8 -8
- package/types/formdata.d.ts +7 -7
- package/types/global-dispatcher.d.ts +4 -4
- package/types/global-origin.d.ts +5 -5
- package/types/handlers.d.ts +4 -4
- package/types/header.d.ts +157 -1
- package/types/index.d.ts +42 -46
- package/types/interceptors.d.ts +10 -8
- package/types/mock-agent.d.ts +18 -18
- package/types/mock-client.d.ts +4 -4
- package/types/mock-errors.d.ts +3 -3
- package/types/mock-interceptor.d.ts +19 -19
- package/types/mock-pool.d.ts +4 -4
- package/types/patch.d.ts +0 -42
- package/types/pool-stats.d.ts +8 -8
- package/types/pool.d.ts +12 -12
- package/types/proxy-agent.d.ts +4 -4
- package/types/readable.d.ts +14 -9
- package/types/retry-agent.d.ts +1 -1
- package/types/retry-handler.d.ts +8 -8
- package/types/util.d.ts +3 -3
- package/types/utility.d.ts +7 -0
- package/types/webidl.d.ts +22 -4
- package/types/websocket.d.ts +1 -3
- package/docs/docs/api/DispatchInterceptor.md +0 -60
- package/lib/interceptor/redirect-interceptor.js +0 -21
- package/lib/web/fetch/file.js +0 -126
- package/lib/web/fileapi/encoding.js +0 -290
- package/lib/web/fileapi/filereader.js +0 -344
- package/lib/web/fileapi/progressevent.js +0 -78
- package/lib/web/fileapi/symbols.js +0 -10
- package/lib/web/fileapi/util.js +0 -391
- package/lib/web/websocket/symbols.js +0 -12
- package/types/file.d.ts +0 -39
- package/types/filereader.d.ts +0 -54
|
@@ -13,7 +13,7 @@ const {
|
|
|
13
13
|
kGetDispatcher
|
|
14
14
|
} = require('./pool-base')
|
|
15
15
|
const Pool = require('./pool')
|
|
16
|
-
const { kUrl
|
|
16
|
+
const { kUrl } = require('../core/symbols')
|
|
17
17
|
const { parseOrigin } = require('../core/util')
|
|
18
18
|
const kFactory = Symbol('factory')
|
|
19
19
|
|
|
@@ -50,6 +50,10 @@ function defaultFactory (origin, opts) {
|
|
|
50
50
|
|
|
51
51
|
class BalancedPool extends PoolBase {
|
|
52
52
|
constructor (upstreams = [], { factory = defaultFactory, ...opts } = {}) {
|
|
53
|
+
if (typeof factory !== 'function') {
|
|
54
|
+
throw new InvalidArgumentError('factory must be a function.')
|
|
55
|
+
}
|
|
56
|
+
|
|
53
57
|
super()
|
|
54
58
|
|
|
55
59
|
this[kOptions] = opts
|
|
@@ -63,13 +67,6 @@ class BalancedPool extends PoolBase {
|
|
|
63
67
|
upstreams = [upstreams]
|
|
64
68
|
}
|
|
65
69
|
|
|
66
|
-
if (typeof factory !== 'function') {
|
|
67
|
-
throw new InvalidArgumentError('factory must be a function.')
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
this[kInterceptors] = opts.interceptors?.BalancedPool && Array.isArray(opts.interceptors.BalancedPool)
|
|
71
|
-
? opts.interceptors.BalancedPool
|
|
72
|
-
: []
|
|
73
70
|
this[kFactory] = factory
|
|
74
71
|
|
|
75
72
|
for (const upstream of upstreams) {
|
|
@@ -78,7 +78,6 @@ async function lazyllhttp () {
|
|
|
78
78
|
|
|
79
79
|
return await WebAssembly.instantiate(mod, {
|
|
80
80
|
env: {
|
|
81
|
-
/* eslint-disable camelcase */
|
|
82
81
|
|
|
83
82
|
wasm_on_url: (p, at, len) => {
|
|
84
83
|
/* istanbul ignore next */
|
|
@@ -117,7 +116,6 @@ async function lazyllhttp () {
|
|
|
117
116
|
return currentParser.onMessageComplete() || 0
|
|
118
117
|
}
|
|
119
118
|
|
|
120
|
-
/* eslint-enable camelcase */
|
|
121
119
|
}
|
|
122
120
|
})
|
|
123
121
|
}
|
|
@@ -164,12 +162,12 @@ class Parser {
|
|
|
164
162
|
this.maxResponseSize = client[kMaxResponseSize]
|
|
165
163
|
}
|
|
166
164
|
|
|
167
|
-
setTimeout (
|
|
165
|
+
setTimeout (delay, type) {
|
|
168
166
|
this.timeoutType = type
|
|
169
|
-
if (
|
|
170
|
-
timers.clearTimeout(this.timeout)
|
|
171
|
-
if (
|
|
172
|
-
this.timeout = timers.setTimeout(onParserTimeout,
|
|
167
|
+
if (delay !== this.timeoutValue) {
|
|
168
|
+
this.timeout && timers.clearTimeout(this.timeout)
|
|
169
|
+
if (delay) {
|
|
170
|
+
this.timeout = timers.setTimeout(onParserTimeout, delay, new WeakRef(this))
|
|
173
171
|
// istanbul ignore else: only for jest
|
|
174
172
|
if (this.timeout.unref) {
|
|
175
173
|
this.timeout.unref()
|
|
@@ -177,7 +175,7 @@ class Parser {
|
|
|
177
175
|
} else {
|
|
178
176
|
this.timeout = null
|
|
179
177
|
}
|
|
180
|
-
this.timeoutValue =
|
|
178
|
+
this.timeoutValue = delay
|
|
181
179
|
} else if (this.timeout) {
|
|
182
180
|
// istanbul ignore else: only for jest
|
|
183
181
|
if (this.timeout.refresh) {
|
|
@@ -192,7 +190,7 @@ class Parser {
|
|
|
192
190
|
}
|
|
193
191
|
|
|
194
192
|
assert(this.ptr != null)
|
|
195
|
-
assert(currentParser
|
|
193
|
+
assert(currentParser === null)
|
|
196
194
|
|
|
197
195
|
this.llhttp.llhttp_resume(this.ptr)
|
|
198
196
|
|
|
@@ -219,22 +217,27 @@ class Parser {
|
|
|
219
217
|
}
|
|
220
218
|
}
|
|
221
219
|
|
|
222
|
-
|
|
220
|
+
/**
|
|
221
|
+
* @param {Buffer} chunk
|
|
222
|
+
*/
|
|
223
|
+
execute (chunk) {
|
|
224
|
+
assert(currentParser === null)
|
|
223
225
|
assert(this.ptr != null)
|
|
224
|
-
assert(currentParser == null)
|
|
225
226
|
assert(!this.paused)
|
|
226
227
|
|
|
227
228
|
const { socket, llhttp } = this
|
|
228
229
|
|
|
229
|
-
if
|
|
230
|
+
// Allocate a new buffer if the current buffer is too small.
|
|
231
|
+
if (chunk.length > currentBufferSize) {
|
|
230
232
|
if (currentBufferPtr) {
|
|
231
233
|
llhttp.free(currentBufferPtr)
|
|
232
234
|
}
|
|
233
|
-
|
|
235
|
+
// Allocate a buffer that is a multiple of 4096 bytes.
|
|
236
|
+
currentBufferSize = Math.ceil(chunk.length / 4096) * 4096
|
|
234
237
|
currentBufferPtr = llhttp.malloc(currentBufferSize)
|
|
235
238
|
}
|
|
236
239
|
|
|
237
|
-
new Uint8Array(llhttp.memory.buffer, currentBufferPtr, currentBufferSize).set(
|
|
240
|
+
new Uint8Array(llhttp.memory.buffer, currentBufferPtr, currentBufferSize).set(chunk)
|
|
238
241
|
|
|
239
242
|
// Call `execute` on the wasm parser.
|
|
240
243
|
// We pass the `llhttp_parser` pointer address, the pointer address of buffer view data,
|
|
@@ -244,9 +247,9 @@ class Parser {
|
|
|
244
247
|
let ret
|
|
245
248
|
|
|
246
249
|
try {
|
|
247
|
-
currentBufferRef =
|
|
250
|
+
currentBufferRef = chunk
|
|
248
251
|
currentParser = this
|
|
249
|
-
ret = llhttp.llhttp_execute(this.ptr, currentBufferPtr,
|
|
252
|
+
ret = llhttp.llhttp_execute(this.ptr, currentBufferPtr, chunk.length)
|
|
250
253
|
/* eslint-disable-next-line no-useless-catch */
|
|
251
254
|
} catch (err) {
|
|
252
255
|
/* istanbul ignore next: difficult to make a test case for */
|
|
@@ -256,25 +259,27 @@ class Parser {
|
|
|
256
259
|
currentBufferRef = null
|
|
257
260
|
}
|
|
258
261
|
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
262
|
+
if (ret !== constants.ERROR.OK) {
|
|
263
|
+
const data = chunk.subarray(llhttp.llhttp_get_error_pos(this.ptr) - currentBufferPtr)
|
|
264
|
+
|
|
265
|
+
if (ret === constants.ERROR.PAUSED_UPGRADE) {
|
|
266
|
+
this.onUpgrade(data)
|
|
267
|
+
} else if (ret === constants.ERROR.PAUSED) {
|
|
268
|
+
this.paused = true
|
|
269
|
+
socket.unshift(data)
|
|
270
|
+
} else {
|
|
271
|
+
const ptr = llhttp.llhttp_get_error_reason(this.ptr)
|
|
272
|
+
let message = ''
|
|
273
|
+
/* istanbul ignore else: difficult to make a test case for */
|
|
274
|
+
if (ptr) {
|
|
275
|
+
const len = new Uint8Array(llhttp.memory.buffer, ptr).indexOf(0)
|
|
276
|
+
message =
|
|
277
|
+
'Response does not match the HTTP/1.1 protocol (' +
|
|
278
|
+
Buffer.from(llhttp.memory.buffer, ptr, len).toString() +
|
|
279
|
+
')'
|
|
280
|
+
}
|
|
281
|
+
throw new HTTPParserError(message, constants.ERROR[ret], data)
|
|
276
282
|
}
|
|
277
|
-
throw new HTTPParserError(message, constants.ERROR[ret], data.slice(offset))
|
|
278
283
|
}
|
|
279
284
|
} catch (err) {
|
|
280
285
|
util.destroy(socket, err)
|
|
@@ -282,13 +287,13 @@ class Parser {
|
|
|
282
287
|
}
|
|
283
288
|
|
|
284
289
|
destroy () {
|
|
290
|
+
assert(currentParser === null)
|
|
285
291
|
assert(this.ptr != null)
|
|
286
|
-
assert(currentParser == null)
|
|
287
292
|
|
|
288
293
|
this.llhttp.llhttp_free(this.ptr)
|
|
289
294
|
this.ptr = null
|
|
290
295
|
|
|
291
|
-
timers.clearTimeout(this.timeout)
|
|
296
|
+
this.timeout && timers.clearTimeout(this.timeout)
|
|
292
297
|
this.timeout = null
|
|
293
298
|
this.timeoutValue = null
|
|
294
299
|
this.timeoutType = null
|
|
@@ -613,16 +618,16 @@ class Parser {
|
|
|
613
618
|
}
|
|
614
619
|
|
|
615
620
|
function onParserTimeout (parser) {
|
|
616
|
-
const { socket, timeoutType, client } = parser
|
|
621
|
+
const { socket, timeoutType, client, paused } = parser.deref()
|
|
617
622
|
|
|
618
623
|
/* istanbul ignore else */
|
|
619
624
|
if (timeoutType === TIMEOUT_HEADERS) {
|
|
620
625
|
if (!socket[kWriting] || socket.writableNeedDrain || client[kRunning] > 1) {
|
|
621
|
-
assert(!
|
|
626
|
+
assert(!paused, 'cannot be paused while waiting for headers')
|
|
622
627
|
util.destroy(socket, new HeadersTimeoutError())
|
|
623
628
|
}
|
|
624
629
|
} else if (timeoutType === TIMEOUT_BODY) {
|
|
625
|
-
if (!
|
|
630
|
+
if (!paused) {
|
|
626
631
|
util.destroy(socket, new BodyTimeoutError())
|
|
627
632
|
}
|
|
628
633
|
} else if (timeoutType === TIMEOUT_IDLE) {
|
package/lib/dispatcher/client.js
CHANGED
|
@@ -43,13 +43,11 @@ const {
|
|
|
43
43
|
kBodyTimeout,
|
|
44
44
|
kStrictContentLength,
|
|
45
45
|
kConnector,
|
|
46
|
-
kMaxRedirections,
|
|
47
46
|
kMaxRequests,
|
|
48
47
|
kCounter,
|
|
49
48
|
kClose,
|
|
50
49
|
kDestroy,
|
|
51
50
|
kDispatch,
|
|
52
|
-
kInterceptors,
|
|
53
51
|
kLocalAddress,
|
|
54
52
|
kMaxResponseSize,
|
|
55
53
|
kOnError,
|
|
@@ -59,7 +57,6 @@ const {
|
|
|
59
57
|
} = require('../core/symbols.js')
|
|
60
58
|
const connectH1 = require('./client-h1.js')
|
|
61
59
|
const connectH2 = require('./client-h2.js')
|
|
62
|
-
let deprecatedInterceptorWarned = false
|
|
63
60
|
|
|
64
61
|
const kClosedResolve = Symbol('kClosedResolve')
|
|
65
62
|
|
|
@@ -77,7 +74,6 @@ class Client extends DispatcherBase {
|
|
|
77
74
|
* @param {import('../../types/client.js').Client.Options} options
|
|
78
75
|
*/
|
|
79
76
|
constructor (url, {
|
|
80
|
-
interceptors,
|
|
81
77
|
maxHeaderSize,
|
|
82
78
|
headersTimeout,
|
|
83
79
|
socketTimeout,
|
|
@@ -95,7 +91,6 @@ class Client extends DispatcherBase {
|
|
|
95
91
|
tls,
|
|
96
92
|
strictContentLength,
|
|
97
93
|
maxCachedSessions,
|
|
98
|
-
maxRedirections,
|
|
99
94
|
connect,
|
|
100
95
|
maxRequestsPerClient,
|
|
101
96
|
localAddress,
|
|
@@ -106,8 +101,6 @@ class Client extends DispatcherBase {
|
|
|
106
101
|
maxConcurrentStreams,
|
|
107
102
|
allowH2
|
|
108
103
|
} = {}) {
|
|
109
|
-
super()
|
|
110
|
-
|
|
111
104
|
if (keepAlive !== undefined) {
|
|
112
105
|
throw new InvalidArgumentError('unsupported keepAlive, use pipelining=0 instead')
|
|
113
106
|
}
|
|
@@ -164,10 +157,6 @@ class Client extends DispatcherBase {
|
|
|
164
157
|
throw new InvalidArgumentError('connect must be a function or an object')
|
|
165
158
|
}
|
|
166
159
|
|
|
167
|
-
if (maxRedirections != null && (!Number.isInteger(maxRedirections) || maxRedirections < 0)) {
|
|
168
|
-
throw new InvalidArgumentError('maxRedirections must be a positive number')
|
|
169
|
-
}
|
|
170
|
-
|
|
171
160
|
if (maxRequestsPerClient != null && (!Number.isInteger(maxRequestsPerClient) || maxRequestsPerClient < 0)) {
|
|
172
161
|
throw new InvalidArgumentError('maxRequestsPerClient must be a positive number')
|
|
173
162
|
}
|
|
@@ -196,6 +185,8 @@ class Client extends DispatcherBase {
|
|
|
196
185
|
throw new InvalidArgumentError('maxConcurrentStreams must be a positive integer, greater than 0')
|
|
197
186
|
}
|
|
198
187
|
|
|
188
|
+
super()
|
|
189
|
+
|
|
199
190
|
if (typeof connect !== 'function') {
|
|
200
191
|
connect = buildConnector({
|
|
201
192
|
...tls,
|
|
@@ -208,18 +199,6 @@ class Client extends DispatcherBase {
|
|
|
208
199
|
})
|
|
209
200
|
}
|
|
210
201
|
|
|
211
|
-
if (interceptors?.Client && Array.isArray(interceptors.Client)) {
|
|
212
|
-
this[kInterceptors] = interceptors.Client
|
|
213
|
-
if (!deprecatedInterceptorWarned) {
|
|
214
|
-
deprecatedInterceptorWarned = true
|
|
215
|
-
process.emitWarning('Client.Options#interceptor is deprecated. Use Dispatcher#compose instead.', {
|
|
216
|
-
code: 'UNDICI-CLIENT-INTERCEPTOR-DEPRECATED'
|
|
217
|
-
})
|
|
218
|
-
}
|
|
219
|
-
} else {
|
|
220
|
-
this[kInterceptors] = [createRedirectInterceptor({ maxRedirections })]
|
|
221
|
-
}
|
|
222
|
-
|
|
223
202
|
this[kUrl] = util.parseOrigin(url)
|
|
224
203
|
this[kConnector] = connect
|
|
225
204
|
this[kPipelining] = pipelining != null ? pipelining : 1
|
|
@@ -236,7 +215,6 @@ class Client extends DispatcherBase {
|
|
|
236
215
|
this[kBodyTimeout] = bodyTimeout != null ? bodyTimeout : 300e3
|
|
237
216
|
this[kHeadersTimeout] = headersTimeout != null ? headersTimeout : 300e3
|
|
238
217
|
this[kStrictContentLength] = strictContentLength == null ? true : strictContentLength
|
|
239
|
-
this[kMaxRedirections] = maxRedirections
|
|
240
218
|
this[kMaxRequests] = maxRequestsPerClient
|
|
241
219
|
this[kClosedResolve] = null
|
|
242
220
|
this[kMaxResponseSize] = maxResponseSize > -1 ? maxResponseSize : -1
|
|
@@ -362,8 +340,6 @@ class Client extends DispatcherBase {
|
|
|
362
340
|
}
|
|
363
341
|
}
|
|
364
342
|
|
|
365
|
-
const createRedirectInterceptor = require('../interceptor/redirect-interceptor.js')
|
|
366
|
-
|
|
367
343
|
function onError (client, err) {
|
|
368
344
|
if (
|
|
369
345
|
client[kRunning] === 0 &&
|
|
@@ -398,7 +374,7 @@ async function connect (client) {
|
|
|
398
374
|
assert(idx !== -1)
|
|
399
375
|
const ip = hostname.substring(1, idx)
|
|
400
376
|
|
|
401
|
-
assert(net.
|
|
377
|
+
assert(net.isIPv6(ip))
|
|
402
378
|
hostname = ip
|
|
403
379
|
}
|
|
404
380
|
|
|
@@ -6,11 +6,10 @@ const {
|
|
|
6
6
|
ClientClosedError,
|
|
7
7
|
InvalidArgumentError
|
|
8
8
|
} = require('../core/errors')
|
|
9
|
-
const { kDestroy, kClose, kClosed, kDestroyed, kDispatch
|
|
9
|
+
const { kDestroy, kClose, kClosed, kDestroyed, kDispatch } = require('../core/symbols')
|
|
10
10
|
|
|
11
11
|
const kOnDestroyed = Symbol('onDestroyed')
|
|
12
12
|
const kOnClosed = Symbol('onClosed')
|
|
13
|
-
const kInterceptedDispatch = Symbol('Intercepted Dispatch')
|
|
14
13
|
|
|
15
14
|
class DispatcherBase extends Dispatcher {
|
|
16
15
|
constructor () {
|
|
@@ -30,23 +29,6 @@ class DispatcherBase extends Dispatcher {
|
|
|
30
29
|
return this[kClosed]
|
|
31
30
|
}
|
|
32
31
|
|
|
33
|
-
get interceptors () {
|
|
34
|
-
return this[kInterceptors]
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
set interceptors (newInterceptors) {
|
|
38
|
-
if (newInterceptors) {
|
|
39
|
-
for (let i = newInterceptors.length - 1; i >= 0; i--) {
|
|
40
|
-
const interceptor = this[kInterceptors][i]
|
|
41
|
-
if (typeof interceptor !== 'function') {
|
|
42
|
-
throw new InvalidArgumentError('interceptor must be an function')
|
|
43
|
-
}
|
|
44
|
-
}
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
this[kInterceptors] = newInterceptors
|
|
48
|
-
}
|
|
49
|
-
|
|
50
32
|
close (callback) {
|
|
51
33
|
if (callback === undefined) {
|
|
52
34
|
return new Promise((resolve, reject) => {
|
|
@@ -142,20 +124,6 @@ class DispatcherBase extends Dispatcher {
|
|
|
142
124
|
})
|
|
143
125
|
}
|
|
144
126
|
|
|
145
|
-
[kInterceptedDispatch] (opts, handler) {
|
|
146
|
-
if (!this[kInterceptors] || this[kInterceptors].length === 0) {
|
|
147
|
-
this[kInterceptedDispatch] = this[kDispatch]
|
|
148
|
-
return this[kDispatch](opts, handler)
|
|
149
|
-
}
|
|
150
|
-
|
|
151
|
-
let dispatch = this[kDispatch].bind(this)
|
|
152
|
-
for (let i = this[kInterceptors].length - 1; i >= 0; i--) {
|
|
153
|
-
dispatch = this[kInterceptors][i](dispatch)
|
|
154
|
-
}
|
|
155
|
-
this[kInterceptedDispatch] = dispatch
|
|
156
|
-
return dispatch(opts, handler)
|
|
157
|
-
}
|
|
158
|
-
|
|
159
127
|
dispatch (opts, handler) {
|
|
160
128
|
if (!handler || typeof handler !== 'object') {
|
|
161
129
|
throw new InvalidArgumentError('handler must be an object')
|
|
@@ -174,7 +142,7 @@ class DispatcherBase extends Dispatcher {
|
|
|
174
142
|
throw new ClientClosedError()
|
|
175
143
|
}
|
|
176
144
|
|
|
177
|
-
return this[
|
|
145
|
+
return this[kDispatch](opts, handler)
|
|
178
146
|
} catch (err) {
|
|
179
147
|
if (typeof handler.onError !== 'function') {
|
|
180
148
|
throw new InvalidArgumentError('invalid onError method')
|
|
@@ -35,30 +35,9 @@ class Dispatcher extends EventEmitter {
|
|
|
35
35
|
}
|
|
36
36
|
}
|
|
37
37
|
|
|
38
|
-
return new
|
|
39
|
-
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
class ComposedDispatcher extends Dispatcher {
|
|
43
|
-
#dispatcher = null
|
|
44
|
-
#dispatch = null
|
|
45
|
-
|
|
46
|
-
constructor (dispatcher, dispatch) {
|
|
47
|
-
super()
|
|
48
|
-
this.#dispatcher = dispatcher
|
|
49
|
-
this.#dispatch = dispatch
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
dispatch (...args) {
|
|
53
|
-
this.#dispatch(...args)
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
close (...args) {
|
|
57
|
-
return this.#dispatcher.close(...args)
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
destroy (...args) {
|
|
61
|
-
return this.#dispatcher.destroy(...args)
|
|
38
|
+
return new Proxy(this, {
|
|
39
|
+
get: (target, key) => key === 'dispatch' ? dispatch : target[key]
|
|
40
|
+
})
|
|
62
41
|
}
|
|
63
42
|
}
|
|
64
43
|
|
package/lib/dispatcher/pool.js
CHANGED
|
@@ -12,7 +12,7 @@ const {
|
|
|
12
12
|
InvalidArgumentError
|
|
13
13
|
} = require('../core/errors')
|
|
14
14
|
const util = require('../core/util')
|
|
15
|
-
const { kUrl
|
|
15
|
+
const { kUrl } = require('../core/symbols')
|
|
16
16
|
const buildConnector = require('../core/connect')
|
|
17
17
|
|
|
18
18
|
const kOptions = Symbol('options')
|
|
@@ -37,8 +37,6 @@ class Pool extends PoolBase {
|
|
|
37
37
|
allowH2,
|
|
38
38
|
...options
|
|
39
39
|
} = {}) {
|
|
40
|
-
super()
|
|
41
|
-
|
|
42
40
|
if (connections != null && (!Number.isFinite(connections) || connections < 0)) {
|
|
43
41
|
throw new InvalidArgumentError('invalid connections')
|
|
44
42
|
}
|
|
@@ -51,6 +49,8 @@ class Pool extends PoolBase {
|
|
|
51
49
|
throw new InvalidArgumentError('connect must be a function or an object')
|
|
52
50
|
}
|
|
53
51
|
|
|
52
|
+
super()
|
|
53
|
+
|
|
54
54
|
if (typeof connect !== 'function') {
|
|
55
55
|
connect = buildConnector({
|
|
56
56
|
...tls,
|
|
@@ -63,9 +63,6 @@ class Pool extends PoolBase {
|
|
|
63
63
|
})
|
|
64
64
|
}
|
|
65
65
|
|
|
66
|
-
this[kInterceptors] = options.interceptors?.Pool && Array.isArray(options.interceptors.Pool)
|
|
67
|
-
? options.interceptors.Pool
|
|
68
|
-
: []
|
|
69
66
|
this[kConnections] = connections || null
|
|
70
67
|
this[kUrl] = util.parseOrigin(origin)
|
|
71
68
|
this[kOptions] = { ...util.deepClone(options), connect, allowH2 }
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
'use strict'
|
|
2
2
|
|
|
3
|
-
const { kProxy, kClose, kDestroy
|
|
3
|
+
const { kProxy, kClose, kDestroy } = require('../core/symbols')
|
|
4
4
|
const { URL } = require('node:url')
|
|
5
5
|
const Agent = require('./agent')
|
|
6
6
|
const Pool = require('./pool')
|
|
@@ -25,8 +25,6 @@ function defaultFactory (origin, opts) {
|
|
|
25
25
|
|
|
26
26
|
class ProxyAgent extends DispatcherBase {
|
|
27
27
|
constructor (opts) {
|
|
28
|
-
super()
|
|
29
|
-
|
|
30
28
|
if (!opts || (typeof opts === 'object' && !(opts instanceof URL) && !opts.uri)) {
|
|
31
29
|
throw new InvalidArgumentError('Proxy uri is mandatory')
|
|
32
30
|
}
|
|
@@ -36,13 +34,12 @@ class ProxyAgent extends DispatcherBase {
|
|
|
36
34
|
throw new InvalidArgumentError('Proxy opts.clientFactory must be a function.')
|
|
37
35
|
}
|
|
38
36
|
|
|
37
|
+
super()
|
|
38
|
+
|
|
39
39
|
const url = this.#getUrl(opts)
|
|
40
40
|
const { href, origin, port, protocol, username, password, hostname: proxyHostname } = url
|
|
41
41
|
|
|
42
42
|
this[kProxy] = { uri: href, protocol }
|
|
43
|
-
this[kInterceptors] = opts.interceptors?.ProxyAgent && Array.isArray(opts.interceptors.ProxyAgent)
|
|
44
|
-
? opts.interceptors.ProxyAgent
|
|
45
|
-
: []
|
|
46
43
|
this[kRequestTls] = opts.requestTls
|
|
47
44
|
this[kProxyTls] = opts.proxyTls
|
|
48
45
|
this[kProxyHeaders] = opts.headers || {}
|
|
@@ -1,7 +1,11 @@
|
|
|
1
1
|
'use strict'
|
|
2
2
|
|
|
3
|
+
const assert = require('node:assert')
|
|
4
|
+
|
|
3
5
|
module.exports = class DecoratorHandler {
|
|
4
6
|
#handler
|
|
7
|
+
#onCompleteCalled = false
|
|
8
|
+
#onErrorCalled = false
|
|
5
9
|
|
|
6
10
|
constructor (handler) {
|
|
7
11
|
if (typeof handler !== 'object' || handler === null) {
|
|
@@ -15,30 +19,50 @@ module.exports = class DecoratorHandler {
|
|
|
15
19
|
}
|
|
16
20
|
|
|
17
21
|
onError (...args) {
|
|
22
|
+
this.#onErrorCalled = true
|
|
18
23
|
return this.#handler.onError?.(...args)
|
|
19
24
|
}
|
|
20
25
|
|
|
21
26
|
onUpgrade (...args) {
|
|
27
|
+
assert(!this.#onCompleteCalled)
|
|
28
|
+
assert(!this.#onErrorCalled)
|
|
29
|
+
|
|
22
30
|
return this.#handler.onUpgrade?.(...args)
|
|
23
31
|
}
|
|
24
32
|
|
|
25
33
|
onResponseStarted (...args) {
|
|
34
|
+
assert(!this.#onCompleteCalled)
|
|
35
|
+
assert(!this.#onErrorCalled)
|
|
36
|
+
|
|
26
37
|
return this.#handler.onResponseStarted?.(...args)
|
|
27
38
|
}
|
|
28
39
|
|
|
29
40
|
onHeaders (...args) {
|
|
41
|
+
assert(!this.#onCompleteCalled)
|
|
42
|
+
assert(!this.#onErrorCalled)
|
|
43
|
+
|
|
30
44
|
return this.#handler.onHeaders?.(...args)
|
|
31
45
|
}
|
|
32
46
|
|
|
33
47
|
onData (...args) {
|
|
48
|
+
assert(!this.#onCompleteCalled)
|
|
49
|
+
assert(!this.#onErrorCalled)
|
|
50
|
+
|
|
34
51
|
return this.#handler.onData?.(...args)
|
|
35
52
|
}
|
|
36
53
|
|
|
37
54
|
onComplete (...args) {
|
|
55
|
+
assert(!this.#onCompleteCalled)
|
|
56
|
+
assert(!this.#onErrorCalled)
|
|
57
|
+
|
|
58
|
+
this.#onCompleteCalled = true
|
|
38
59
|
return this.#handler.onComplete?.(...args)
|
|
39
60
|
}
|
|
40
61
|
|
|
41
62
|
onBodySent (...args) {
|
|
63
|
+
assert(!this.#onCompleteCalled)
|
|
64
|
+
assert(!this.#onErrorCalled)
|
|
65
|
+
|
|
42
66
|
return this.#handler.onBodySent?.(...args)
|
|
43
67
|
}
|
|
44
68
|
}
|
|
@@ -24,6 +24,15 @@ class BodyAsyncIterable {
|
|
|
24
24
|
}
|
|
25
25
|
|
|
26
26
|
class RedirectHandler {
|
|
27
|
+
static buildDispatch (dispatcher, maxRedirections) {
|
|
28
|
+
if (maxRedirections != null && (!Number.isInteger(maxRedirections) || maxRedirections < 0)) {
|
|
29
|
+
throw new InvalidArgumentError('maxRedirections must be a positive number')
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
const dispatch = dispatcher.dispatch.bind(dispatcher)
|
|
33
|
+
return (opts, originalHandler) => dispatch(opts, new RedirectHandler(dispatch, maxRedirections, opts, originalHandler))
|
|
34
|
+
}
|
|
35
|
+
|
|
27
36
|
constructor (dispatch, maxRedirections, opts, handler) {
|
|
28
37
|
if (maxRedirections != null && (!Number.isInteger(maxRedirections) || maxRedirections < 0)) {
|
|
29
38
|
throw new InvalidArgumentError('maxRedirections must be a positive number')
|
|
@@ -192,8 +192,18 @@ class RetryHandler {
|
|
|
192
192
|
if (this.resume != null) {
|
|
193
193
|
this.resume = null
|
|
194
194
|
|
|
195
|
-
|
|
196
|
-
|
|
195
|
+
// Only Partial Content 206 supposed to provide Content-Range,
|
|
196
|
+
// any other status code that partially consumed the payload
|
|
197
|
+
// should not be retry because it would result in downstream
|
|
198
|
+
// wrongly concatanete multiple responses.
|
|
199
|
+
if (statusCode !== 206 && (this.start > 0 || statusCode !== 200)) {
|
|
200
|
+
this.abort(
|
|
201
|
+
new RequestRetryError('server does not support the range header and the payload was partially consumed', statusCode, {
|
|
202
|
+
headers,
|
|
203
|
+
data: { count: this.retryCount }
|
|
204
|
+
})
|
|
205
|
+
)
|
|
206
|
+
return false
|
|
197
207
|
}
|
|
198
208
|
|
|
199
209
|
const contentRange = parseRangeHeader(headers['content-range'])
|
|
@@ -271,7 +281,11 @@ class RetryHandler {
|
|
|
271
281
|
// Weak etags are not useful for comparison nor cache
|
|
272
282
|
// for instance not safe to assume if the response is byte-per-byte
|
|
273
283
|
// equal
|
|
274
|
-
if (
|
|
284
|
+
if (
|
|
285
|
+
this.etag != null &&
|
|
286
|
+
this.etag[0] === 'W' &&
|
|
287
|
+
this.etag[1] === '/'
|
|
288
|
+
) {
|
|
275
289
|
this.etag = null
|
|
276
290
|
}
|
|
277
291
|
|
|
@@ -329,6 +343,11 @@ class RetryHandler {
|
|
|
329
343
|
onRetry.bind(this)
|
|
330
344
|
)
|
|
331
345
|
|
|
346
|
+
/**
|
|
347
|
+
* @this {RetryHandler}
|
|
348
|
+
* @param {Error} [err]
|
|
349
|
+
* @returns
|
|
350
|
+
*/
|
|
332
351
|
function onRetry (err) {
|
|
333
352
|
if (err != null || this.aborted || isDisturbed(this.opts.body)) {
|
|
334
353
|
return this.handler.onError(err)
|
package/lib/interceptor/dump.js
CHANGED
|
@@ -14,12 +14,12 @@ class DumpHandler extends DecoratorHandler {
|
|
|
14
14
|
#handler = null
|
|
15
15
|
|
|
16
16
|
constructor ({ maxSize }, handler) {
|
|
17
|
-
super(handler)
|
|
18
|
-
|
|
19
17
|
if (maxSize != null && (!Number.isFinite(maxSize) || maxSize < 1)) {
|
|
20
18
|
throw new InvalidArgumentError('maxSize must be a number greater than 0')
|
|
21
19
|
}
|
|
22
20
|
|
|
21
|
+
super(handler)
|
|
22
|
+
|
|
23
23
|
this.#maxSize = maxSize ?? this.#maxSize
|
|
24
24
|
this.#handler = handler
|
|
25
25
|
}
|
|
@@ -1,24 +1,21 @@
|
|
|
1
1
|
'use strict'
|
|
2
|
+
|
|
2
3
|
const RedirectHandler = require('../handler/redirect-handler')
|
|
3
4
|
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
const { maxRedirections = globalMaxRedirections, ...baseOpts } = opts
|
|
5
|
+
function createRedirectInterceptor ({ maxRedirections: defaultMaxRedirections } = {}) {
|
|
6
|
+
return (dispatch) => {
|
|
7
|
+
return function Intercept (opts, handler) {
|
|
8
|
+
const { maxRedirections = defaultMaxRedirections, ...rest } = opts
|
|
9
9
|
|
|
10
|
-
if (
|
|
10
|
+
if (maxRedirections == null || maxRedirections === 0) {
|
|
11
11
|
return dispatch(opts, handler)
|
|
12
12
|
}
|
|
13
13
|
|
|
14
|
-
const
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
opts,
|
|
18
|
-
handler
|
|
19
|
-
)
|
|
20
|
-
|
|
21
|
-
return dispatch(baseOpts, redirectHandler)
|
|
14
|
+
const dispatchOpts = { ...rest, maxRedirections: 0 } // Stop sub dispatcher from also redirecting.
|
|
15
|
+
const redirectHandler = new RedirectHandler(dispatch, maxRedirections, dispatchOpts, handler)
|
|
16
|
+
return dispatch(dispatchOpts, redirectHandler)
|
|
22
17
|
}
|
|
23
18
|
}
|
|
24
19
|
}
|
|
20
|
+
|
|
21
|
+
module.exports = createRedirectInterceptor
|