undici 7.9.0 → 7.11.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/README.md +157 -0
- package/docs/docs/api/CacheStore.md +23 -3
- package/docs/docs/api/Debug.md +13 -13
- package/docs/docs/api/DiagnosticsChannel.md +25 -0
- package/docs/docs/api/Dispatcher.md +20 -1
- package/docs/docs/api/GlobalInstallation.md +91 -0
- package/docs/docs/api/MockClient.md +4 -0
- package/docs/docs/api/MockPool.md +6 -0
- package/docs/docs/api/Pool.md +1 -0
- package/docs/docs/api/ProxyAgent.md +3 -0
- package/docs/docs/api/RetryAgent.md +6 -1
- package/docs/docs/api/RetryHandler.md +1 -0
- package/index.js +15 -0
- package/lib/api/api-stream.js +1 -1
- package/lib/cache/memory-cache-store.js +42 -4
- package/lib/cache/sqlite-cache-store.js +1 -1
- package/lib/core/connect.js +21 -51
- package/lib/core/diagnostics.js +6 -4
- package/lib/core/request.js +6 -0
- package/lib/core/util.js +0 -45
- package/lib/dispatcher/agent.js +25 -15
- package/lib/dispatcher/client-h1.js +1 -1
- package/lib/dispatcher/pool.js +17 -3
- package/lib/dispatcher/proxy-agent.js +90 -3
- package/lib/handler/retry-handler.js +110 -56
- package/lib/mock/mock-agent.js +8 -8
- package/lib/mock/mock-client.js +4 -0
- package/lib/mock/mock-pool.js +4 -0
- package/lib/util/cache.js +11 -1
- package/lib/util/timers.js +11 -9
- package/lib/web/cache/cache.js +1 -1
- package/lib/web/cache/cachestorage.js +1 -1
- package/lib/web/cookies/index.js +1 -1
- package/lib/web/eventsource/eventsource.js +3 -6
- package/lib/web/eventsource/util.js +1 -1
- package/lib/web/fetch/body.js +2 -2
- package/lib/web/fetch/dispatcher-weakref.js +0 -41
- package/lib/web/fetch/formdata-parser.js +4 -4
- package/lib/web/fetch/formdata.js +1 -1
- package/lib/web/fetch/headers.js +1 -1
- package/lib/web/fetch/index.js +7 -1
- package/lib/web/fetch/request.js +1 -1
- package/lib/web/fetch/response.js +1 -1
- package/lib/web/fetch/util.js +2 -2
- package/lib/web/{fetch/webidl.js → webidl/index.js} +57 -9
- package/lib/web/websocket/connection.js +4 -3
- package/lib/web/websocket/events.js +1 -1
- package/lib/web/websocket/frame.js +2 -1
- package/lib/web/websocket/stream/websocketerror.js +1 -1
- package/lib/web/websocket/stream/websocketstream.js +1 -1
- package/lib/web/websocket/websocket.js +4 -4
- package/package.json +4 -4
- package/types/diagnostics-channel.d.ts +9 -0
- package/types/dispatcher.d.ts +3 -2
- package/types/env-http-proxy-agent.d.ts +2 -1
- package/types/eventsource.d.ts +3 -3
- package/types/fetch.d.ts +1 -0
- package/types/handlers.d.ts +1 -1
- package/types/mock-client.d.ts +2 -0
- package/types/mock-interceptor.d.ts +2 -0
- package/types/mock-pool.d.ts +2 -0
- package/types/pool.d.ts +2 -0
- package/types/proxy-agent.d.ts +1 -0
- package/types/retry-handler.d.ts +9 -0
- package/types/webidl.d.ts +19 -15
- package/types/websocket.d.ts +1 -1
package/lib/core/connect.js
CHANGED
|
@@ -12,64 +12,34 @@ let tls // include tls conditionally since it is not always available
|
|
|
12
12
|
// resolve the same servername multiple times even when
|
|
13
13
|
// re-use is enabled.
|
|
14
14
|
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
this._maxCachedSessions = maxCachedSessions
|
|
22
|
-
this._sessionCache = new Map()
|
|
23
|
-
this._sessionRegistry = new global.FinalizationRegistry((key) => {
|
|
24
|
-
if (this._sessionCache.size < this._maxCachedSessions) {
|
|
25
|
-
return
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
const ref = this._sessionCache.get(key)
|
|
29
|
-
if (ref !== undefined && ref.deref() === undefined) {
|
|
30
|
-
this._sessionCache.delete(key)
|
|
31
|
-
}
|
|
32
|
-
})
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
get (sessionKey) {
|
|
36
|
-
const ref = this._sessionCache.get(sessionKey)
|
|
37
|
-
return ref ? ref.deref() : null
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
set (sessionKey, session) {
|
|
41
|
-
if (this._maxCachedSessions === 0) {
|
|
15
|
+
const SessionCache = class WeakSessionCache {
|
|
16
|
+
constructor (maxCachedSessions) {
|
|
17
|
+
this._maxCachedSessions = maxCachedSessions
|
|
18
|
+
this._sessionCache = new Map()
|
|
19
|
+
this._sessionRegistry = new FinalizationRegistry((key) => {
|
|
20
|
+
if (this._sessionCache.size < this._maxCachedSessions) {
|
|
42
21
|
return
|
|
43
22
|
}
|
|
44
23
|
|
|
45
|
-
this._sessionCache.
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
}
|
|
49
|
-
} else {
|
|
50
|
-
SessionCache = class SimpleSessionCache {
|
|
51
|
-
constructor (maxCachedSessions) {
|
|
52
|
-
this._maxCachedSessions = maxCachedSessions
|
|
53
|
-
this._sessionCache = new Map()
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
get (sessionKey) {
|
|
57
|
-
return this._sessionCache.get(sessionKey)
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
set (sessionKey, session) {
|
|
61
|
-
if (this._maxCachedSessions === 0) {
|
|
62
|
-
return
|
|
24
|
+
const ref = this._sessionCache.get(key)
|
|
25
|
+
if (ref !== undefined && ref.deref() === undefined) {
|
|
26
|
+
this._sessionCache.delete(key)
|
|
63
27
|
}
|
|
28
|
+
})
|
|
29
|
+
}
|
|
64
30
|
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
}
|
|
31
|
+
get (sessionKey) {
|
|
32
|
+
const ref = this._sessionCache.get(sessionKey)
|
|
33
|
+
return ref ? ref.deref() : null
|
|
34
|
+
}
|
|
70
35
|
|
|
71
|
-
|
|
36
|
+
set (sessionKey, session) {
|
|
37
|
+
if (this._maxCachedSessions === 0) {
|
|
38
|
+
return
|
|
72
39
|
}
|
|
40
|
+
|
|
41
|
+
this._sessionCache.set(sessionKey, new WeakRef(session))
|
|
42
|
+
this._sessionRegistry.register(session, sessionKey)
|
|
73
43
|
}
|
|
74
44
|
}
|
|
75
45
|
|
package/lib/core/diagnostics.js
CHANGED
|
@@ -16,6 +16,8 @@ const channels = {
|
|
|
16
16
|
// Request
|
|
17
17
|
create: diagnosticsChannel.channel('undici:request:create'),
|
|
18
18
|
bodySent: diagnosticsChannel.channel('undici:request:bodySent'),
|
|
19
|
+
bodyChunkSent: diagnosticsChannel.channel('undici:request:bodyChunkSent'),
|
|
20
|
+
bodyChunkReceived: diagnosticsChannel.channel('undici:request:bodyChunkReceived'),
|
|
19
21
|
headers: diagnosticsChannel.channel('undici:request:headers'),
|
|
20
22
|
trailers: diagnosticsChannel.channel('undici:request:trailers'),
|
|
21
23
|
error: diagnosticsChannel.channel('undici:request:error'),
|
|
@@ -85,7 +87,7 @@ function trackClientEvents (debugLog = undiciDebugLog) {
|
|
|
85
87
|
const {
|
|
86
88
|
request: { method, path, origin }
|
|
87
89
|
} = evt
|
|
88
|
-
debugLog('sending request to %s %s
|
|
90
|
+
debugLog('sending request to %s %s%s', method, origin, path)
|
|
89
91
|
})
|
|
90
92
|
}
|
|
91
93
|
|
|
@@ -105,7 +107,7 @@ function trackRequestEvents (debugLog = undiciDebugLog) {
|
|
|
105
107
|
response: { statusCode }
|
|
106
108
|
} = evt
|
|
107
109
|
debugLog(
|
|
108
|
-
'received response to %s %s
|
|
110
|
+
'received response to %s %s%s - HTTP %d',
|
|
109
111
|
method,
|
|
110
112
|
origin,
|
|
111
113
|
path,
|
|
@@ -118,7 +120,7 @@ function trackRequestEvents (debugLog = undiciDebugLog) {
|
|
|
118
120
|
const {
|
|
119
121
|
request: { method, path, origin }
|
|
120
122
|
} = evt
|
|
121
|
-
debugLog('trailers received from %s %s
|
|
123
|
+
debugLog('trailers received from %s %s%s', method, origin, path)
|
|
122
124
|
})
|
|
123
125
|
|
|
124
126
|
diagnosticsChannel.subscribe('undici:request:error',
|
|
@@ -128,7 +130,7 @@ function trackRequestEvents (debugLog = undiciDebugLog) {
|
|
|
128
130
|
error
|
|
129
131
|
} = evt
|
|
130
132
|
debugLog(
|
|
131
|
-
'request to %s %s
|
|
133
|
+
'request to %s %s%s errored - %s',
|
|
132
134
|
method,
|
|
133
135
|
origin,
|
|
134
136
|
path,
|
package/lib/core/request.js
CHANGED
|
@@ -194,6 +194,9 @@ class Request {
|
|
|
194
194
|
}
|
|
195
195
|
|
|
196
196
|
onBodySent (chunk) {
|
|
197
|
+
if (channels.bodyChunkSent.hasSubscribers) {
|
|
198
|
+
channels.bodyChunkSent.publish({ request: this, chunk })
|
|
199
|
+
}
|
|
197
200
|
if (this[kHandler].onBodySent) {
|
|
198
201
|
try {
|
|
199
202
|
return this[kHandler].onBodySent(chunk)
|
|
@@ -252,6 +255,9 @@ class Request {
|
|
|
252
255
|
assert(!this.aborted)
|
|
253
256
|
assert(!this.completed)
|
|
254
257
|
|
|
258
|
+
if (channels.bodyChunkReceived.hasSubscribers) {
|
|
259
|
+
channels.bodyChunkReceived.publish({ request: this, chunk })
|
|
260
|
+
}
|
|
255
261
|
try {
|
|
256
262
|
return this[kHandler].onData(chunk)
|
|
257
263
|
} catch (err) {
|
package/lib/core/util.js
CHANGED
|
@@ -6,7 +6,6 @@ const { IncomingMessage } = require('node:http')
|
|
|
6
6
|
const stream = require('node:stream')
|
|
7
7
|
const net = require('node:net')
|
|
8
8
|
const { Blob } = require('node:buffer')
|
|
9
|
-
const nodeUtil = require('node:util')
|
|
10
9
|
const { stringify } = require('node:querystring')
|
|
11
10
|
const { EventEmitter: EE } = require('node:events')
|
|
12
11
|
const timers = require('../util/timers')
|
|
@@ -660,48 +659,6 @@ function addAbortListener (signal, listener) {
|
|
|
660
659
|
return () => signal.removeListener('abort', listener)
|
|
661
660
|
}
|
|
662
661
|
|
|
663
|
-
/**
|
|
664
|
-
* @function
|
|
665
|
-
* @param {string} value
|
|
666
|
-
* @returns {string}
|
|
667
|
-
*/
|
|
668
|
-
const toUSVString = (() => {
|
|
669
|
-
if (typeof String.prototype.toWellFormed === 'function') {
|
|
670
|
-
/**
|
|
671
|
-
* @param {string} value
|
|
672
|
-
* @returns {string}
|
|
673
|
-
*/
|
|
674
|
-
return (value) => `${value}`.toWellFormed()
|
|
675
|
-
} else {
|
|
676
|
-
/**
|
|
677
|
-
* @param {string} value
|
|
678
|
-
* @returns {string}
|
|
679
|
-
*/
|
|
680
|
-
return nodeUtil.toUSVString
|
|
681
|
-
}
|
|
682
|
-
})()
|
|
683
|
-
|
|
684
|
-
/**
|
|
685
|
-
* @param {*} value
|
|
686
|
-
* @returns {boolean}
|
|
687
|
-
*/
|
|
688
|
-
// TODO: move this to webidl
|
|
689
|
-
const isUSVString = (() => {
|
|
690
|
-
if (typeof String.prototype.isWellFormed === 'function') {
|
|
691
|
-
/**
|
|
692
|
-
* @param {*} value
|
|
693
|
-
* @returns {boolean}
|
|
694
|
-
*/
|
|
695
|
-
return (value) => `${value}`.isWellFormed()
|
|
696
|
-
} else {
|
|
697
|
-
/**
|
|
698
|
-
* @param {*} value
|
|
699
|
-
* @returns {boolean}
|
|
700
|
-
*/
|
|
701
|
-
return (value) => toUSVString(value) === `${value}`
|
|
702
|
-
}
|
|
703
|
-
})()
|
|
704
|
-
|
|
705
662
|
/**
|
|
706
663
|
* @see https://tools.ietf.org/html/rfc7230#section-3.2.6
|
|
707
664
|
* @param {number} c
|
|
@@ -943,8 +900,6 @@ Object.setPrototypeOf(normalizedMethodRecords, null)
|
|
|
943
900
|
module.exports = {
|
|
944
901
|
kEnumerableProperty,
|
|
945
902
|
isDisturbed,
|
|
946
|
-
toUSVString,
|
|
947
|
-
isUSVString,
|
|
948
903
|
isBlobLike,
|
|
949
904
|
parseOrigin,
|
|
950
905
|
parseURL,
|
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) => {
|
|
@@ -75,7 +144,8 @@ class ProxyAgent extends DispatcherBase {
|
|
|
75
144
|
signal: opts.signal,
|
|
76
145
|
headers: {
|
|
77
146
|
...this[kProxyHeaders],
|
|
78
|
-
host: opts.host
|
|
147
|
+
host: opts.host,
|
|
148
|
+
...(opts.connections == null || opts.connections > 0 ? { 'proxy-connection': 'keep-alive' } : {})
|
|
79
149
|
},
|
|
80
150
|
servername: this[kProxyTls]?.servername || proxyHostname
|
|
81
151
|
})
|
|
@@ -115,6 +185,10 @@ class ProxyAgent extends DispatcherBase {
|
|
|
115
185
|
headers.host = host
|
|
116
186
|
}
|
|
117
187
|
|
|
188
|
+
if (!this.#shouldConnect(new URL(opts.origin))) {
|
|
189
|
+
opts.path = opts.origin + opts.path
|
|
190
|
+
}
|
|
191
|
+
|
|
118
192
|
return this[kAgent].dispatch(
|
|
119
193
|
{
|
|
120
194
|
...opts,
|
|
@@ -147,6 +221,19 @@ class ProxyAgent extends DispatcherBase {
|
|
|
147
221
|
await this[kAgent].destroy()
|
|
148
222
|
await this[kClient].destroy()
|
|
149
223
|
}
|
|
224
|
+
|
|
225
|
+
#shouldConnect (uri) {
|
|
226
|
+
if (typeof uri === 'string') {
|
|
227
|
+
uri = new URL(uri)
|
|
228
|
+
}
|
|
229
|
+
if (this[kTunnelProxy]) {
|
|
230
|
+
return true
|
|
231
|
+
}
|
|
232
|
+
if (uri.protocol !== 'http:' || this[kProxy].protocol !== 'http:') {
|
|
233
|
+
return true
|
|
234
|
+
}
|
|
235
|
+
return false
|
|
236
|
+
}
|
|
150
237
|
}
|
|
151
238
|
|
|
152
239
|
/**
|