Skip to content

Commit 93df232

Browse files
SuperOleg39o.drapeza
andauthored
feat(dispatcher/proxy-agent): new diagnostics event 'undici:proxy:connected' (#4659)
Co-authored-by: o.drapeza <[email protected]>
1 parent 6e82b9b commit 93df232

File tree

4 files changed

+42
-4
lines changed

4 files changed

+42
-4
lines changed

docs/docs/api/DiagnosticsChannel.md

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -254,3 +254,17 @@ diagnosticsChannel.channel('undici:websocket:pong').subscribe(({ payload, websoc
254254
console.log(websocket) // the WebSocket instance
255255
})
256256
```
257+
258+
## `undici:proxy:connected`
259+
260+
This message is published after the `ProxyAgent` establishes a connection to the proxy server.
261+
262+
```js
263+
import diagnosticsChannel from 'diagnostics_channel'
264+
265+
diagnosticsChannel.channel('undici:proxy:connected').subscribe(({ socket, connectParams }) => {
266+
console.log(socket)
267+
console.log(connectParams)
268+
// const { origin, port, path, signal, headers, servername } = connectParams
269+
})
270+
```

lib/core/diagnostics.js

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,9 @@ const channels = {
2626
close: diagnosticsChannel.channel('undici:websocket:close'),
2727
socketError: diagnosticsChannel.channel('undici:websocket:socket_error'),
2828
ping: diagnosticsChannel.channel('undici:websocket:ping'),
29-
pong: diagnosticsChannel.channel('undici:websocket:pong')
29+
pong: diagnosticsChannel.channel('undici:websocket:pong'),
30+
// ProxyAgent
31+
proxyConnected: diagnosticsChannel.channel('undici:proxy:connected')
3032
}
3133

3234
let isTrackingClientEvents = false

lib/dispatcher/proxy-agent.js

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ const DispatcherBase = require('./dispatcher-base')
77
const { InvalidArgumentError, RequestAbortedError, SecureProxyConnectionError } = require('../core/errors')
88
const buildConnector = require('../core/connect')
99
const Client = require('./client')
10+
const { channels } = require('../core/diagnostics')
1011

1112
const kAgent = Symbol('proxy agent')
1213
const kClient = Symbol('proxy client')
@@ -150,7 +151,7 @@ class ProxyAgent extends DispatcherBase {
150151
requestedPath += `:${defaultProtocolPort(opts.protocol)}`
151152
}
152153
try {
153-
const { socket, statusCode } = await this[kClient].connect({
154+
const connectParams = {
154155
origin,
155156
port,
156157
path: requestedPath,
@@ -161,12 +162,21 @@ class ProxyAgent extends DispatcherBase {
161162
...(opts.connections == null || opts.connections > 0 ? { 'proxy-connection': 'keep-alive' } : {})
162163
},
163164
servername: this[kProxyTls]?.servername || proxyHostname
164-
})
165+
}
166+
const { socket, statusCode } = await this[kClient].connect(connectParams)
165167
if (statusCode !== 200) {
166168
socket.on('error', noop).destroy()
167169
callback(new RequestAbortedError(`Proxy response (${statusCode}) !== 200 when HTTP Tunneling`))
168170
return
169171
}
172+
173+
if (channels.proxyConnected.hasSubscribers) {
174+
channels.proxyConnected.publish({
175+
socket,
176+
connectParams
177+
})
178+
}
179+
170180
if (opts.protocol !== 'https:') {
171181
callback(null, socket)
172182
return

test/proxy-agent.js

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,12 +2,14 @@
22

33
const { tspl } = require('@matteo.collina/tspl')
44
const { test, after } = require('node:test')
5+
const diagnosticsChannel = require('node:diagnostics_channel')
56
const { request, fetch, setGlobalDispatcher, getGlobalDispatcher } = require('..')
67
const { InvalidArgumentError, SecureProxyConnectionError } = require('../lib/core/errors')
78
const ProxyAgent = require('../lib/dispatcher/proxy-agent')
89
const Pool = require('../lib/dispatcher/pool')
910
const { createServer } = require('node:http')
1011
const https = require('node:https')
12+
const { Socket } = require('node:net')
1113
const { createProxy } = require('proxy')
1214

1315
const certs = (() => {
@@ -120,11 +122,17 @@ test('should accept string, URL and object as options', (t) => {
120122
})
121123

122124
test('use proxy-agent to connect through proxy (keep alive)', async (t) => {
123-
t = tspl(t, { plan: 6 })
125+
t = tspl(t, { plan: 10 })
124126
const server = await buildServer()
125127
const proxy = await buildProxy()
126128
delete proxy.authenticate
127129

130+
let _socket, _connectParams
131+
diagnosticsChannel.channel('undici:proxy:connected').subscribe(({ socket, connectParams }) => {
132+
_socket = socket
133+
_connectParams = connectParams
134+
})
135+
128136
const serverUrl = `http://localhost:${server.address().port}`
129137
const proxyUrl = `http://localhost:${proxy.address().port}`
130138
const proxyAgent = new ProxyAgent({
@@ -154,6 +162,10 @@ test('use proxy-agent to connect through proxy (keep alive)', async (t) => {
154162
t.strictEqual(statusCode, 200)
155163
t.deepStrictEqual(json, { hello: 'world' })
156164
t.strictEqual(headers.connection, 'keep-alive', 'should remain the connection open')
165+
t.ok(_socket instanceof Socket)
166+
t.equal(_connectParams.origin, proxyUrl)
167+
t.equal(_connectParams.path, serverUrl.replace('http://', ''))
168+
t.equal(_connectParams.headers['proxy-connection'], 'keep-alive')
157169

158170
server.close()
159171
proxy.close()

0 commit comments

Comments
 (0)