[英]Express Error handling middleware for production and development
我正在嘗試將生產錯誤日志設為默認值,並僅在environment variable
為development
時向用戶顯示其他內容。 我正在嘗試通過以下方式執行此操作,但我收到一條消息,說Cannot set headers after they are sent to the client
。
const ErrorClass = require('../routes/utils/ErrorClass');
const prodDBCastError = err => {
const message = `Invalid ${err.path}: ${err.value}`;
return new ErrorClass(message, 400);
};
const prodDBDuplicateFieldsError = err => {
const value = err.errmsg.match(/(["'])(\\?.)*?\1/)[0];
const message = `Duplicate field value: ${value}.`;
return new ErrorClass(message, 400);
};
const prodDBDValidationError = err => {
const errors = Object.values(err.errors).map(el => el.message);
const message = `Invalid input data. ${errors.join('. ')}`;
return new ErrorClass(message, 400);
};
const handleBadRequestDB = err => {
const errors = err.message;
const message = `Fixes: ${errors}`;
return new ErrorClass(message, 400);
};
const sendProdError = (err, res) => {
if (err.isOperationalError){
res.status(err.status).json({
status: err.status,
message: err.message,
});
} else {
res.status(500).json({
status: 'error',
message: 'Server Issue.',
});
}
};
const sendVerboseDevError = (err, res) => {
logger.error(err);
err.status = err.status || 500;
res.status(err.status).json({
status: err.status,
name: err.name,
path: err.path,
errors: err.errors,
message: err.message,
stack: err.stack,
});
};
module.exports = (err, req, res, next) => {
if (process.env.APP_ENV === 'development'){
sendVerboseDevError(err, res);
}
if (err.name === 'CastError') {err = prodDBCastError(err);}
if (err.name === 'MongoError') {err = prodDBDuplicateFieldsError(err);}
if (err.name === 'ValidationError') {err = prodDBDValidationError(err);}
if (err.name === 'Bad Request') {err = handleBadRequestDB(err);}
sendProdError(err, res);
};
這就是我的 ErrorClass 的樣子:
class ErrorClass extends Error {
constructor(message, status) {
super(message);
this.status = status;
this.isOperationalError = true;
Error.captureStackTrace(this, this.constructor);
}
}
module.exports = ErrorClass;
我建議您像這樣更改代碼。
首先,您避免多次調用res.json
並且您只檢查應用程序是否在開發模式下運行一次。 無需檢查每個請求。
var devHandler = (err, req, res, next) => {
logger.error(err);
err.status = err.status || 500;
res.status(err.status).json({
status: err.status,
name: err.name,
path: err.path,
errors: err.errors,
message: err.message,
stack: err.stack,
});
};
var prodHandler = (err, req, res, next) => {
if (err.name === 'CastError') {err = prodDBCastError(err);}
if (err.name === 'MongoError') {err = prodDBDuplicateFieldsError(err);}
if (err.name === 'ValidationError') {err = prodDBDValidationError(err);}
if (err.name === 'Bad Request') {err = handleBadRequestDB(err);}
if (err.isOperationalError){
res.status(err.status).json({
status: err.status,
message: err.message,
});
} else {
res.status(500).json({
status: 'error',
message: 'Server Issue.',
});
}
};
module.exports = process.env.APP_ENV === 'development' ? devHandler : prodHandler;
這是因為您在從sendVerboseDevError
發送數據后嘗試從sendProdError
發送數據。 res.json
正在向客戶端發送 json。
這背后的原因在這里解釋https://stackoverflow.com/a/7086621/2232902
Express 中的 res object 是 Node.js 的 http.ServerResponse 的子類(閱讀 http.js 源)。 您可以隨時調用 res.setHeader(name, value),直到調用 res.writeHead(statusCode)。 在 writeHead 之后,headers 被烘焙進去,你只能調用 res.write(data),最后調用 res.end(data)
我建議您將sendProdError
和sendVerboseDevError
修改為constructProdError
和constructVerboseDevError
,然后從代碼中的同一點發送。
我認為這里的問題在於多個 if 條件。 如果第一個條件成立,則將執行“sendVerboseDevError”function。 其中有以下代碼
res.status(err.status).json({
status: err.status,
name: err.name,
path: err.path,
errors: err.errors,
message: err.message,
stack: err.stack,
});
在此 function 之后,將設置響應 header。 然后,如果條件為真,則進入 if 條件的 rest 的流程,然后再次調用其他一些 function 正在嘗試再次設置響應的流程。 這就是為什么您收到錯誤“在將標頭發送到客戶端后無法設置標頭”您需要在設置響應后調用“next()”方法。 所以你應該在每個 if 條件的底部添加“next()”方法。
就像是
module.exports = (err, req, res, next) => {
if (process.env.APP_ENV === 'development'){
sendVerboseDevError(err, res);
next();
}
if (err.name === 'CastError') {
err = prodDBCastError(err);
next();
}
if (err.name === 'MongoError') {
err = prodDBDuplicateFieldsError(err);
next();
}
if (err.name === 'ValidationError') {
err = prodDBDValidationError(err);
next();
}
if (err.name === 'Bad Request') {
err = handleBadRequestDB(err);
next();
}
sendProdError(err, res);
next();
};
下一個 function 將終止 API 調用並返回響應,並防止它在響應已由當前執行的任何 function 設置響應后再次設置響應。
PS:你也可以調用“res.end()”而不是“next()”function。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.