Skip to content

Commit 289337d

Browse files
committed
Return explicit error when trying to stream a non-Readable stream
This replaces "TypeError: Cannot read property 'objectMode' of undefined" error response Fixes hapijs#2368 & hapijs#2301
1 parent 4e38dba commit 289337d

File tree

3 files changed

+128
-4
lines changed

3 files changed

+128
-4
lines changed

lib/response.js

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -447,8 +447,11 @@ internals.Response.prototype._marshal = function (next) {
447447
internals.Response.prototype._streamify = function (source, next) {
448448

449449
if (source instanceof Stream) {
450-
var stream = (source.socket || source);
451-
if (stream._readableState.objectMode) {
450+
if (typeof source._read !== 'function' || typeof source._readableState !== 'object') {
451+
return next(Boom.badImplementation('Stream is not a streams2 Readable'));
452+
}
453+
454+
if (source._readableState.objectMode) {
452455
return next(Boom.badImplementation('Cannot reply with stream in object mode'));
453456
}
454457

test/reply.js

Lines changed: 51 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -251,7 +251,56 @@ describe('Reply', function () {
251251
});
252252
});
253253

254-
it('responds with an http client stream reply', function (done) {
254+
it('errors on non-readable stream reply', function (done) {
255+
256+
var streamHandler = function (request, reply) {
257+
258+
var stream = new Stream();
259+
stream.writable = true;
260+
261+
reply(stream);
262+
};
263+
264+
var writableHandler = function (request, reply) {
265+
266+
var writable = new Stream.Writable();
267+
writable._write = function () {};
268+
269+
reply(writable);
270+
};
271+
272+
var server = new Hapi.Server({ debug: false });
273+
server.connection();
274+
server.route({ method: 'GET', path: '/stream', handler: streamHandler });
275+
server.route({ method: 'GET', path: '/writable', handler: writableHandler });
276+
277+
var requestError;
278+
server.on('request-error', function (request, err) {
279+
280+
requestError = err;
281+
});
282+
283+
server.start(function () {
284+
285+
server.inject('/stream', function (res) {
286+
287+
expect(res.statusCode).to.equal(500);
288+
expect(requestError).to.exist();
289+
expect(requestError.message).to.contain('Stream is not a streams2 Readable');
290+
291+
requestError = undefined;
292+
server.inject('/writable', function (res) {
293+
294+
expect(res.statusCode).to.equal(500);
295+
expect(requestError).to.exist();
296+
expect(requestError.message).to.contain('Stream is not a streams2 Readable');
297+
done();
298+
});
299+
});
300+
});
301+
});
302+
303+
it('errors on an http client stream reply', function (done) {
255304

256305
var handler = function (request, reply) {
257306

@@ -272,7 +321,7 @@ describe('Reply', function () {
272321

273322
server.inject('/stream', function (res) {
274323

275-
expect(res.statusCode).to.equal(200);
324+
expect(res.statusCode).to.equal(500);
276325
done();
277326
});
278327
});

test/transmit.js

Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1318,6 +1318,78 @@ describe('transmission', function () {
13181318
});
13191319
});
13201320

1321+
it('does not leak classic stream data when passed to request and aborted', function (done) {
1322+
1323+
var destroyed = false;
1324+
1325+
var handler = function (request, reply) {
1326+
1327+
var stream = new Stream();
1328+
stream.readable = true;
1329+
1330+
var _read = function () {
1331+
1332+
setImmediate(function () {
1333+
1334+
if (paused) {
1335+
return;
1336+
}
1337+
1338+
var chunk = new Array(1024).join('x');
1339+
1340+
if (destroyed) {
1341+
stream.emit('data', chunk);
1342+
stream.readable = false;
1343+
stream.emit('end');
1344+
} else {
1345+
stream.emit('data', chunk);
1346+
_read();
1347+
}
1348+
});
1349+
};
1350+
1351+
var paused = true;
1352+
stream.resume = function () {
1353+
1354+
if (paused) {
1355+
paused = false;
1356+
_read();
1357+
}
1358+
};
1359+
stream.pause = function () {
1360+
1361+
paused = true;
1362+
};
1363+
1364+
stream.resume();
1365+
1366+
stream.once('end', function () {
1367+
1368+
done();
1369+
});
1370+
1371+
return reply(stream);
1372+
};
1373+
1374+
var server = new Hapi.Server();
1375+
server.connection();
1376+
server.route({ method: 'GET', path: '/', handler: handler });
1377+
1378+
server.start(function () {
1379+
1380+
Wreck.request('GET', 'http://localhost:' + server.info.port, {}, function (err, res) {
1381+
1382+
res.on('data', function (chunk) {
1383+
1384+
if (!destroyed) {
1385+
destroyed = true;
1386+
res.destroy();
1387+
}
1388+
});
1389+
});
1390+
});
1391+
});
1392+
13211393
it('does not leak stream data when request timeouts before stream drains', function (done) {
13221394

13231395
var handler = function (request, reply) {

0 commit comments

Comments
 (0)