如何在生产环境中处理Express中的HTTP错误?

8
我正在开发 express 应用程序,当我指定所有路由和中间件后,在 server.js 文件的末尾添加了以下内容:
// Log errors
app.use(function (err, req, res, next) {
    logger.error(err.stack);

    if(process.env.NODE_ENV === 'production')
        return res.status(500).send('Something broke!');

    next(err);
});

// Start server
app.listen(port, () => {
    logger.info('Server is up on port ' + port);
});

这样做的目的是在生产环境中捕获所有错误,避免向客户端泄露敏感数据。

我在其中一个控制器中有这段代码:

const createHTTPError = require('http-errors')

async function(req, res, next) {
    try {
        invoice = await Invoice.create({
            // data
        });
    }catch (e) {
        if(e instanceof Sequelize.ValidationError){
             logger.error(e);
             return next(createHTTPError(400, 'Validation did not pass: ' + e.message));
        }
    }
}

问题是,当使用 http-errors 对象调用 next() 时,它会冒泡到我的通用错误处理程序,但信息会丢失,在其中,err 对象是一个简单的 Error 实例,具有以下这些参数:
message = "Validation did not pass: notNull Violation: invoice.clientEmail cannot be null"
name = "BadRequestError"
stack = "BadRequestError: Validation did not pass: notNull Violation: invoice.clientEmail cannot be null\n    at module.exports (/home/XXXX/create-new-invoice.js:109:33)"

错误代码编号丢失。错误对象类型丢失(好吧,在名称中转换为字符串)。

我该怎么办?如果我删除我的捕获所有异常的语句,那么就有泄露一些敏感信息的风险。谢谢。


1
将该条件添加到处理程序中 - 如果这是生产环境 且不是验证错误 - jonrsharpe
您当前设置为在生产环境中始终发送500错误。 - Len Joseph
@jonrsharpe,问题在于正如我在问题中所说的那样,进入这个 catch-all 函数的对象是简单的 Error。我无法检查它是什么类型的错误。 - michnovka
难道你不可以看一下消息的开头吗? - jonrsharpe
问题在于我无法为每种情况都实现这个,因为我可能会因为各种原因而生成http错误,而不仅仅是验证错误。我需要一种方法来检测错误是否为http错误实例,并在这种情况下提取我发送到http-errors的状态和消息。我看不到任何解决办法。 - michnovka
1个回答

6
所以我最终得到了这段代码:
const HTTPErrors = require('http-errors');
const HTTPStatuses = require('statuses');

// ... set up express, middlewares, routes...

// Log errors
app.use(function (err, req, res, next) {

    let messageToSend;

    if(err instanceof HTTPErrors.HttpError){
        // handle http err
        messageToSend = {message: err.message};

        if(process.env.NODE_ENV === 'development')
            messageToSend.stack = err.stack;

        messageToSend.status = err.statusCode;
    }else{
        // log other than HTTP errors (these are created by me manually, so I can log them when thrown)
        logger.error(err.stack);
    }

    if(process.env.NODE_ENV === 'production' && !messageToSend){
        messageToSend = {message: 'Something broke', status: 500};
    }

    if(messageToSend) {

        let statusCode = parseInt(messageToSend.status,10);
        let statusName = HTTPStatuses[statusCode];

        res.status(statusCode);

        // respond with html page
        if (req.accepts('html')) {
            res.send('<html><head><title>'+statusCode+' '+statusName+'</title></head><body><h1>'+statusCode+' '+statusName+'</h1>'+messageToSend.message+'<br/><br/>'+(messageToSend.stack ? messageToSend.stack : '')+'</body></html>');
            return;
        }

        // respond with json
        if (req.accepts('json')) {
            let responseObject = { error: statusName, code: statusCode, message: messageToSend.message };

            if(messageToSend.stack)
                responseObject.stack = messageToSend.stack;

            res.send(responseObject);
            return;
        }

        // default to plain-text. send()
        res.type('txt').send(statusName+' '+messageToSend.message);
        return;
    }

    // if this is not HTTP error and we are not in production, let express handle it the default way
    next(err);
});

这个解决方案:
  • 可以检测并展示来自http-errors模块的HTTP错误(在开发环境中附带堆栈跟踪,生产环境则不会)
  • 对于其他任何类型的错误,如果处于生产环境,则抛出通用的500服务器错误;如果处于开发环境,则让Express处理该错误,默认情况下打印出带有堆栈跟踪的错误信息。
  • 根据Accepts头设置的格式输出错误信息(因此,如果应用程序期望JSON,则发送JSON格式的错误信息)

我还利用了404catchall中的新捕获函数:

// DEFAULT CATCH
app.use(function(req, res, next){
    next(HTTPErrors(404));
});

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接