簡體   English   中英

用於生產和開發的 Express 錯誤處理中間件

[英]Express Error handling middleware for production and development

我正在嘗試將生產錯誤日志設為默認值,並僅在environment variabledevelopment時向用戶顯示其他內容。 我正在嘗試通過以下方式執行此操作,但我收到一條消息,說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)

我建議您將sendProdErrorsendVerboseDevError修改為constructProdErrorconstructVerboseDevError ,然后從代碼中的同一點發送。

參考: https://stackoverflow.com/a/733858/2232902

我認為這里的問題在於多個 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.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM