undici 6.20.0 → 7.0.0-alpha.2
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 +6 -10
- package/docs/docs/api/Agent.md +0 -3
- package/docs/docs/api/Client.md +1 -3
- package/docs/docs/api/Debug.md +1 -1
- package/docs/docs/api/Dispatcher.md +60 -8
- package/docs/docs/api/EnvHttpProxyAgent.md +0 -1
- package/docs/docs/api/Fetch.md +1 -0
- package/docs/docs/api/MockAgent.md +2 -0
- package/docs/docs/api/MockPool.md +2 -1
- package/docs/docs/api/Pool.md +0 -1
- package/docs/docs/api/RetryAgent.md +1 -1
- package/docs/docs/api/RetryHandler.md +1 -1
- package/docs/docs/api/WebSocket.md +45 -3
- package/index.js +6 -6
- package/lib/api/abort-signal.js +2 -0
- package/lib/api/api-connect.js +3 -1
- package/lib/api/api-pipeline.js +7 -6
- package/lib/api/api-request.js +32 -47
- package/lib/api/api-stream.js +39 -50
- package/lib/api/api-upgrade.js +5 -3
- package/lib/api/readable.js +261 -64
- package/lib/api/util.js +2 -0
- package/lib/core/constants.js +11 -9
- package/lib/core/diagnostics.js +122 -128
- package/lib/core/errors.js +4 -4
- package/lib/core/request.js +11 -9
- package/lib/core/symbols.js +2 -1
- package/lib/core/tree.js +9 -1
- package/lib/core/util.js +219 -48
- package/lib/dispatcher/agent.js +3 -17
- package/lib/dispatcher/balanced-pool.js +5 -8
- package/lib/dispatcher/client-h1.js +278 -54
- package/lib/dispatcher/client-h2.js +1 -1
- package/lib/dispatcher/client.js +23 -34
- package/lib/dispatcher/dispatcher-base.js +2 -34
- package/lib/dispatcher/dispatcher.js +3 -24
- package/lib/dispatcher/fixed-queue.js +91 -49
- package/lib/dispatcher/pool-stats.js +2 -0
- package/lib/dispatcher/pool.js +3 -6
- package/lib/dispatcher/proxy-agent.js +6 -7
- package/lib/handler/decorator-handler.js +24 -0
- package/lib/handler/redirect-handler.js +11 -2
- package/lib/handler/retry-handler.js +12 -3
- package/lib/interceptor/dns.js +346 -0
- package/lib/interceptor/dump.js +2 -2
- package/lib/interceptor/redirect.js +11 -14
- package/lib/interceptor/response-error.js +4 -1
- 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-agent.js +5 -8
- package/lib/mock/mock-client.js +9 -4
- package/lib/mock/mock-errors.js +3 -1
- package/lib/mock/mock-interceptor.js +8 -6
- package/lib/mock/mock-pool.js +9 -4
- package/lib/mock/mock-symbols.js +3 -1
- package/lib/mock/mock-utils.js +29 -5
- package/lib/web/cache/cache.js +24 -21
- package/lib/web/cache/cachestorage.js +1 -1
- package/lib/web/cookies/index.js +17 -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 +42 -36
- package/lib/web/fetch/constants.js +35 -26
- package/lib/web/fetch/data-url.js +1 -1
- package/lib/web/fetch/formdata-parser.js +2 -2
- package/lib/web/fetch/formdata.js +65 -54
- package/lib/web/fetch/headers.js +117 -85
- package/lib/web/fetch/index.js +55 -62
- package/lib/web/fetch/request.js +135 -77
- package/lib/web/fetch/response.js +86 -56
- package/lib/web/fetch/util.js +90 -64
- package/lib/web/fetch/webidl.js +99 -64
- package/lib/web/websocket/connection.js +76 -147
- package/lib/web/websocket/constants.js +3 -4
- package/lib/web/websocket/events.js +4 -2
- package/lib/web/websocket/frame.js +45 -3
- package/lib/web/websocket/receiver.js +29 -33
- package/lib/web/websocket/sender.js +18 -13
- package/lib/web/websocket/stream/websocketerror.js +83 -0
- package/lib/web/websocket/stream/websocketstream.js +485 -0
- package/lib/web/websocket/util.js +128 -77
- package/lib/web/websocket/websocket.js +234 -135
- package/package.json +20 -33
- 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/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 +22 -8
- package/types/mock-agent.d.ts +21 -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 -4
- 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 +22 -14
- 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 +44 -6
- package/types/websocket.d.ts +34 -1
- package/docs/docs/api/DispatchInterceptor.md +0 -60
- package/lib/interceptor/redirect-interceptor.js +0 -21
- package/lib/mock/pluralizer.js +0 -29
- package/lib/web/cache/symbols.js +0 -5
- package/lib/web/fetch/file.js +0 -126
- package/lib/web/fetch/symbols.js +0 -9
- 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
package/lib/dispatcher/client.js
CHANGED
|
@@ -1,5 +1,3 @@
|
|
|
1
|
-
// @ts-check
|
|
2
|
-
|
|
3
1
|
'use strict'
|
|
4
2
|
|
|
5
3
|
const assert = require('node:assert')
|
|
@@ -43,13 +41,11 @@ const {
|
|
|
43
41
|
kBodyTimeout,
|
|
44
42
|
kStrictContentLength,
|
|
45
43
|
kConnector,
|
|
46
|
-
kMaxRedirections,
|
|
47
44
|
kMaxRequests,
|
|
48
45
|
kCounter,
|
|
49
46
|
kClose,
|
|
50
47
|
kDestroy,
|
|
51
48
|
kDispatch,
|
|
52
|
-
kInterceptors,
|
|
53
49
|
kLocalAddress,
|
|
54
50
|
kMaxResponseSize,
|
|
55
51
|
kOnError,
|
|
@@ -59,10 +55,18 @@ const {
|
|
|
59
55
|
} = require('../core/symbols.js')
|
|
60
56
|
const connectH1 = require('./client-h1.js')
|
|
61
57
|
const connectH2 = require('./client-h2.js')
|
|
62
|
-
let deprecatedInterceptorWarned = false
|
|
63
58
|
|
|
64
59
|
const kClosedResolve = Symbol('kClosedResolve')
|
|
65
60
|
|
|
61
|
+
const getDefaultNodeMaxHeaderSize = http &&
|
|
62
|
+
http.maxHeaderSize &&
|
|
63
|
+
Number.isInteger(http.maxHeaderSize) &&
|
|
64
|
+
http.maxHeaderSize > 0
|
|
65
|
+
? () => http.maxHeaderSize
|
|
66
|
+
: () => { throw new InvalidArgumentError('http module not available or http.maxHeaderSize invalid') }
|
|
67
|
+
|
|
68
|
+
const noop = () => {}
|
|
69
|
+
|
|
66
70
|
function getPipelining (client) {
|
|
67
71
|
return client[kPipelining] ?? client[kHTTPContext]?.defaultPipelining ?? 1
|
|
68
72
|
}
|
|
@@ -77,7 +81,6 @@ class Client extends DispatcherBase {
|
|
|
77
81
|
* @param {import('../../types/client.js').Client.Options} options
|
|
78
82
|
*/
|
|
79
83
|
constructor (url, {
|
|
80
|
-
interceptors,
|
|
81
84
|
maxHeaderSize,
|
|
82
85
|
headersTimeout,
|
|
83
86
|
socketTimeout,
|
|
@@ -95,7 +98,6 @@ class Client extends DispatcherBase {
|
|
|
95
98
|
tls,
|
|
96
99
|
strictContentLength,
|
|
97
100
|
maxCachedSessions,
|
|
98
|
-
maxRedirections,
|
|
99
101
|
connect,
|
|
100
102
|
maxRequestsPerClient,
|
|
101
103
|
localAddress,
|
|
@@ -106,8 +108,6 @@ class Client extends DispatcherBase {
|
|
|
106
108
|
maxConcurrentStreams,
|
|
107
109
|
allowH2
|
|
108
110
|
} = {}) {
|
|
109
|
-
super()
|
|
110
|
-
|
|
111
111
|
if (keepAlive !== undefined) {
|
|
112
112
|
throw new InvalidArgumentError('unsupported keepAlive, use pipelining=0 instead')
|
|
113
113
|
}
|
|
@@ -128,8 +128,14 @@ class Client extends DispatcherBase {
|
|
|
128
128
|
throw new InvalidArgumentError('unsupported maxKeepAliveTimeout, use keepAliveMaxTimeout instead')
|
|
129
129
|
}
|
|
130
130
|
|
|
131
|
-
if (maxHeaderSize != null
|
|
132
|
-
|
|
131
|
+
if (maxHeaderSize != null) {
|
|
132
|
+
if (!Number.isInteger(maxHeaderSize) || maxHeaderSize < 1) {
|
|
133
|
+
throw new InvalidArgumentError('invalid maxHeaderSize')
|
|
134
|
+
}
|
|
135
|
+
} else {
|
|
136
|
+
// If maxHeaderSize is not provided, use the default value from the http module
|
|
137
|
+
// or if that is not available, throw an error.
|
|
138
|
+
maxHeaderSize = getDefaultNodeMaxHeaderSize()
|
|
133
139
|
}
|
|
134
140
|
|
|
135
141
|
if (socketPath != null && typeof socketPath !== 'string') {
|
|
@@ -164,10 +170,6 @@ class Client extends DispatcherBase {
|
|
|
164
170
|
throw new InvalidArgumentError('connect must be a function or an object')
|
|
165
171
|
}
|
|
166
172
|
|
|
167
|
-
if (maxRedirections != null && (!Number.isInteger(maxRedirections) || maxRedirections < 0)) {
|
|
168
|
-
throw new InvalidArgumentError('maxRedirections must be a positive number')
|
|
169
|
-
}
|
|
170
|
-
|
|
171
173
|
if (maxRequestsPerClient != null && (!Number.isInteger(maxRequestsPerClient) || maxRequestsPerClient < 0)) {
|
|
172
174
|
throw new InvalidArgumentError('maxRequestsPerClient must be a positive number')
|
|
173
175
|
}
|
|
@@ -196,6 +198,8 @@ class Client extends DispatcherBase {
|
|
|
196
198
|
throw new InvalidArgumentError('maxConcurrentStreams must be a positive integer, greater than 0')
|
|
197
199
|
}
|
|
198
200
|
|
|
201
|
+
super()
|
|
202
|
+
|
|
199
203
|
if (typeof connect !== 'function') {
|
|
200
204
|
connect = buildConnector({
|
|
201
205
|
...tls,
|
|
@@ -208,22 +212,10 @@ class Client extends DispatcherBase {
|
|
|
208
212
|
})
|
|
209
213
|
}
|
|
210
214
|
|
|
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
215
|
this[kUrl] = util.parseOrigin(url)
|
|
224
216
|
this[kConnector] = connect
|
|
225
217
|
this[kPipelining] = pipelining != null ? pipelining : 1
|
|
226
|
-
this[kMaxHeadersSize] = maxHeaderSize
|
|
218
|
+
this[kMaxHeadersSize] = maxHeaderSize
|
|
227
219
|
this[kKeepAliveDefaultTimeout] = keepAliveTimeout == null ? 4e3 : keepAliveTimeout
|
|
228
220
|
this[kKeepAliveMaxTimeout] = keepAliveMaxTimeout == null ? 600e3 : keepAliveMaxTimeout
|
|
229
221
|
this[kKeepAliveTimeoutThreshold] = keepAliveTimeoutThreshold == null ? 2e3 : keepAliveTimeoutThreshold
|
|
@@ -236,7 +228,6 @@ class Client extends DispatcherBase {
|
|
|
236
228
|
this[kBodyTimeout] = bodyTimeout != null ? bodyTimeout : 300e3
|
|
237
229
|
this[kHeadersTimeout] = headersTimeout != null ? headersTimeout : 300e3
|
|
238
230
|
this[kStrictContentLength] = strictContentLength == null ? true : strictContentLength
|
|
239
|
-
this[kMaxRedirections] = maxRedirections
|
|
240
231
|
this[kMaxRequests] = maxRequestsPerClient
|
|
241
232
|
this[kClosedResolve] = null
|
|
242
233
|
this[kMaxResponseSize] = maxResponseSize > -1 ? maxResponseSize : -1
|
|
@@ -362,8 +353,6 @@ class Client extends DispatcherBase {
|
|
|
362
353
|
}
|
|
363
354
|
}
|
|
364
355
|
|
|
365
|
-
const createRedirectInterceptor = require('../interceptor/redirect-interceptor.js')
|
|
366
|
-
|
|
367
356
|
function onError (client, err) {
|
|
368
357
|
if (
|
|
369
358
|
client[kRunning] === 0 &&
|
|
@@ -402,7 +391,7 @@ async function connect (client) {
|
|
|
402
391
|
assert(idx !== -1)
|
|
403
392
|
const ip = hostname.substring(1, idx)
|
|
404
393
|
|
|
405
|
-
assert(net.
|
|
394
|
+
assert(net.isIPv6(ip))
|
|
406
395
|
hostname = ip
|
|
407
396
|
}
|
|
408
397
|
|
|
@@ -442,7 +431,7 @@ async function connect (client) {
|
|
|
442
431
|
})
|
|
443
432
|
|
|
444
433
|
if (client.destroyed) {
|
|
445
|
-
util.destroy(socket.on('error',
|
|
434
|
+
util.destroy(socket.on('error', noop), new ClientDestroyedError())
|
|
446
435
|
return
|
|
447
436
|
}
|
|
448
437
|
|
|
@@ -453,7 +442,7 @@ async function connect (client) {
|
|
|
453
442
|
? await connectH2(client, socket)
|
|
454
443
|
: await connectH1(client, socket)
|
|
455
444
|
} catch (err) {
|
|
456
|
-
socket.destroy().on('error',
|
|
445
|
+
socket.destroy().on('error', noop)
|
|
457
446
|
throw err
|
|
458
447
|
}
|
|
459
448
|
|
|
@@ -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
|
|
|
@@ -1,12 +1,10 @@
|
|
|
1
|
-
/* eslint-disable */
|
|
2
|
-
|
|
3
1
|
'use strict'
|
|
4
2
|
|
|
5
3
|
// Extracted from node/lib/internal/fixed_queue.js
|
|
6
4
|
|
|
7
5
|
// Currently optimal queue size, tested on V8 6.0 - 6.6. Must be power of two.
|
|
8
|
-
const kSize = 2048
|
|
9
|
-
const kMask = kSize - 1
|
|
6
|
+
const kSize = 2048
|
|
7
|
+
const kMask = kSize - 1
|
|
10
8
|
|
|
11
9
|
// The FixedQueue is implemented as a singly-linked list of fixed-size
|
|
12
10
|
// circular buffers. It looks something like this:
|
|
@@ -17,18 +15,18 @@ const kMask = kSize - 1;
|
|
|
17
15
|
// +-----------+ <-----\ +-----------+ <------\ +-----------+
|
|
18
16
|
// | [null] | \----- | next | \------- | next |
|
|
19
17
|
// +-----------+ +-----------+ +-----------+
|
|
20
|
-
// | item | <-- bottom | item | <-- bottom |
|
|
21
|
-
// | item | | item | |
|
|
22
|
-
// | item | | item | |
|
|
23
|
-
// | item | | item | |
|
|
18
|
+
// | item | <-- bottom | item | <-- bottom | undefined |
|
|
19
|
+
// | item | | item | | undefined |
|
|
20
|
+
// | item | | item | | undefined |
|
|
21
|
+
// | item | | item | | undefined |
|
|
24
22
|
// | item | | item | bottom --> | item |
|
|
25
23
|
// | item | | item | | item |
|
|
26
24
|
// | ... | | ... | | ... |
|
|
27
25
|
// | item | | item | | item |
|
|
28
26
|
// | item | | item | | item |
|
|
29
|
-
// |
|
|
30
|
-
// |
|
|
31
|
-
// |
|
|
27
|
+
// | undefined | <-- top | item | | item |
|
|
28
|
+
// | undefined | | item | | item |
|
|
29
|
+
// | undefined | | undefined | <-- top top --> | undefined |
|
|
32
30
|
// +-----------+ +-----------+ +-----------+
|
|
33
31
|
//
|
|
34
32
|
// Or, if there is only one circular buffer, it looks something
|
|
@@ -40,12 +38,12 @@ const kMask = kSize - 1;
|
|
|
40
38
|
// +-----------+ +-----------+
|
|
41
39
|
// | [null] | | [null] |
|
|
42
40
|
// +-----------+ +-----------+
|
|
43
|
-
// |
|
|
44
|
-
// |
|
|
45
|
-
// | item | <-- bottom top --> |
|
|
46
|
-
// | item | |
|
|
47
|
-
// |
|
|
48
|
-
// |
|
|
41
|
+
// | undefined | | item |
|
|
42
|
+
// | undefined | | item |
|
|
43
|
+
// | item | <-- bottom top --> | undefined |
|
|
44
|
+
// | item | | undefined |
|
|
45
|
+
// | undefined | <-- top bottom --> | item |
|
|
46
|
+
// | undefined | | item |
|
|
49
47
|
// +-----------+ +-----------+
|
|
50
48
|
//
|
|
51
49
|
// Adding a value means moving `top` forward by one, removing means
|
|
@@ -56,62 +54,106 @@ const kMask = kSize - 1;
|
|
|
56
54
|
// `top + 1 === bottom` it's full. This wastes a single space of storage
|
|
57
55
|
// but allows much quicker checks.
|
|
58
56
|
|
|
57
|
+
/**
|
|
58
|
+
* @type {FixedCircularBuffer}
|
|
59
|
+
* @template T
|
|
60
|
+
*/
|
|
59
61
|
class FixedCircularBuffer {
|
|
60
|
-
constructor() {
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
this.
|
|
62
|
+
constructor () {
|
|
63
|
+
/**
|
|
64
|
+
* @type {number}
|
|
65
|
+
*/
|
|
66
|
+
this.bottom = 0
|
|
67
|
+
/**
|
|
68
|
+
* @type {number}
|
|
69
|
+
*/
|
|
70
|
+
this.top = 0
|
|
71
|
+
/**
|
|
72
|
+
* @type {Array<T|undefined>}
|
|
73
|
+
*/
|
|
74
|
+
this.list = new Array(kSize).fill(undefined)
|
|
75
|
+
/**
|
|
76
|
+
* @type {T|null}
|
|
77
|
+
*/
|
|
78
|
+
this.next = null
|
|
65
79
|
}
|
|
66
80
|
|
|
67
|
-
|
|
68
|
-
|
|
81
|
+
/**
|
|
82
|
+
* @returns {boolean}
|
|
83
|
+
*/
|
|
84
|
+
isEmpty () {
|
|
85
|
+
return this.top === this.bottom
|
|
69
86
|
}
|
|
70
87
|
|
|
71
|
-
|
|
72
|
-
|
|
88
|
+
/**
|
|
89
|
+
* @returns {boolean}
|
|
90
|
+
*/
|
|
91
|
+
isFull () {
|
|
92
|
+
return ((this.top + 1) & kMask) === this.bottom
|
|
73
93
|
}
|
|
74
94
|
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
95
|
+
/**
|
|
96
|
+
* @param {T} data
|
|
97
|
+
* @returns {void}
|
|
98
|
+
*/
|
|
99
|
+
push (data) {
|
|
100
|
+
this.list[this.top] = data
|
|
101
|
+
this.top = (this.top + 1) & kMask
|
|
78
102
|
}
|
|
79
103
|
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
this.list[this.bottom]
|
|
85
|
-
|
|
86
|
-
|
|
104
|
+
/**
|
|
105
|
+
* @returns {T|null}
|
|
106
|
+
*/
|
|
107
|
+
shift () {
|
|
108
|
+
const nextItem = this.list[this.bottom]
|
|
109
|
+
if (nextItem === undefined) { return null }
|
|
110
|
+
this.list[this.bottom] = undefined
|
|
111
|
+
this.bottom = (this.bottom + 1) & kMask
|
|
112
|
+
return nextItem
|
|
87
113
|
}
|
|
88
114
|
}
|
|
89
115
|
|
|
116
|
+
/**
|
|
117
|
+
* @template T
|
|
118
|
+
*/
|
|
90
119
|
module.exports = class FixedQueue {
|
|
91
|
-
constructor() {
|
|
92
|
-
|
|
120
|
+
constructor () {
|
|
121
|
+
/**
|
|
122
|
+
* @type {FixedCircularBuffer<T>}
|
|
123
|
+
*/
|
|
124
|
+
this.head = this.tail = new FixedCircularBuffer()
|
|
93
125
|
}
|
|
94
126
|
|
|
95
|
-
|
|
96
|
-
|
|
127
|
+
/**
|
|
128
|
+
* @returns {boolean}
|
|
129
|
+
*/
|
|
130
|
+
isEmpty () {
|
|
131
|
+
return this.head.isEmpty()
|
|
97
132
|
}
|
|
98
133
|
|
|
99
|
-
|
|
134
|
+
/**
|
|
135
|
+
* @param {T} data
|
|
136
|
+
*/
|
|
137
|
+
push (data) {
|
|
100
138
|
if (this.head.isFull()) {
|
|
101
139
|
// Head is full: Creates a new queue, sets the old queue's `.next` to it,
|
|
102
140
|
// and sets it as the new main queue.
|
|
103
|
-
this.head = this.head.next = new FixedCircularBuffer()
|
|
141
|
+
this.head = this.head.next = new FixedCircularBuffer()
|
|
104
142
|
}
|
|
105
|
-
this.head.push(data)
|
|
143
|
+
this.head.push(data)
|
|
106
144
|
}
|
|
107
145
|
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
146
|
+
/**
|
|
147
|
+
* @returns {T|null}
|
|
148
|
+
*/
|
|
149
|
+
shift () {
|
|
150
|
+
const tail = this.tail
|
|
151
|
+
const next = tail.shift()
|
|
111
152
|
if (tail.isEmpty() && tail.next !== null) {
|
|
112
153
|
// If there is another queue, it forms the new tail.
|
|
113
|
-
this.tail = tail.next
|
|
154
|
+
this.tail = tail.next
|
|
155
|
+
tail.next = null
|
|
114
156
|
}
|
|
115
|
-
return next
|
|
157
|
+
return next
|
|
116
158
|
}
|
|
117
|
-
}
|
|
159
|
+
}
|
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')
|
|
@@ -23,10 +23,10 @@ function defaultFactory (origin, opts) {
|
|
|
23
23
|
return new Pool(origin, opts)
|
|
24
24
|
}
|
|
25
25
|
|
|
26
|
+
const noop = () => {}
|
|
27
|
+
|
|
26
28
|
class ProxyAgent extends DispatcherBase {
|
|
27
29
|
constructor (opts) {
|
|
28
|
-
super()
|
|
29
|
-
|
|
30
30
|
if (!opts || (typeof opts === 'object' && !(opts instanceof URL) && !opts.uri)) {
|
|
31
31
|
throw new InvalidArgumentError('Proxy uri is mandatory')
|
|
32
32
|
}
|
|
@@ -36,13 +36,12 @@ class ProxyAgent extends DispatcherBase {
|
|
|
36
36
|
throw new InvalidArgumentError('Proxy opts.clientFactory must be a function.')
|
|
37
37
|
}
|
|
38
38
|
|
|
39
|
+
super()
|
|
40
|
+
|
|
39
41
|
const url = this.#getUrl(opts)
|
|
40
42
|
const { href, origin, port, protocol, username, password, hostname: proxyHostname } = url
|
|
41
43
|
|
|
42
44
|
this[kProxy] = { uri: href, protocol }
|
|
43
|
-
this[kInterceptors] = opts.interceptors?.ProxyAgent && Array.isArray(opts.interceptors.ProxyAgent)
|
|
44
|
-
? opts.interceptors.ProxyAgent
|
|
45
|
-
: []
|
|
46
45
|
this[kRequestTls] = opts.requestTls
|
|
47
46
|
this[kProxyTls] = opts.proxyTls
|
|
48
47
|
this[kProxyHeaders] = opts.headers || {}
|
|
@@ -81,7 +80,7 @@ class ProxyAgent extends DispatcherBase {
|
|
|
81
80
|
servername: this[kProxyTls]?.servername || proxyHostname
|
|
82
81
|
})
|
|
83
82
|
if (statusCode !== 200) {
|
|
84
|
-
socket.on('error',
|
|
83
|
+
socket.on('error', noop).destroy()
|
|
85
84
|
callback(new RequestAbortedError(`Proxy response (${statusCode}) !== 200 when HTTP Tunneling`))
|
|
86
85
|
}
|
|
87
86
|
if (opts.protocol !== 'https:') {
|
|
@@ -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,12 +24,21 @@ 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')
|
|
30
39
|
}
|
|
31
40
|
|
|
32
|
-
util.
|
|
41
|
+
util.assertRequestHandler(handler, opts.method, opts.upgrade)
|
|
33
42
|
|
|
34
43
|
this.dispatch = dispatch
|
|
35
44
|
this.location = null
|
|
@@ -137,7 +146,7 @@ class RedirectHandler {
|
|
|
137
146
|
|
|
138
147
|
TLDR: undici always ignores 3xx response bodies.
|
|
139
148
|
|
|
140
|
-
Redirection is used to serve the requested resource from another URL, so it
|
|
149
|
+
Redirection is used to serve the requested resource from another URL, so it assumes that
|
|
141
150
|
no body is generated (and thus can be ignored). Even though generating a body is not prohibited.
|
|
142
151
|
|
|
143
152
|
For status 301, 302, 303, 307 and 308 (the latter from RFC 7238), the specs mention that the body usually
|
|
@@ -194,8 +194,8 @@ class RetryHandler {
|
|
|
194
194
|
|
|
195
195
|
// Only Partial Content 206 supposed to provide Content-Range,
|
|
196
196
|
// any other status code that partially consumed the payload
|
|
197
|
-
// should not be
|
|
198
|
-
// wrongly
|
|
197
|
+
// should not be retried because it would result in downstream
|
|
198
|
+
// wrongly concatenate multiple responses.
|
|
199
199
|
if (statusCode !== 206 && (this.start > 0 || statusCode !== 200)) {
|
|
200
200
|
this.abort(
|
|
201
201
|
new RequestRetryError('server does not support the range header and the payload was partially consumed', statusCode, {
|
|
@@ -281,7 +281,11 @@ class RetryHandler {
|
|
|
281
281
|
// Weak etags are not useful for comparison nor cache
|
|
282
282
|
// for instance not safe to assume if the response is byte-per-byte
|
|
283
283
|
// equal
|
|
284
|
-
if (
|
|
284
|
+
if (
|
|
285
|
+
this.etag != null &&
|
|
286
|
+
this.etag[0] === 'W' &&
|
|
287
|
+
this.etag[1] === '/'
|
|
288
|
+
) {
|
|
285
289
|
this.etag = null
|
|
286
290
|
}
|
|
287
291
|
|
|
@@ -339,6 +343,11 @@ class RetryHandler {
|
|
|
339
343
|
onRetry.bind(this)
|
|
340
344
|
)
|
|
341
345
|
|
|
346
|
+
/**
|
|
347
|
+
* @this {RetryHandler}
|
|
348
|
+
* @param {Error} [err]
|
|
349
|
+
* @returns
|
|
350
|
+
*/
|
|
342
351
|
function onRetry (err) {
|
|
343
352
|
if (err != null || this.aborted || isDisturbed(this.opts.body)) {
|
|
344
353
|
return this.handler.onError(err)
|