🐛 Bug Report
If response validation fails, reply.send() will thrown an Error (as expected), but the error is not handled by Fastify and it breaks the response lifecycle.
This is the code of reply.send. I commented the 2 important lines.
Reply.prototype.send = function(payload) {
if (this.sent) {
this.res.log.warn({ err: new Error('Reply already sent') }, 'Reply already sent');
return;
}
// ⚠️ If in this method is thrown an error, this flag remains true ⚠️
this.sent = true;
if (payload instanceof Error || this._isError === true) {
handleError(this, payload, onSendHook);
return;
}
if (payload === undefined) {
onSendHook(this, payload);
return;
}
var contentType = getHeader(this, 'content-type');
var hasContentType = contentType !== undefined;
if (payload !== null) {
if (Buffer.isBuffer(payload) || typeof payload.pipe === 'function') {
if (hasContentType === false) {
this._headers['content-type'] = CONTENT_TYPE.OCTET;
}
onSendHook(this, payload);
return;
}
if (hasContentType === false && typeof payload === 'string') {
this._headers['content-type'] = CONTENT_TYPE.PLAIN;
onSendHook(this, payload);
return;
}
}
if (this._serializer) {
payload = this._serializer(payload);
} else if (hasContentType === false || contentType.indexOf('application/json') > -1) {
if (hasContentType === false || contentType.indexOf('charset') === -1) {
this._headers['content-type'] = CONTENT_TYPE.JSON;
}
// ⚠️ Throw error on validation error ⚠️
payload = serialize(this.context, payload, this.res.statusCode);
flatstr(payload);
}
onSendHook(this, payload);
};
When serialize(this.context, payload, this.res.statusCode); throws an error, onSendHook is never called, but this.sent remains true. This is not really true, because to the client is not sent any response. In my route I can catch the error and would treat it as server error:
try {
reply.send(invalidResponse)
} catch (error) {
// We tried to return an invalid response. Handle it as a server error
reply.code(500).send('Internal Server Error'); // throw reply already sent
}
But it throws reply already sent, due to the sent flag set to true. In this case, to the client isn't sent anything and the connection still opened until timeout.
To Reproduce
Steps to reproduce the behavior:
Just create a route and return an invalid response.
const options = {
schema: {
response: {
200: {
type: 'object',
required: ['_id', 'value'],
properties: {
_id: {
type: 'string',
},
value: {
type: 'string',
},
},
},
},
},
};
fastify.get('/', options, (request, reply) => {
try {
// Throw an error due to prop name typo
reply.send({ _id: 'someid', valu: 'wrong_prop_name' });
} catch (error) {
// throw reply already sent
reply.code(500).send('Internal Server Error');
}
});
The app hangs up with "reply already sent" and nothing is sent to the client
Expected behavior
I expect that:
- Fastify returns a server error automatically, on validation error. OR
- Sent isn't set to true until the response I really sent
My Environment
- node version: 10
- fastify version: 1.13.1
- os: Mac, Linux. Windows not tested
🐛 Bug Report
If response validation fails,
reply.send()will thrown an Error (as expected), but the error is not handled by Fastify and it breaks the response lifecycle.This is the code of
reply.send. I commented the 2 important lines.When
serialize(this.context, payload, this.res.statusCode);throws an error,onSendHookis never called, butthis.sentremainstrue. This is not really true, because to the client is not sent any response. In my route I can catch the error and would treat it as server error:But it throws reply already sent, due to the
sentflag set to true. In this case, to the client isn't sent anything and the connection still opened until timeout.To Reproduce
Steps to reproduce the behavior:
Just create a route and return an invalid response.
The app hangs up with "reply already sent" and nothing is sent to the client
Expected behavior
I expect that:
My Environment