Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions lib/dispatcher/agent.js
Original file line number Diff line number Diff line change
Expand Up @@ -107,15 +107,15 @@ class Agent extends DispatcherBase {
}

let hasOrigin = false
for (const client of this[kClients].values()) {
if (client[kUrl].origin === dispatcher[kUrl].origin) {
for (const k of this[kClients].keys()) {
if (k === origin || k === `${origin}#http1-only`) {
hasOrigin = true
break
}
}

if (!hasOrigin) {
this[kOrigins].delete(dispatcher[kUrl].origin)
this[kOrigins].delete(origin)
}
}

Expand Down
48 changes: 47 additions & 1 deletion test/agent-connection-management.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
const { test, describe } = require('node:test')
const assert = require('node:assert')
const { createServer } = require('node:http')
const { request, Agent, Pool } = require('..')
const { once } = require('node:events')
const { request, Agent, Pool, ProxyAgent } = require('..')
const { kClients } = require('../lib/core/symbols')

// https://github.com/nodejs/undici/issues/4424
describe('Agent should close inactive clients', () => {
Expand Down Expand Up @@ -154,3 +156,47 @@ describe('Agent should not close active clients', () => {
assert.deepEqual(socketSequence.slice(3), ['2', '2'])
})
})

// https://github.com/nodejs/undici/issues/5529
describe('Agent teardown of factory dispatchers without an internal url', () => {
test('ProxyAgent forwarding plain http does not crash on teardown', async (t) => {
// A minimal forward proxy: answers any absolute-form request itself.
const proxy = createServer((req, res) => {
res.setHeader('connection', 'close')
res.end('ok')
}).listen(0)

t.after(() => {
proxy.closeAllConnections?.()
proxy.close()
})
await once(proxy, 'listening')

const proxyAgent = new ProxyAgent(`http://localhost:${proxy.address().port}`)
t.after(() => proxyAgent.close())

// A plain-http request registers an Http1ProxyWrapper, which has no kUrl,
// in the inner Agent's client map.
const { statusCode, body } = await request('http://target.example/', { dispatcher: proxyAgent })
assert.equal(statusCode, 200)
await body.text()

let innerAgent
for (const sym of Object.getOwnPropertySymbols(proxyAgent)) {
const value = proxyAgent[sym]
if (value && value[kClients] instanceof Map && value[kClients].size > 0) {
innerAgent = value
break
}
}
assert.ok(innerAgent, 'expected to find the inner Agent')
const [wrapper] = innerAgent[kClients].values()

// The Agent subscribes to this event on every dispatcher its factory
// returns. Before the fix this threw
// "Cannot read properties of undefined (reading 'origin')".
wrapper.emit('disconnect', 'http://target.example', [wrapper], new Error('closed'))

assert.equal(innerAgent[kClients].size, 0, 'expected the unused wrapper to be removed')
})
})