undici 7.9.0 → 7.10.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/docs/docs/api/CacheStore.md +22 -2
- package/docs/docs/api/Pool.md +1 -0
- package/docs/docs/api/ProxyAgent.md +1 -0
- package/lib/cache/memory-cache-store.js +39 -1
- package/lib/dispatcher/agent.js +25 -15
- package/lib/dispatcher/pool.js +17 -3
- package/lib/dispatcher/proxy-agent.js +88 -2
- package/lib/mock/mock-agent.js +8 -8
- package/package.json +1 -1
- package/types/pool.d.ts +2 -0
- package/types/proxy-agent.d.ts +1 -0
|
@@ -13,8 +13,28 @@ The `MemoryCacheStore` stores the responses in-memory.
|
|
|
13
13
|
|
|
14
14
|
**Options**
|
|
15
15
|
|
|
16
|
+
- `maxSize` - The maximum total size in bytes of all stored responses. Default `Infinity`.
|
|
16
17
|
- `maxCount` - The maximum amount of responses to store. Default `Infinity`.
|
|
17
|
-
- `maxEntrySize` - The maximum size in bytes that a response's body can be. If a response's body is greater than or equal to this, the response will not be cached.
|
|
18
|
+
- `maxEntrySize` - The maximum size in bytes that a response's body can be. If a response's body is greater than or equal to this, the response will not be cached. Default `Infinity`.
|
|
19
|
+
|
|
20
|
+
### Getters
|
|
21
|
+
|
|
22
|
+
#### `MemoryCacheStore.size`
|
|
23
|
+
|
|
24
|
+
Returns the current total size in bytes of all stored responses.
|
|
25
|
+
|
|
26
|
+
### Methods
|
|
27
|
+
|
|
28
|
+
#### `MemoryCacheStore.isFull()`
|
|
29
|
+
|
|
30
|
+
Returns a boolean indicating whether the cache has reached its maximum size or count.
|
|
31
|
+
|
|
32
|
+
### Events
|
|
33
|
+
|
|
34
|
+
#### `'maxSizeExceeded'`
|
|
35
|
+
|
|
36
|
+
Emitted when the cache exceeds its maximum size or count limits. The event payload contains `size`, `maxSize`, `count`, and `maxCount` properties.
|
|
37
|
+
|
|
18
38
|
|
|
19
39
|
### `SqliteCacheStore`
|
|
20
40
|
|
|
@@ -26,7 +46,7 @@ The `SqliteCacheStore` is only exposed if the `node:sqlite` api is present.
|
|
|
26
46
|
|
|
27
47
|
- `location` - The location of the SQLite database to use. Default `:memory:`.
|
|
28
48
|
- `maxCount` - The maximum number of entries to store in the database. Default `Infinity`.
|
|
29
|
-
- `maxEntrySize` - The maximum size in bytes that a
|
|
49
|
+
- `maxEntrySize` - The maximum size in bytes that a response's body can be. If a response's body is greater than or equal to this, the response will not be cached. Default `Infinity`.
|
|
30
50
|
|
|
31
51
|
## Defining a Custom Cache Store
|
|
32
52
|
|
package/docs/docs/api/Pool.md
CHANGED
|
@@ -19,6 +19,7 @@ Extends: [`ClientOptions`](/docs/docs/api/Client.md#parameter-clientoptions)
|
|
|
19
19
|
|
|
20
20
|
* **factory** `(origin: URL, opts: Object) => Dispatcher` - Default: `(origin, opts) => new Client(origin, opts)`
|
|
21
21
|
* **connections** `number | null` (optional) - Default: `null` - The number of `Client` instances to create. When set to `null`, the `Pool` instance will create an unlimited amount of `Client` instances.
|
|
22
|
+
* **clientTtl** `number | null` (optional) - Default: `null` - The amount of time before a `Client` instance is removed from the `Pool` and closed. When set to `null`, `Client` instances will not be removed or closed based on age.
|
|
22
23
|
|
|
23
24
|
## Instance Properties
|
|
24
25
|
|
|
@@ -25,6 +25,7 @@ For detailed information on the parsing process and potential validation errors,
|
|
|
25
25
|
* **clientFactory** `(origin: URL, opts: Object) => Dispatcher` (optional) - Default: `(origin, opts) => new Pool(origin, opts)`
|
|
26
26
|
* **requestTls** `BuildOptions` (optional) - Options object passed when creating the underlying socket via the connector builder for the request. It extends from [`Client#ConnectOptions`](/docs/docs/api/Client.md#parameter-connectoptions).
|
|
27
27
|
* **proxyTls** `BuildOptions` (optional) - Options object passed when creating the underlying socket via the connector builder for the proxy server. It extends from [`Client#ConnectOptions`](/docs/docs/api/Client.md#parameter-connectoptions).
|
|
28
|
+
* **proxyTunnel** `boolean` (optional) - By default, ProxyAgent will request that the Proxy facilitate a tunnel between the endpoint and the agent. Setting `proxyTunnel` to false avoids issuing a CONNECT extension, and includes the endpoint domain and path in each request.
|
|
28
29
|
|
|
29
30
|
Examples:
|
|
30
31
|
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
'use strict'
|
|
2
2
|
|
|
3
3
|
const { Writable } = require('node:stream')
|
|
4
|
+
const { EventEmitter } = require('node:events')
|
|
4
5
|
const { assertCacheKey, assertCacheValue } = require('../util/cache.js')
|
|
5
6
|
|
|
6
7
|
/**
|
|
@@ -12,8 +13,9 @@ const { assertCacheKey, assertCacheValue } = require('../util/cache.js')
|
|
|
12
13
|
|
|
13
14
|
/**
|
|
14
15
|
* @implements {CacheStore}
|
|
16
|
+
* @extends {EventEmitter}
|
|
15
17
|
*/
|
|
16
|
-
class MemoryCacheStore {
|
|
18
|
+
class MemoryCacheStore extends EventEmitter {
|
|
17
19
|
#maxCount = Infinity
|
|
18
20
|
#maxSize = Infinity
|
|
19
21
|
#maxEntrySize = Infinity
|
|
@@ -21,11 +23,13 @@ class MemoryCacheStore {
|
|
|
21
23
|
#size = 0
|
|
22
24
|
#count = 0
|
|
23
25
|
#entries = new Map()
|
|
26
|
+
#hasEmittedMaxSizeEvent = false
|
|
24
27
|
|
|
25
28
|
/**
|
|
26
29
|
* @param {import('../../types/cache-interceptor.d.ts').default.MemoryCacheStoreOpts | undefined} [opts]
|
|
27
30
|
*/
|
|
28
31
|
constructor (opts) {
|
|
32
|
+
super()
|
|
29
33
|
if (opts) {
|
|
30
34
|
if (typeof opts !== 'object') {
|
|
31
35
|
throw new TypeError('MemoryCacheStore options must be an object')
|
|
@@ -66,6 +70,22 @@ class MemoryCacheStore {
|
|
|
66
70
|
}
|
|
67
71
|
}
|
|
68
72
|
|
|
73
|
+
/**
|
|
74
|
+
* Get the current size of the cache in bytes
|
|
75
|
+
* @returns {number} The current size of the cache in bytes
|
|
76
|
+
*/
|
|
77
|
+
get size () {
|
|
78
|
+
return this.#size
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* Check if the cache is full (either max size or max count reached)
|
|
83
|
+
* @returns {boolean} True if the cache is full, false otherwise
|
|
84
|
+
*/
|
|
85
|
+
isFull () {
|
|
86
|
+
return this.#size >= this.#maxSize || this.#count >= this.#maxCount
|
|
87
|
+
}
|
|
88
|
+
|
|
69
89
|
/**
|
|
70
90
|
* @param {import('../../types/cache-interceptor.d.ts').default.CacheKey} req
|
|
71
91
|
* @returns {import('../../types/cache-interceptor.d.ts').default.GetResult | undefined}
|
|
@@ -144,7 +164,20 @@ class MemoryCacheStore {
|
|
|
144
164
|
|
|
145
165
|
store.#size += entry.size
|
|
146
166
|
|
|
167
|
+
// Check if cache is full and emit event if needed
|
|
147
168
|
if (store.#size > store.#maxSize || store.#count > store.#maxCount) {
|
|
169
|
+
// Emit maxSizeExceeded event if we haven't already
|
|
170
|
+
if (!store.#hasEmittedMaxSizeEvent) {
|
|
171
|
+
store.emit('maxSizeExceeded', {
|
|
172
|
+
size: store.#size,
|
|
173
|
+
maxSize: store.#maxSize,
|
|
174
|
+
count: store.#count,
|
|
175
|
+
maxCount: store.#maxCount
|
|
176
|
+
})
|
|
177
|
+
store.#hasEmittedMaxSizeEvent = true
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
// Perform eviction
|
|
148
181
|
for (const [key, entries] of store.#entries) {
|
|
149
182
|
for (const entry of entries.splice(0, entries.length / 2)) {
|
|
150
183
|
store.#size -= entry.size
|
|
@@ -154,6 +187,11 @@ class MemoryCacheStore {
|
|
|
154
187
|
store.#entries.delete(key)
|
|
155
188
|
}
|
|
156
189
|
}
|
|
190
|
+
|
|
191
|
+
// Reset the event flag after eviction
|
|
192
|
+
if (store.#size < store.#maxSize && store.#count < store.#maxCount) {
|
|
193
|
+
store.#hasEmittedMaxSizeEvent = false
|
|
194
|
+
}
|
|
157
195
|
}
|
|
158
196
|
|
|
159
197
|
callback(null)
|
package/lib/dispatcher/agent.js
CHANGED
|
@@ -45,22 +45,35 @@ class Agent extends DispatcherBase {
|
|
|
45
45
|
}
|
|
46
46
|
|
|
47
47
|
this[kOnConnect] = (origin, targets) => {
|
|
48
|
+
const result = this[kClients].get(origin)
|
|
49
|
+
if (result) {
|
|
50
|
+
result.count += 1
|
|
51
|
+
}
|
|
48
52
|
this.emit('connect', origin, [this, ...targets])
|
|
49
53
|
}
|
|
50
54
|
|
|
51
55
|
this[kOnDisconnect] = (origin, targets, err) => {
|
|
56
|
+
const result = this[kClients].get(origin)
|
|
57
|
+
if (result) {
|
|
58
|
+
result.count -= 1
|
|
59
|
+
if (result.count <= 0) {
|
|
60
|
+
this[kClients].delete(origin)
|
|
61
|
+
result.dispatcher.destroy()
|
|
62
|
+
}
|
|
63
|
+
}
|
|
52
64
|
this.emit('disconnect', origin, [this, ...targets], err)
|
|
53
65
|
}
|
|
54
66
|
|
|
55
67
|
this[kOnConnectionError] = (origin, targets, err) => {
|
|
68
|
+
// TODO: should this decrement result.count here?
|
|
56
69
|
this.emit('connectionError', origin, [this, ...targets], err)
|
|
57
70
|
}
|
|
58
71
|
}
|
|
59
72
|
|
|
60
73
|
get [kRunning] () {
|
|
61
74
|
let ret = 0
|
|
62
|
-
for (const
|
|
63
|
-
ret +=
|
|
75
|
+
for (const { dispatcher } of this[kClients].values()) {
|
|
76
|
+
ret += dispatcher[kRunning]
|
|
64
77
|
}
|
|
65
78
|
return ret
|
|
66
79
|
}
|
|
@@ -73,8 +86,8 @@ class Agent extends DispatcherBase {
|
|
|
73
86
|
throw new InvalidArgumentError('opts.origin must be a non-empty string or URL.')
|
|
74
87
|
}
|
|
75
88
|
|
|
76
|
-
|
|
77
|
-
|
|
89
|
+
const result = this[kClients].get(key)
|
|
90
|
+
let dispatcher = result && result.dispatcher
|
|
78
91
|
if (!dispatcher) {
|
|
79
92
|
dispatcher = this[kFactory](opts.origin, this[kOptions])
|
|
80
93
|
.on('drain', this[kOnDrain])
|
|
@@ -82,10 +95,7 @@ class Agent extends DispatcherBase {
|
|
|
82
95
|
.on('disconnect', this[kOnDisconnect])
|
|
83
96
|
.on('connectionError', this[kOnConnectionError])
|
|
84
97
|
|
|
85
|
-
|
|
86
|
-
// TODO(mcollina): remove te timer when the client/pool do not have any more
|
|
87
|
-
// active connections.
|
|
88
|
-
this[kClients].set(key, dispatcher)
|
|
98
|
+
this[kClients].set(key, { count: 0, dispatcher })
|
|
89
99
|
}
|
|
90
100
|
|
|
91
101
|
return dispatcher.dispatch(opts, handler)
|
|
@@ -93,8 +103,8 @@ class Agent extends DispatcherBase {
|
|
|
93
103
|
|
|
94
104
|
async [kClose] () {
|
|
95
105
|
const closePromises = []
|
|
96
|
-
for (const
|
|
97
|
-
closePromises.push(
|
|
106
|
+
for (const { dispatcher } of this[kClients].values()) {
|
|
107
|
+
closePromises.push(dispatcher.close())
|
|
98
108
|
}
|
|
99
109
|
this[kClients].clear()
|
|
100
110
|
|
|
@@ -103,8 +113,8 @@ class Agent extends DispatcherBase {
|
|
|
103
113
|
|
|
104
114
|
async [kDestroy] (err) {
|
|
105
115
|
const destroyPromises = []
|
|
106
|
-
for (const
|
|
107
|
-
destroyPromises.push(
|
|
116
|
+
for (const { dispatcher } of this[kClients].values()) {
|
|
117
|
+
destroyPromises.push(dispatcher.destroy(err))
|
|
108
118
|
}
|
|
109
119
|
this[kClients].clear()
|
|
110
120
|
|
|
@@ -113,9 +123,9 @@ class Agent extends DispatcherBase {
|
|
|
113
123
|
|
|
114
124
|
get stats () {
|
|
115
125
|
const allClientStats = {}
|
|
116
|
-
for (const
|
|
117
|
-
if (
|
|
118
|
-
allClientStats[
|
|
126
|
+
for (const { dispatcher } of this[kClients].values()) {
|
|
127
|
+
if (dispatcher.stats) {
|
|
128
|
+
allClientStats[dispatcher[kUrl].origin] = dispatcher.stats
|
|
119
129
|
}
|
|
120
130
|
}
|
|
121
131
|
return allClientStats
|
package/lib/dispatcher/pool.js
CHANGED
|
@@ -5,7 +5,8 @@ const {
|
|
|
5
5
|
kClients,
|
|
6
6
|
kNeedDrain,
|
|
7
7
|
kAddClient,
|
|
8
|
-
kGetDispatcher
|
|
8
|
+
kGetDispatcher,
|
|
9
|
+
kRemoveClient
|
|
9
10
|
} = require('./pool-base')
|
|
10
11
|
const Client = require('./client')
|
|
11
12
|
const {
|
|
@@ -35,6 +36,7 @@ class Pool extends PoolBase {
|
|
|
35
36
|
autoSelectFamily,
|
|
36
37
|
autoSelectFamilyAttemptTimeout,
|
|
37
38
|
allowH2,
|
|
39
|
+
clientTtl,
|
|
38
40
|
...options
|
|
39
41
|
} = {}) {
|
|
40
42
|
if (connections != null && (!Number.isFinite(connections) || connections < 0)) {
|
|
@@ -65,12 +67,20 @@ class Pool extends PoolBase {
|
|
|
65
67
|
|
|
66
68
|
this[kConnections] = connections || null
|
|
67
69
|
this[kUrl] = util.parseOrigin(origin)
|
|
68
|
-
this[kOptions] = { ...util.deepClone(options), connect, allowH2 }
|
|
70
|
+
this[kOptions] = { ...util.deepClone(options), connect, allowH2, clientTtl }
|
|
69
71
|
this[kOptions].interceptors = options.interceptors
|
|
70
72
|
? { ...options.interceptors }
|
|
71
73
|
: undefined
|
|
72
74
|
this[kFactory] = factory
|
|
73
75
|
|
|
76
|
+
this.on('connect', (origin, targets) => {
|
|
77
|
+
if (clientTtl != null && clientTtl > 0) {
|
|
78
|
+
for (const target of targets) {
|
|
79
|
+
Object.assign(target, { ttl: Date.now() })
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
})
|
|
83
|
+
|
|
74
84
|
this.on('connectionError', (origin, targets, error) => {
|
|
75
85
|
// If a connection error occurs, we remove the client from the pool,
|
|
76
86
|
// and emit a connectionError event. They will not be re-used.
|
|
@@ -87,8 +97,12 @@ class Pool extends PoolBase {
|
|
|
87
97
|
}
|
|
88
98
|
|
|
89
99
|
[kGetDispatcher] () {
|
|
100
|
+
const clientTtlOption = this[kOptions].clientTtl
|
|
90
101
|
for (const client of this[kClients]) {
|
|
91
|
-
|
|
102
|
+
// check ttl of client and if it's stale, remove it from the pool
|
|
103
|
+
if (clientTtlOption != null && clientTtlOption > 0 && client.ttl && ((Date.now() - client.ttl) > clientTtlOption)) {
|
|
104
|
+
this[kRemoveClient](client)
|
|
105
|
+
} else if (!client[kNeedDrain]) {
|
|
92
106
|
return client
|
|
93
107
|
}
|
|
94
108
|
}
|
|
@@ -1,12 +1,13 @@
|
|
|
1
1
|
'use strict'
|
|
2
2
|
|
|
3
|
-
const { kProxy, kClose, kDestroy } = require('../core/symbols')
|
|
3
|
+
const { kProxy, kClose, kDestroy, kDispatch, kConnector } = require('../core/symbols')
|
|
4
4
|
const { URL } = require('node:url')
|
|
5
5
|
const Agent = require('./agent')
|
|
6
6
|
const Pool = require('./pool')
|
|
7
7
|
const DispatcherBase = require('./dispatcher-base')
|
|
8
8
|
const { InvalidArgumentError, RequestAbortedError, SecureProxyConnectionError } = require('../core/errors')
|
|
9
9
|
const buildConnector = require('../core/connect')
|
|
10
|
+
const Client = require('./client')
|
|
10
11
|
|
|
11
12
|
const kAgent = Symbol('proxy agent')
|
|
12
13
|
const kClient = Symbol('proxy client')
|
|
@@ -14,6 +15,7 @@ const kProxyHeaders = Symbol('proxy headers')
|
|
|
14
15
|
const kRequestTls = Symbol('request tls settings')
|
|
15
16
|
const kProxyTls = Symbol('proxy tls settings')
|
|
16
17
|
const kConnectEndpoint = Symbol('connect endpoint function')
|
|
18
|
+
const kTunnelProxy = Symbol('tunnel proxy')
|
|
17
19
|
|
|
18
20
|
function defaultProtocolPort (protocol) {
|
|
19
21
|
return protocol === 'https:' ? 443 : 80
|
|
@@ -25,6 +27,61 @@ function defaultFactory (origin, opts) {
|
|
|
25
27
|
|
|
26
28
|
const noop = () => {}
|
|
27
29
|
|
|
30
|
+
class ProxyClient extends DispatcherBase {
|
|
31
|
+
#client = null
|
|
32
|
+
constructor (origin, opts) {
|
|
33
|
+
if (typeof origin === 'string') {
|
|
34
|
+
origin = new URL(origin)
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
if (origin.protocol !== 'http:' && origin.protocol !== 'https:') {
|
|
38
|
+
throw new InvalidArgumentError('ProxyClient only supports http and https protocols')
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
super()
|
|
42
|
+
|
|
43
|
+
this.#client = new Client(origin, opts)
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
async [kClose] () {
|
|
47
|
+
await this.#client.close()
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
async [kDestroy] () {
|
|
51
|
+
await this.#client.destroy()
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
async [kDispatch] (opts, handler) {
|
|
55
|
+
const { method, origin } = opts
|
|
56
|
+
if (method === 'CONNECT') {
|
|
57
|
+
this.#client[kConnector]({
|
|
58
|
+
origin,
|
|
59
|
+
port: opts.port || defaultProtocolPort(opts.protocol),
|
|
60
|
+
path: opts.host,
|
|
61
|
+
signal: opts.signal,
|
|
62
|
+
headers: {
|
|
63
|
+
...this[kProxyHeaders],
|
|
64
|
+
host: opts.host
|
|
65
|
+
},
|
|
66
|
+
servername: this[kProxyTls]?.servername || opts.servername
|
|
67
|
+
},
|
|
68
|
+
(err, socket) => {
|
|
69
|
+
if (err) {
|
|
70
|
+
handler.callback(err)
|
|
71
|
+
} else {
|
|
72
|
+
handler.callback(null, { socket, statusCode: 200 })
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
)
|
|
76
|
+
return
|
|
77
|
+
}
|
|
78
|
+
if (typeof origin === 'string') {
|
|
79
|
+
opts.origin = new URL(origin)
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
return this.#client.dispatch(opts, handler)
|
|
83
|
+
}
|
|
84
|
+
}
|
|
28
85
|
class ProxyAgent extends DispatcherBase {
|
|
29
86
|
constructor (opts) {
|
|
30
87
|
if (!opts || (typeof opts === 'object' && !(opts instanceof URL) && !opts.uri)) {
|
|
@@ -36,6 +93,8 @@ class ProxyAgent extends DispatcherBase {
|
|
|
36
93
|
throw new InvalidArgumentError('Proxy opts.clientFactory must be a function.')
|
|
37
94
|
}
|
|
38
95
|
|
|
96
|
+
const { proxyTunnel = true } = opts
|
|
97
|
+
|
|
39
98
|
super()
|
|
40
99
|
|
|
41
100
|
const url = this.#getUrl(opts)
|
|
@@ -57,9 +116,19 @@ class ProxyAgent extends DispatcherBase {
|
|
|
57
116
|
this[kProxyHeaders]['proxy-authorization'] = `Basic ${Buffer.from(`${decodeURIComponent(username)}:${decodeURIComponent(password)}`).toString('base64')}`
|
|
58
117
|
}
|
|
59
118
|
|
|
119
|
+
const factory = (!proxyTunnel && protocol === 'http:')
|
|
120
|
+
? (origin, options) => {
|
|
121
|
+
if (origin.protocol === 'http:') {
|
|
122
|
+
return new ProxyClient(origin, options)
|
|
123
|
+
}
|
|
124
|
+
return new Client(origin, options)
|
|
125
|
+
}
|
|
126
|
+
: undefined
|
|
127
|
+
|
|
60
128
|
const connect = buildConnector({ ...opts.proxyTls })
|
|
61
129
|
this[kConnectEndpoint] = buildConnector({ ...opts.requestTls })
|
|
62
|
-
this[kClient] = clientFactory(url, { connect })
|
|
130
|
+
this[kClient] = clientFactory(url, { connect, factory })
|
|
131
|
+
this[kTunnelProxy] = proxyTunnel
|
|
63
132
|
this[kAgent] = new Agent({
|
|
64
133
|
...opts,
|
|
65
134
|
connect: async (opts, callback) => {
|
|
@@ -115,6 +184,10 @@ class ProxyAgent extends DispatcherBase {
|
|
|
115
184
|
headers.host = host
|
|
116
185
|
}
|
|
117
186
|
|
|
187
|
+
if (!this.#shouldConnect(new URL(opts.origin))) {
|
|
188
|
+
opts.path = opts.origin + opts.path
|
|
189
|
+
}
|
|
190
|
+
|
|
118
191
|
return this[kAgent].dispatch(
|
|
119
192
|
{
|
|
120
193
|
...opts,
|
|
@@ -147,6 +220,19 @@ class ProxyAgent extends DispatcherBase {
|
|
|
147
220
|
await this[kAgent].destroy()
|
|
148
221
|
await this[kClient].destroy()
|
|
149
222
|
}
|
|
223
|
+
|
|
224
|
+
#shouldConnect (uri) {
|
|
225
|
+
if (typeof uri === 'string') {
|
|
226
|
+
uri = new URL(uri)
|
|
227
|
+
}
|
|
228
|
+
if (this[kTunnelProxy]) {
|
|
229
|
+
return true
|
|
230
|
+
}
|
|
231
|
+
if (uri.protocol !== 'http:' || this[kProxy].protocol !== 'http:') {
|
|
232
|
+
return true
|
|
233
|
+
}
|
|
234
|
+
return false
|
|
235
|
+
}
|
|
150
236
|
}
|
|
151
237
|
|
|
152
238
|
/**
|
package/lib/mock/mock-agent.js
CHANGED
|
@@ -159,7 +159,7 @@ class MockAgent extends Dispatcher {
|
|
|
159
159
|
}
|
|
160
160
|
|
|
161
161
|
[kMockAgentSet] (origin, dispatcher) {
|
|
162
|
-
this[kClients].set(origin, dispatcher)
|
|
162
|
+
this[kClients].set(origin, { count: 0, dispatcher })
|
|
163
163
|
}
|
|
164
164
|
|
|
165
165
|
[kFactory] (origin) {
|
|
@@ -171,9 +171,9 @@ class MockAgent extends Dispatcher {
|
|
|
171
171
|
|
|
172
172
|
[kMockAgentGet] (origin) {
|
|
173
173
|
// First check if we can immediately find it
|
|
174
|
-
const
|
|
175
|
-
if (
|
|
176
|
-
return
|
|
174
|
+
const result = this[kClients].get(origin)
|
|
175
|
+
if (result?.dispatcher) {
|
|
176
|
+
return result.dispatcher
|
|
177
177
|
}
|
|
178
178
|
|
|
179
179
|
// If the origin is not a string create a dummy parent pool and return to user
|
|
@@ -184,11 +184,11 @@ class MockAgent extends Dispatcher {
|
|
|
184
184
|
}
|
|
185
185
|
|
|
186
186
|
// If we match, create a pool and assign the same dispatches
|
|
187
|
-
for (const [keyMatcher,
|
|
188
|
-
if (
|
|
187
|
+
for (const [keyMatcher, result] of Array.from(this[kClients])) {
|
|
188
|
+
if (result && typeof keyMatcher !== 'string' && matchValue(keyMatcher, origin)) {
|
|
189
189
|
const dispatcher = this[kFactory](origin)
|
|
190
190
|
this[kMockAgentSet](origin, dispatcher)
|
|
191
|
-
dispatcher[kDispatches] =
|
|
191
|
+
dispatcher[kDispatches] = result.dispatcher[kDispatches]
|
|
192
192
|
return dispatcher
|
|
193
193
|
}
|
|
194
194
|
}
|
|
@@ -202,7 +202,7 @@ class MockAgent extends Dispatcher {
|
|
|
202
202
|
const mockAgentClients = this[kClients]
|
|
203
203
|
|
|
204
204
|
return Array.from(mockAgentClients.entries())
|
|
205
|
-
.flatMap(([origin,
|
|
205
|
+
.flatMap(([origin, result]) => result.dispatcher[kDispatches].map(dispatch => ({ ...dispatch, origin })))
|
|
206
206
|
.filter(({ pending }) => pending)
|
|
207
207
|
}
|
|
208
208
|
|
package/package.json
CHANGED
package/types/pool.d.ts
CHANGED
|
@@ -33,6 +33,8 @@ declare namespace Pool {
|
|
|
33
33
|
factory?(origin: URL, opts: object): Dispatcher;
|
|
34
34
|
/** The max number of clients to create. `null` if no limit. Default `null`. */
|
|
35
35
|
connections?: number | null;
|
|
36
|
+
/** The amount of time before a client is removed from the pool and closed. `null` if no time limit. Default `null` */
|
|
37
|
+
clientTtl?: number | null;
|
|
36
38
|
|
|
37
39
|
interceptors?: { Pool?: readonly Dispatcher.DispatchInterceptor[] } & Client.Options['interceptors']
|
|
38
40
|
}
|