Skip to content

Commit d0399c4

Browse files
authored
fix(inteceptor/dump): handle preemptive network errors (#4354)
1 parent 57efacb commit d0399c4

File tree

2 files changed

+68
-2
lines changed

2 files changed

+68
-2
lines changed

lib/interceptor/dump.js

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,8 @@ class DumpHandler extends DecoratorHandler {
5757
return
5858
}
5959

60-
err = this.#controller.reason ?? err
60+
// On network errors before connect, controller will be null
61+
err = this.#controller?.reason ?? err
6162

6263
super.onResponseError(controller, err)
6364
}

test/interceptors/dump-interceptor.js

Lines changed: 66 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ const { createServer } = require('node:http')
55
const { once } = require('node:events')
66
const { tspl } = require('@matteo.collina/tspl')
77

8-
const { Client, interceptors } = require('../..')
8+
const { Client, Agent, interceptors } = require('../..')
99
const { dump } = interceptors
1010

1111
if (platform() === 'win32') {
@@ -14,6 +14,71 @@ if (platform() === 'win32') {
1414
process.exit(0)
1515
}
1616

17+
test('Should handle preemptive network error', async t => {
18+
t = tspl(t, { plan: 4 })
19+
let offset = 0
20+
const server = createServer({ joinDuplicateHeaders: true }, (req, res) => {
21+
const max = 1024 * 1024
22+
const buffer = Buffer.alloc(max)
23+
24+
res.writeHead(200, {
25+
'Content-Length': buffer.length,
26+
'Content-Type': 'application/octet-stream'
27+
})
28+
29+
const interval = setInterval(() => {
30+
offset += 256
31+
const chunk = buffer.subarray(offset - 256, offset)
32+
33+
if (offset === max) {
34+
clearInterval(interval)
35+
res.end(chunk)
36+
return
37+
}
38+
39+
res.write(chunk)
40+
}, 0)
41+
})
42+
43+
const requestOptions = {
44+
method: 'GET',
45+
path: '/'
46+
}
47+
48+
const client = new Agent().compose(dump({ maxSize: 1024 * 1024 }))
49+
50+
after(async () => {
51+
await client.close()
52+
53+
server.close()
54+
await once(server, 'close')
55+
})
56+
57+
try {
58+
await client.request({
59+
origin: 'http://localhost',
60+
...requestOptions
61+
})
62+
} catch (error) {
63+
t.equal(error.code, 'ECONNREFUSED')
64+
}
65+
66+
server.listen(0)
67+
await once(server, 'listening')
68+
69+
const response = await client.request({
70+
origin: `http://localhost:${server.address().port}`,
71+
...requestOptions
72+
})
73+
const body = await response.body.text()
74+
75+
t.equal(response.headers['content-length'], `${1024 * 1024}`)
76+
t.equal(response.statusCode, 200)
77+
t.equal(body, '')
78+
79+
await t.completed
80+
})
81+
1782
test('Should dump on abort', async t => {
1883
t = tspl(t, { plan: 2 })
1984
let offset = 0

0 commit comments

Comments
 (0)