undici 8.9.0 → 8.10.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/docs/docs/api/Client.md +25 -8
- package/docs/docs/api/EnvHttpProxyAgent.md +3 -2
- package/docs/docs/api/EventSource.md +4 -0
- package/lib/api/readable.js +33 -43
- package/lib/cache/memory-cache-store.js +1 -1
- package/lib/core/connect.js +16 -2
- package/lib/core/symbols.js +1 -0
- package/lib/dispatcher/agent.js +7 -2
- package/lib/dispatcher/balanced-pool.js +1 -1
- package/lib/dispatcher/client-h1.js +9 -1
- package/lib/dispatcher/client-h2.js +221 -12
- package/lib/dispatcher/client.js +76 -30
- package/lib/dispatcher/dispatcher-base.js +13 -1
- package/lib/dispatcher/dispatcher.js +10 -0
- package/lib/dispatcher/env-http-proxy-agent.js +37 -8
- package/lib/dispatcher/proxy-agent.js +1 -1
- package/lib/dispatcher/round-robin-pool.js +1 -1
- package/lib/dispatcher/socks5-proxy-agent.js +18 -2
- package/lib/handler/cache-handler.js +8 -0
- package/lib/handler/decorator-handler.js +7 -1
- package/lib/handler/deduplication-handler.js +12 -6
- package/lib/handler/redirect-handler.js +8 -0
- package/lib/handler/retry-handler.js +90 -13
- package/lib/interceptor/cache.js +5 -2
- package/lib/interceptor/deduplicate.js +1 -1
- package/lib/interceptor/dump.js +1 -1
- package/lib/mock/mock-agent.js +13 -1
- package/lib/mock/mock-utils.js +217 -27
- package/lib/util/cache.js +2 -4
- package/lib/web/cookies/parse.js +4 -2
- package/lib/web/eventsource/eventsource-stream.js +28 -1
- package/lib/web/eventsource/eventsource.js +4 -1
- package/lib/web/fetch/request.js +2 -3
- package/lib/web/websocket/websocket.js +22 -0
- package/package.json +3 -3
- package/types/client.d.ts +50 -0
package/docs/docs/api/Client.md
CHANGED
|
@@ -111,22 +111,35 @@ added: v1.0.0
|
|
|
111
111
|
`autoSelectFamily` is enabled. **Default:** `250`.
|
|
112
112
|
* `allowH2` {boolean} Enables HTTP/2 support when the server assigns it a
|
|
113
113
|
higher priority through ALPN negotiation. **Default:** `true`.
|
|
114
|
-
* `useH2c` {boolean} Enforces h2c (HTTP/2 cleartext) for non-HTTPS
|
|
115
|
-
|
|
116
|
-
* `maxConcurrentStreams` {number} The maximum number of concurrent HTTP/2
|
|
114
|
+
* `useH2c` {boolean} _Deprecated: use h2Options.useH2c instead_ Enforces h2c (HTTP/2 cleartext) for non-HTTPS
|
|
115
|
+
connections. **Default:** `false`.
|
|
116
|
+
* `maxConcurrentStreams` {number} _Deprecated: use h2Options.useH2c instead_ The maximum number of concurrent HTTP/2
|
|
117
117
|
streams for a single session. Once h2 is negotiated this — not `pipelining`,
|
|
118
118
|
which is HTTP/1.1 only — is the ceiling used to dispatch in-flight requests.
|
|
119
119
|
It may be overridden by the server's `SETTINGS_MAX_CONCURRENT_STREAMS`
|
|
120
120
|
frame. **Default:** `100`.
|
|
121
|
-
* `
|
|
122
|
-
size (`SETTINGS_INITIAL_WINDOW_SIZE`). Must be a positive integer.
|
|
123
|
-
**Default:** `262144`.
|
|
124
|
-
* `connectionWindowSize` {number} The HTTP/2 connection-level flow-control
|
|
121
|
+
* `connectionWindowSize` {number} _Deprecated: use h2Options.connectionWindowSize instead_ The HTTP/2 connection-level flow-control
|
|
125
122
|
window size set via `ClientHttp2Session.setLocalWindowSize()`. Must be a
|
|
126
123
|
positive integer. **Default:** `524288`.
|
|
127
|
-
* `pingInterval` {number} The time interval, in milliseconds, between HTTP/2
|
|
124
|
+
* `pingInterval` {number} _Deprecated: use h2Options.pingInterval instead_ The time interval, in milliseconds, between HTTP/2
|
|
128
125
|
PING frames. Set to `0` to disable PING frames. Applies only to HTTP/2
|
|
129
126
|
connections and emits a `ping` event on the client. **Default:** `60e3`.
|
|
127
|
+
* `h2Options` {object} Set of options for HTTP/2 sessions
|
|
128
|
+
* `useH2c` {boolean} Enforces h2c (HTTP/2 cleartext) for non-HTTPS
|
|
129
|
+
connections. **Default:** `false`.
|
|
130
|
+
* `maxConcurrentStreams` {number} The maximum number of concurrent HTTP/2
|
|
131
|
+
streams for a single session. Once h2 is negotiated this — not `pipelining`,
|
|
132
|
+
which is HTTP/1.1 only — is the ceiling used to dispatch in-flight requests.
|
|
133
|
+
It may be overridden by the server's `SETTINGS_MAX_CONCURRENT_STREAMS`
|
|
134
|
+
frame. **Default:** `100`.
|
|
135
|
+
* `connectionWindowSize` {number} The HTTP/2 connection-level flow-control
|
|
136
|
+
window size set via `ClientHttp2Session.setLocalWindowSize()`. Must be a
|
|
137
|
+
positive integer. **Default:** `524288`.
|
|
138
|
+
* `pingInterval` {number} The time interval, in milliseconds, between HTTP/2
|
|
139
|
+
PING frames. Set to `0` to disable PING frames. Applies only to HTTP/2
|
|
140
|
+
connections and emits a `ping` event on the client. **Default:** `60e3`.
|
|
141
|
+
* `settings` {object} `SETTINGS` frame options. For full reference, take a
|
|
142
|
+
look to [HTTP/2#Settings Object](https://nodejs.org/api/http2.html#settings-object)
|
|
130
143
|
* `webSocket` {Object} (optional) WebSocket-specific configuration.
|
|
131
144
|
* `maxFragments` {number} The maximum number of fragments in a message. Set
|
|
132
145
|
to `0` to disable the limit. **Default:** `131072`.
|
|
@@ -134,6 +147,10 @@ added: v1.0.0
|
|
|
134
147
|
WebSocket messages. Applied to uncompressed messages, compressed frame
|
|
135
148
|
payloads, and decompressed (`permessage-deflate`) messages. Set to `0` to
|
|
136
149
|
disable the limit. **Default:** `134217728`.
|
|
150
|
+
* `eventSource` {Object} (optional) EventSource-specific configuration.
|
|
151
|
+
* `maxEventSize` {number} The maximum allowed event size, in bytes, for
|
|
152
|
+
EventSource messages. Set to `0` to disable the limit.
|
|
153
|
+
**Default:** `buffer.kStringMaxLength`.
|
|
137
154
|
* Returns: {Client}
|
|
138
155
|
|
|
139
156
|
Instantiating a `Client` does not open a connection; the connection is
|
|
@@ -25,8 +25,9 @@ it is used only for HTTPS requests.
|
|
|
25
25
|
proxied. Each entry may include a leading dot or `*.` wildcard (for example
|
|
26
26
|
`.example.com`) to match subdomains, and an optional `:port` suffix to restrict
|
|
27
27
|
the match to a specific port. A request bypasses the proxy when its host equals
|
|
28
|
-
an entry or is a subdomain of one.
|
|
29
|
-
|
|
28
|
+
an entry or is a subdomain of one. A trailing dot is ignored on both sides, so
|
|
29
|
+
`example.com.` and `example.com` match each other. Setting `no_proxy` to `*`
|
|
30
|
+
bypasses the proxy for every request.
|
|
30
31
|
|
|
31
32
|
The uppercase variants `HTTP_PROXY`, `HTTPS_PROXY`, and `NO_PROXY` are also
|
|
32
33
|
honored. When both the lowercase and uppercase forms of a variable are set, the
|
|
@@ -66,6 +66,9 @@ added: v6.5.0
|
|
|
66
66
|
wait before re-establishing a dropped connection. The server may override
|
|
67
67
|
this value with a `retry` field. **Default:** `3000`.
|
|
68
68
|
|
|
69
|
+
EventSource-specific limits can be configured on the dispatcher using the
|
|
70
|
+
`eventSource` option. See [`Client`][] for details.
|
|
71
|
+
|
|
69
72
|
Creates a new `EventSource` and immediately begins connecting to `url`. The
|
|
70
73
|
request is sent with the `Accept: text/event-stream` header, a cache mode of
|
|
71
74
|
`no-store`, and an initiator type of `other`.
|
|
@@ -349,6 +352,7 @@ eventSource.onerror = () => {
|
|
|
349
352
|
```
|
|
350
353
|
|
|
351
354
|
[WHATWG-conformant]: https://html.spec.whatwg.org/multipage/server-sent-events.html#server-sent-events
|
|
355
|
+
[`Client`]: Client.md#new-clienturl-options
|
|
352
356
|
[`Dispatcher`]: Dispatcher.md#class-dispatcher
|
|
353
357
|
[`addEventListener()`]: https://developer.mozilla.org/en-US/docs/Web/API/EventTarget/addEventListener
|
|
354
358
|
[server-sent events]: https://developer.mozilla.org/en-US/docs/Web/API/Server-sent_events/Using_server-sent_events
|
package/lib/api/readable.js
CHANGED
|
@@ -15,7 +15,6 @@ const kContentType = Symbol('kContentType')
|
|
|
15
15
|
const kContentLength = Symbol('kContentLength')
|
|
16
16
|
const kUsed = Symbol('kUsed')
|
|
17
17
|
const kBytesRead = Symbol('kBytesRead')
|
|
18
|
-
const kPreservedBuffer = Symbol('kPreservedBuffer')
|
|
19
18
|
|
|
20
19
|
const noop = () => {}
|
|
21
20
|
|
|
@@ -326,36 +325,14 @@ class BodyReadable extends Readable {
|
|
|
326
325
|
*/
|
|
327
326
|
setEncoding (encoding) {
|
|
328
327
|
if (Buffer.isEncoding(encoding)) {
|
|
329
|
-
// Preserve raw Buffer chunks for the consume path (body.text(),
|
|
330
|
-
// body.json(), etc.) before super.setEncoding() replaces them
|
|
331
|
-
// with decoded strings. Without this, the consume path would
|
|
332
|
-
// lose access to the original bytes — some of which may be held
|
|
333
|
-
// by the decoder for incomplete multi-byte sequences, and the
|
|
334
|
-
// rest converted to strings that can't be safely concatenated
|
|
335
|
-
// byte-wise.
|
|
336
|
-
const state = this._readableState
|
|
337
|
-
const buffer = state.buffer
|
|
338
|
-
if (buffer && state.length > 0) {
|
|
339
|
-
const bufferIndex = state.bufferIndex ?? 0
|
|
340
|
-
const preserved = []
|
|
341
|
-
const source = typeof buffer.slice === 'function'
|
|
342
|
-
? buffer.slice(bufferIndex)
|
|
343
|
-
: buffer
|
|
344
|
-
for (const data of source) {
|
|
345
|
-
if (Buffer.isBuffer(data)) {
|
|
346
|
-
preserved.push(data)
|
|
347
|
-
}
|
|
348
|
-
}
|
|
349
|
-
if (preserved.length > 0) {
|
|
350
|
-
this[kPreservedBuffer] = (this[kPreservedBuffer] || []).concat(preserved)
|
|
351
|
-
}
|
|
352
|
-
}
|
|
353
|
-
|
|
354
328
|
// Delegate to Node.js Readable.setEncoding() which initializes a
|
|
355
329
|
// StringDecoder and re-encodes already-buffered chunks. This properly
|
|
356
330
|
// handles multi-byte sequences split at chunk boundaries for the
|
|
357
331
|
// for-await / on('data') paths. Without this, Node.js uses
|
|
358
332
|
// buf.toString(encoding) on each chunk, producing U+FFFD for split chars.
|
|
333
|
+
//
|
|
334
|
+
// The consume path (body.text(), body.json(), ...) copes with the
|
|
335
|
+
// decoded strings this leaves in state.buffer, see consumeStart().
|
|
359
336
|
super.setEncoding(encoding)
|
|
360
337
|
}
|
|
361
338
|
return this
|
|
@@ -464,17 +441,7 @@ function consumeStart (consume) {
|
|
|
464
441
|
|
|
465
442
|
const { _readableState: state } = consume.stream
|
|
466
443
|
|
|
467
|
-
|
|
468
|
-
// (which would break Buffer.concat in chunksDecode). Use the preserved
|
|
469
|
-
// raw Buffers (saved before super.setEncoding() in setEncoding()) for
|
|
470
|
-
// byte-level accurate consumption. Otherwise read from state.buffer.
|
|
471
|
-
const preserved = consume.stream[kPreservedBuffer]
|
|
472
|
-
if (preserved && preserved.length > 0) {
|
|
473
|
-
for (const chunk of preserved) {
|
|
474
|
-
consumePush(consume, chunk)
|
|
475
|
-
}
|
|
476
|
-
consume.stream[kPreservedBuffer] = null
|
|
477
|
-
} else if (state.bufferIndex) {
|
|
444
|
+
if (state.bufferIndex) {
|
|
478
445
|
const start = state.bufferIndex
|
|
479
446
|
const end = state.buffer.length
|
|
480
447
|
for (let n = start; n < end; n++) {
|
|
@@ -486,14 +453,29 @@ function consumeStart (consume) {
|
|
|
486
453
|
}
|
|
487
454
|
}
|
|
488
455
|
|
|
456
|
+
// If setEncoding() was called, state.buffer holds decoded strings, which
|
|
457
|
+
// consumePush() turns back into bytes. The trailing bytes of a multi-byte
|
|
458
|
+
// sequence split across a chunk boundary are not part of any of those
|
|
459
|
+
// strings, they are held inside the decoder until the rest arrives, so
|
|
460
|
+
// take them from there.
|
|
461
|
+
const decoder = state.decoder
|
|
462
|
+
if (decoder != null && decoder.lastNeed > 0) {
|
|
463
|
+
consumePush(consume, Buffer.from(decoder.lastChar.subarray(0, decoder.lastTotal - decoder.lastNeed)))
|
|
464
|
+
}
|
|
465
|
+
|
|
489
466
|
if (state.endEmitted) {
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
467
|
+
// No `this` to read the consume off here: consumeStart is a free function, called from
|
|
468
|
+
// the queueMicrotask above. The callback below does have one, because the emitter passes
|
|
469
|
+
// the stream as its receiver. Returning matters too - consumeEnd() clears consume.stream,
|
|
470
|
+
// which the resume() below would then dereference.
|
|
471
|
+
consumeEnd(consume, state.encoding)
|
|
472
|
+
return
|
|
495
473
|
}
|
|
496
474
|
|
|
475
|
+
consume.stream.on('end', function () {
|
|
476
|
+
consumeEnd(this[kConsume], this._readableState.encoding)
|
|
477
|
+
})
|
|
478
|
+
|
|
497
479
|
consume.stream.resume()
|
|
498
480
|
|
|
499
481
|
while (consume.stream.read() != null) {
|
|
@@ -583,7 +565,7 @@ function consumeEnd (consume, encoding) {
|
|
|
583
565
|
|
|
584
566
|
/**
|
|
585
567
|
* @param {Consume} consume
|
|
586
|
-
* @param {Buffer} chunk
|
|
568
|
+
* @param {Buffer|string} chunk
|
|
587
569
|
* @returns {void}
|
|
588
570
|
*/
|
|
589
571
|
function consumePush (consume, chunk) {
|
|
@@ -591,6 +573,14 @@ function consumePush (consume, chunk) {
|
|
|
591
573
|
return
|
|
592
574
|
}
|
|
593
575
|
|
|
576
|
+
if (typeof chunk === 'string') {
|
|
577
|
+
// Buffered before the consume started, while an encoding was set.
|
|
578
|
+
// consume.length has to stay a byte count and chunksDecode()/chunksConcat()
|
|
579
|
+
// only work on bytes, so re-encode. A string's own length is in UTF-16 code
|
|
580
|
+
// units and Uint8Array.prototype.set() ignores a string argument entirely.
|
|
581
|
+
chunk = Buffer.from(chunk, consume.stream._readableState.encoding)
|
|
582
|
+
}
|
|
583
|
+
|
|
594
584
|
consume.length += chunk.length
|
|
595
585
|
consume.body.push(chunk)
|
|
596
586
|
}
|
|
@@ -179,7 +179,7 @@ class MemoryCacheStore extends EventEmitter {
|
|
|
179
179
|
|
|
180
180
|
// Perform eviction
|
|
181
181
|
for (const [key, entries] of store.#entries) {
|
|
182
|
-
for (const entry of entries.splice(0, entries.length / 2)) {
|
|
182
|
+
for (const entry of entries.splice(0, Math.ceil(entries.length / 2))) {
|
|
183
183
|
store.#size -= entry.size
|
|
184
184
|
store.#count -= 1
|
|
185
185
|
}
|
package/lib/core/connect.js
CHANGED
|
@@ -105,13 +105,27 @@ function buildConnector ({ allowH2, preferH2, useH2c, maxCachedSessions, socketP
|
|
|
105
105
|
|
|
106
106
|
port = port || 80
|
|
107
107
|
|
|
108
|
-
|
|
108
|
+
const connectOptions = {
|
|
109
109
|
highWaterMark: 64 * 1024, // Same as nodejs fs streams.
|
|
110
110
|
...options,
|
|
111
111
|
localAddress,
|
|
112
112
|
port,
|
|
113
113
|
host: hostname
|
|
114
|
-
}
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
const family = net.isIP(hostname)
|
|
117
|
+
if (family !== 0 && servername && servername !== hostname) {
|
|
118
|
+
connectOptions.host = servername
|
|
119
|
+
connectOptions.lookup = (_hostname, lookupOptions, cb) => {
|
|
120
|
+
if (lookupOptions.all) {
|
|
121
|
+
cb(null, [{ address: hostname, family }])
|
|
122
|
+
} else {
|
|
123
|
+
cb(null, hostname, family)
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
socket = net.connect(connectOptions)
|
|
115
129
|
if (useH2c === true) {
|
|
116
130
|
socket.alpnProtocol = 'h2'
|
|
117
131
|
}
|
package/lib/core/symbols.js
CHANGED
|
@@ -56,6 +56,7 @@ module.exports = {
|
|
|
56
56
|
kCounter: Symbol('socket request counter'),
|
|
57
57
|
kMaxResponseSize: Symbol('max response size'),
|
|
58
58
|
kHTTP2Session: Symbol('http2Session'),
|
|
59
|
+
kHTTP2Options: Symbol('http2 options'),
|
|
59
60
|
kHTTP2SessionState: Symbol('http2Session state'),
|
|
60
61
|
kRetryHandlerDefaultRetry: Symbol('retry agent default retry'),
|
|
61
62
|
kConstruct: Symbol('constructable'),
|
package/lib/dispatcher/agent.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
'use strict'
|
|
2
2
|
|
|
3
3
|
const { InvalidArgumentError, MaxOriginsReachedError } = require('../core/errors')
|
|
4
|
-
const { kBusy, kClients, kConnected, kRunning, kClose, kDestroy, kDispatch, kUrl } = require('../core/symbols')
|
|
4
|
+
const { kBusy, kClients, kConnected, kRunning, kPending, kClose, kDestroy, kDispatch, kUrl } = require('../core/symbols')
|
|
5
5
|
const DispatcherBase = require('./dispatcher-base')
|
|
6
6
|
const Pool = require('./pool')
|
|
7
7
|
const Client = require('./client')
|
|
@@ -97,7 +97,12 @@ class Agent extends DispatcherBase {
|
|
|
97
97
|
return
|
|
98
98
|
}
|
|
99
99
|
|
|
100
|
-
|
|
100
|
+
// A GOAWAY detaches the HTTP/2 session before requeued requests are
|
|
101
|
+
// dispatched on a replacement connection. At that point the pool has
|
|
102
|
+
// no connected clients and is not busy, but it still has pending work.
|
|
103
|
+
// Closing it here lets the replacement Client finish those requests
|
|
104
|
+
// and then destroys that new connection with ClientDestroyedError.
|
|
105
|
+
if (dispatcher[kConnected] > 0 || dispatcher[kBusy] || dispatcher[kPending] > 0) {
|
|
101
106
|
return
|
|
102
107
|
}
|
|
103
108
|
|
|
@@ -1061,6 +1061,15 @@ function clearIdleSocketValidation (socket) {
|
|
|
1061
1061
|
|
|
1062
1062
|
function scheduleIdleSocketValidation (client, socket) {
|
|
1063
1063
|
socket[kIdleSocketValidation] = 1
|
|
1064
|
+
// Yield to the check phase (after poll) so unsolicited bytes / FIN / RST
|
|
1065
|
+
// already pending on this idle keep-alive socket are processed before the
|
|
1066
|
+
// next request is written (GHSA-35p6-xmwp-9g52).
|
|
1067
|
+
//
|
|
1068
|
+
// setTimeout(0) pays Node's ~1ms timer floor on every sequential reuse
|
|
1069
|
+
// (#5493). setImmediate avoids that, but an *unref'd* Immediate lets poll
|
|
1070
|
+
// block for ~500ms when the event loop is otherwise idle (#5600 / #5606).
|
|
1071
|
+
// A ref'd Immediate both keeps the pending request alive and makes poll
|
|
1072
|
+
// return immediately — the hybrid those issues asked for.
|
|
1064
1073
|
socket[kIdleSocketValidationTimeout] = setImmediate(() => {
|
|
1065
1074
|
socket[kIdleSocketValidationTimeout] = null
|
|
1066
1075
|
socket[kIdleSocketValidation] = 2
|
|
@@ -1069,7 +1078,6 @@ function scheduleIdleSocketValidation (client, socket) {
|
|
|
1069
1078
|
client[kResume]()
|
|
1070
1079
|
}
|
|
1071
1080
|
})
|
|
1072
|
-
socket[kIdleSocketValidationTimeout].unref?.()
|
|
1073
1081
|
}
|
|
1074
1082
|
|
|
1075
1083
|
/**
|