Skip to content

Commit 37d211a

Browse files
authored
Handle stream.destroy() called before stream end (#4552)
* Handle stream.destroy() called before stream end * Fix test on node < 18
1 parent c42fb6e commit 37d211a

File tree

2 files changed

+66
-0
lines changed

2 files changed

+66
-0
lines changed

lib/transmit.js

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -266,6 +266,7 @@ internals.pipe = function (request, stream) {
266266
}
267267
else {
268268
stream.on('error', end);
269+
stream.on('close', aborted);
269270
stream.pipe(request.raw.res);
270271
}
271272

@@ -364,6 +365,7 @@ internals.chain = function (sources) {
364365
for (let i = 1; i < sources.length; ++i) {
365366
const to = sources[i];
366367
if (to) {
368+
from.on('close', internals.destroyPipe.bind(from, to));
367369
from.on('error', internals.errorPipe.bind(from, to));
368370
from = from.pipe(to);
369371
}
@@ -373,6 +375,13 @@ internals.chain = function (sources) {
373375
};
374376

375377

378+
internals.destroyPipe = function (to) {
379+
380+
if (!this.readableEnded && !this.errored) {
381+
to.destroy();
382+
}
383+
};
384+
376385
internals.errorPipe = function (to, err) {
377386

378387
to.emit('error', err);

test/transmit.js

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1418,6 +1418,63 @@ describe('transmission', () => {
14181418
expect(count).to.equal(1);
14191419
});
14201420

1421+
it('handles stream that is destroyed with no error', async () => {
1422+
1423+
const handler = (request, h) => {
1424+
1425+
const stream = new Stream.Readable({ read: Hoek.ignore });
1426+
1427+
stream.push('hello');
1428+
Hoek.wait(1).then(() => stream.destroy());
1429+
1430+
return h.response(stream).type('text/html');
1431+
};
1432+
1433+
const server = Hapi.server();
1434+
server.route({ method: 'GET', path: '/', handler });
1435+
1436+
const log = server.events.once('response');
1437+
const err = await expect(server.inject({ url: '/', headers: { 'accept-encoding': 'gzip' } })).to.reject(Boom.Boom);
1438+
expect(err.output.statusCode).to.equal(499);
1439+
1440+
const [request] = await log;
1441+
expect(request.response.isBoom).to.be.true();
1442+
expect(request.response.output.statusCode).to.equal(499);
1443+
});
1444+
1445+
it('handles stream that is destroyed with error', async () => {
1446+
1447+
const handler = (request, h) => {
1448+
1449+
const stream = new Stream.Readable({ read: Hoek.ignore });
1450+
if (stream.errored === undefined) {
1451+
1452+
// Expose errored property on node 14 & 16 to enable coverage
1453+
1454+
stream.on('error', () => {
1455+
1456+
stream.errored = true;
1457+
});
1458+
}
1459+
1460+
stream.push('hello');
1461+
Hoek.wait(1).then(() => stream.destroy(new Error('failed')));
1462+
1463+
return h.response(stream).type('text/html');
1464+
};
1465+
1466+
const server = Hapi.server();
1467+
server.route({ method: 'GET', path: '/', handler });
1468+
1469+
const log = server.events.once('response');
1470+
const err = await expect(server.inject({ url: '/', headers: { 'accept-encoding': 'gzip' } })).to.reject(Boom.Boom);
1471+
expect(err.output.statusCode).to.equal(499);
1472+
1473+
const [request] = await log;
1474+
expect(request.response.isBoom).to.be.true();
1475+
expect(request.response.output.statusCode).to.equal(500);
1476+
});
1477+
14211478
describe('response range', () => {
14221479

14231480
const fileStreamHandler = (request, h) => {

0 commit comments

Comments
 (0)