|
1 | 1 | const { test, describe } = require('node:test') |
2 | 2 | const assert = require('node:assert') |
3 | 3 | const { createServer } = require('node:http') |
4 | | -const { request, Agent, Pool } = require('..') |
| 4 | +const { once } = require('node:events') |
| 5 | +const { request, Agent, Pool, ProxyAgent } = require('..') |
| 6 | +const { kClients } = require('../lib/core/symbols') |
5 | 7 |
|
6 | 8 | // https://github.com/nodejs/undici/issues/4424 |
7 | 9 | describe('Agent should close inactive clients', () => { |
@@ -154,3 +156,47 @@ describe('Agent should not close active clients', () => { |
154 | 156 | assert.deepEqual(socketSequence.slice(3), ['2', '2']) |
155 | 157 | }) |
156 | 158 | }) |
| 159 | + |
| 160 | +// https://github.com/nodejs/undici/issues/5529 |
| 161 | +describe('Agent teardown of factory dispatchers without an internal url', () => { |
| 162 | + test('ProxyAgent forwarding plain http does not crash on teardown', async (t) => { |
| 163 | + // A minimal forward proxy: answers any absolute-form request itself. |
| 164 | + const proxy = createServer((req, res) => { |
| 165 | + res.setHeader('connection', 'close') |
| 166 | + res.end('ok') |
| 167 | + }).listen(0) |
| 168 | + |
| 169 | + t.after(() => { |
| 170 | + proxy.closeAllConnections?.() |
| 171 | + proxy.close() |
| 172 | + }) |
| 173 | + await once(proxy, 'listening') |
| 174 | + |
| 175 | + const proxyAgent = new ProxyAgent(`http://localhost:${proxy.address().port}`) |
| 176 | + t.after(() => proxyAgent.close()) |
| 177 | + |
| 178 | + // A plain-http request registers an Http1ProxyWrapper, which has no kUrl, |
| 179 | + // in the inner Agent's client map. |
| 180 | + const { statusCode, body } = await request('http://target.example/', { dispatcher: proxyAgent }) |
| 181 | + assert.equal(statusCode, 200) |
| 182 | + await body.text() |
| 183 | + |
| 184 | + let innerAgent |
| 185 | + for (const sym of Object.getOwnPropertySymbols(proxyAgent)) { |
| 186 | + const value = proxyAgent[sym] |
| 187 | + if (value && value[kClients] instanceof Map && value[kClients].size > 0) { |
| 188 | + innerAgent = value |
| 189 | + break |
| 190 | + } |
| 191 | + } |
| 192 | + assert.ok(innerAgent, 'expected to find the inner Agent') |
| 193 | + const [wrapper] = innerAgent[kClients].values() |
| 194 | + |
| 195 | + // The Agent subscribes to this event on every dispatcher its factory |
| 196 | + // returns. Before the fix this threw |
| 197 | + // "Cannot read properties of undefined (reading 'origin')". |
| 198 | + wrapper.emit('disconnect', 'http://target.example', [wrapper], new Error('closed')) |
| 199 | + |
| 200 | + assert.equal(innerAgent[kClients].size, 0, 'expected the unused wrapper to be removed') |
| 201 | + }) |
| 202 | +}) |
0 commit comments