Skip to content

Commit 74d6b52

Browse files
authored
Add response lifecycle tracking and checks (#4257)
* Add response lifecycle tracking and checks. Closes #4256 * Cleanup
1 parent a542b0c commit 74d6b52

File tree

4 files changed

+53
-6
lines changed

4 files changed

+53
-6
lines changed

lib/response.js

Lines changed: 17 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -55,10 +55,11 @@ exports = module.exports = internals.Response = class {
5555

5656
this._events = null;
5757
this._payload = null; // Readable stream
58-
this._error = null; // The boom object when created from an error (used for logging)
58+
this._error = options.error || null; // The boom object when created from an error (used for logging)
5959
this._contentType = null; // Used if no explicit content-type is set and type is known
6060
this._takeover = false;
6161
this._statusCode = false; // true when code() called
62+
this._state = this._error ? 'prepare' : 'init'; // One of 'init', 'prepare', 'marshall', 'close'
6263

6364
this._processors = {
6465
marshal: options.marshal,
@@ -495,6 +496,10 @@ exports = module.exports = internals.Response = class {
495496

496497
_prepare() {
497498

499+
Hoek.assert(this._state === 'init');
500+
501+
this._state = 'prepare';
502+
498503
this._passThrough();
499504

500505
if (!this._processors.prepare) {
@@ -558,10 +563,14 @@ exports = module.exports = internals.Response = class {
558563

559564
async _marshal() {
560565

561-
let source = this.source;
566+
Hoek.assert(this._state === 'prepare');
567+
568+
this._state = 'marshall';
562569

563570
// Processor marshal
564571

572+
let source = this.source;
573+
565574
if (this._processors.marshal) {
566575
try {
567576
source = await this._processors.marshal(this);
@@ -638,6 +647,12 @@ exports = module.exports = internals.Response = class {
638647

639648
_close() {
640649

650+
if (this._state === 'close') {
651+
return;
652+
}
653+
654+
this._state = 'close';
655+
641656
if (this._processors.close) {
642657
try {
643658
this._processors.close(this);

lib/toolkit.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -101,7 +101,7 @@ exports.Manager = class {
101101

102102
if (typeof response !== 'symbol') {
103103
response = request._core.Response.wrap(response, request);
104-
if (!response.isBoom) {
104+
if (!response.isBoom && response._state === 'init') {
105105
response = await response._prepare();
106106
}
107107
}

lib/transmit.js

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,9 @@ exports.send = async function (request) {
3838
internals.marshal = async function (response) {
3939

4040
for (const func of response.request._route._marshalCycle) {
41-
await func(response);
41+
if (response._state !== 'close') {
42+
await func(response);
43+
}
4244
}
4345
};
4446

@@ -72,8 +74,7 @@ internals.fail = async function (request, boom) {
7274
internals.error = function (request, boom) {
7375

7476
const error = boom.output;
75-
const response = new request._core.Response(error.payload, request);
76-
response._error = boom;
77+
const response = new request._core.Response(error.payload, request, { error: boom });
7778
response.code(error.statusCode);
7879
response.headers = Hoek.clone(error.headers); // Prevent source from being modified
7980
return response;

test/response.js

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1290,6 +1290,37 @@ describe('Response', () => {
12901290
const res = await server.inject('/');
12911291
expect(res.statusCode).to.equal(500);
12921292
});
1293+
1294+
it('is only called once for returned responses', async () => {
1295+
1296+
let calls = 0;
1297+
const pre = (request, h) => {
1298+
1299+
const prepare = (response) => {
1300+
1301+
++calls;
1302+
return response;
1303+
};
1304+
1305+
return request.generateResponse(null, { prepare });
1306+
};
1307+
1308+
const server = Hapi.server();
1309+
server.route({
1310+
method: 'GET',
1311+
path: '/',
1312+
options: {
1313+
pre: [
1314+
{ method: pre, assign: 'p' }
1315+
],
1316+
handler: (request) => request.preResponses.p
1317+
}
1318+
});
1319+
1320+
const res = await server.inject('/');
1321+
expect(res.statusCode).to.equal(204);
1322+
expect(calls).to.equal(1);
1323+
});
12931324
});
12941325

12951326
describe('_tap()', () => {

0 commit comments

Comments
 (0)