Skip to content

Commit 138ad61

Browse files
Feat: Agent connect/disconnect events (#558)
* feat: ๐ŸŽธ Add connect/disconnect events to Agent Extend EventEmitter class from within Agent and define support for the connect and disconnect events. โœ… Closes: #522 * test: ๐Ÿ’ Add tests for Agent connect/disconnect events Provide minimum set of tests for connect/disconnect events for the Agent class. โœ… Closes: #522 * test: ๐Ÿ’ Extend tests for Agent connect/disconnect events Disconnect event listener should be removed from the pool/client (acquired through the Agent instance) when instance of the Agent class is destroyed. โœ… Closes: #522 * test: ๐Ÿ’ Increase Agent test coverage Provide the tests for the Agent get method, checking weather the pool is instance of Client or Pool class. โœ… Closes: #522
1 parent 6612d33 commit 138ad61

2 files changed

Lines changed: 142 additions & 11 deletions

File tree

โ€Žlib/agent.jsโ€Ž

Lines changed: 27 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -4,11 +4,32 @@ const Pool = require('./pool')
44
const Client = require('./core/client')
55
const util = require('./core/util')
66
const { kAgentOpts, kAgentCache } = require('./core/symbols')
7+
const EventEmitter = require('events')
78

8-
class Agent {
9+
const kOnConnect = Symbol('onConnect')
10+
const kOnDisconnect = Symbol('onDisconnect')
11+
12+
class Agent extends EventEmitter {
913
constructor (opts) {
14+
super()
15+
1016
this[kAgentOpts] = opts
1117
this[kAgentCache] = new Map()
18+
19+
const agent = this
20+
21+
this[kOnConnect] = function onConnect (client) {
22+
agent.emit('connect', client)
23+
}
24+
25+
this[kOnDisconnect] = function onDestroy (client, err) {
26+
if (this.connected === 0 && this.size === 0) {
27+
this.off('disconnect', agent[kOnDisconnect])
28+
agent[kAgentCache].delete(this.origin)
29+
}
30+
31+
agent.emit('disconnect', client, err)
32+
}
1233
}
1334

1435
get (origin) {
@@ -19,18 +40,15 @@ class Agent {
1940
const self = this
2041
let pool = self[kAgentCache].get(origin)
2142

22-
function onDisconnect () {
23-
if (this.connected === 0 && this.size === 0) {
24-
this.off('disconnect', onDisconnect)
25-
self[kAgentCache].delete(origin)
26-
}
27-
}
28-
2943
if (!pool) {
3044
pool = self[kAgentOpts] && self[kAgentOpts].connections === 1
3145
? new Client(origin, self[kAgentOpts])
3246
: new Pool(origin, self[kAgentOpts])
33-
pool.on('disconnect', onDisconnect)
47+
48+
pool
49+
.on('connect', this[kOnConnect])
50+
.on('disconnect', this[kOnDisconnect])
51+
3452
self[kAgentCache].set(origin, pool)
3553
}
3654

โ€Žtest/agent.jsโ€Ž

Lines changed: 115 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,10 +5,11 @@ const http = require('http')
55
const { Agent, request, stream, pipeline, setGlobalAgent } = require('../lib/agent')
66
const { PassThrough } = require('stream')
77
const { InvalidArgumentError, InvalidReturnValueError } = require('../lib/core/errors')
8-
const { errors } = require('..')
8+
const { Client, Pool, errors } = require('../index')
9+
const { promisify } = require('util')
910

1011
tap.test('Agent', t => {
11-
t.plan(6)
12+
t.plan(9)
1213

1314
t.test('setGlobalAgent', t => {
1415
t.plan(2)
@@ -122,6 +123,118 @@ tap.test('Agent', t => {
122123
})
123124
})
124125

126+
t.test('Agent connect/disconnect event(s)', t => {
127+
t.plan(2)
128+
129+
t.test('multiple connections', t => {
130+
const connections = 3
131+
t.plan(6 * connections)
132+
133+
const server = http.createServer((req, res) => {
134+
res.writeHead(200, {
135+
Connection: 'keep-alive',
136+
'Keep-Alive': 'timeout=1s'
137+
})
138+
res.end('ok')
139+
})
140+
t.tearDown(server.close.bind(server))
141+
142+
server.listen(0, async () => {
143+
const origin = `http://localhost:${server.address().port}`
144+
const agent = new Agent({ connections })
145+
146+
t.tearDown(agent.close.bind(agent))
147+
148+
agent.on('connect', (client) => {
149+
t.ok(client)
150+
})
151+
agent.on('disconnect', (client, error) => {
152+
t.ok(client)
153+
t.true(error instanceof errors.InformationalError)
154+
t.strictEqual(error.code, 'UND_ERR_INFO')
155+
t.strictEqual(error.message, 'reset')
156+
})
157+
158+
for (let i = 0; i < connections; i++) {
159+
await request(origin, { agent })
160+
.then(() => {
161+
t.pass('should pass')
162+
})
163+
.catch(err => {
164+
t.fail(err)
165+
})
166+
}
167+
})
168+
})
169+
170+
t.test('remove disconnect listeners when destroyed', t => {
171+
t.plan(3)
172+
173+
const server = http.createServer((req, res) => {
174+
res.writeHead(200, {
175+
Connection: 'keep-alive',
176+
'Keep-Alive': 'timeout=1s'
177+
})
178+
res.end('ok')
179+
})
180+
t.tearDown(server.close.bind(server))
181+
182+
server.listen(0, async () => {
183+
const origin = `http://localhost:${server.address().port}`
184+
const agent = new Agent()
185+
186+
t.tearDown(agent.close.bind(agent))
187+
188+
const pool = agent.get(origin)
189+
t.true(pool.listeners('disconnect').length === 1)
190+
191+
agent.on('disconnect', () => {
192+
t.true(pool.listeners('disconnect').length === 0)
193+
})
194+
195+
await request(origin, { agent })
196+
.then(() => {
197+
t.pass('should pass')
198+
})
199+
.catch(err => {
200+
t.fail(err)
201+
})
202+
})
203+
})
204+
})
205+
206+
t.test('check if pool', async t => {
207+
t.plan(1)
208+
209+
const server = http.createServer()
210+
t.tearDown(server.close.bind(server))
211+
await promisify(server.listen.bind(server))(0)
212+
213+
const origin = `http://localhost:${server.address().port}`
214+
const agent = new Agent()
215+
216+
t.tearDown(agent.close.bind(agent))
217+
218+
const pool = agent.get(origin)
219+
t.true(pool instanceof Pool)
220+
})
221+
222+
t.test('check if client', async t => {
223+
t.plan(1)
224+
225+
const server = http.createServer()
226+
t.tearDown(server.close.bind(server))
227+
await promisify(server.listen.bind(server))(0)
228+
229+
const origin = `http://localhost:${server.address().port}`
230+
const agent = new Agent({ connections: 1 })
231+
232+
t.tearDown(agent.close.bind(agent))
233+
234+
const pool = agent.get(origin)
235+
t.true(pool instanceof Client)
236+
})
237+
125238
t.test('request a resource', t => {
126239
t.plan(5)
127240

0 commit comments

Comments
ย (0)