捕获和处理错误
您可以使用Node内置的domain模块来实现。
域提供了一种将多个不同的IO操作作为单个组处理的方式。如果任何事件发射器或回调函数向域发出错误事件,或者抛出错误,则域对象将被通知,而不是在process.on('uncaughtException')处理程序中丢失错误上下文,或者导致程序立即退出并带有错误代码。
需要注意的一件非常重要的事情是:
域错误处理程序不能替代当错误发生时关闭进程。
由于JavaScript中throw的工作方式的本质,几乎没有安全的“从上次离开的地方”继续执行的方法,而不会泄漏引用或创建某种其他未定义的脆弱状态。
由于您只询问如何使用500
错误进行响应,因此我不会像Node文档那样详细介绍如何处理重新启动服务器等内容;我强烈建议查看节点文档中的示例。他们的示例展示了如何捕获错误,将错误响应发送回客户端(如果可能),然后重新启动服务器。我只会展示域创建和发送500
错误响应。 (有关重新启动进程的内容请参见下一节)
域的工作方式类似于在createServer
回调中放置try
/catch
。在您的回调中:
- 创建一个新的域对象
- 侦听域的
error
事件
- 将
req
和res
添加到域中(因为它们是在域存在之前创建的)
run
域并调用您的请求处理程序(这类似于try
/catch
的try
部分)
像这样:
var domain = require('domain');
function handleRequest(req, res) {
setTimeout(function() {
throw Error("Some random async error");
res.end("Hello world!");
}, 100);
}
var server = require("http").createServer(function (req, res) {
var d = domain.create();
d.on('error', function(err) {
server.close();
res.statusCode = 500;
res.setHeader("content-type", "text/plain");
res.end("Server error: " + err.message);
});
d.add(req);
d.add(res);
d.run(function() {
handleRequest(req, res);
});
}).listen(8080);
在错误后重启进程
通过使用cluster
模块,您可以在错误后很好地重新启动进程。我基本上是从节点文档中复制了一个示例,在主进程中启动多个工作进程。工作进程是处理传入连接的进程。如果其中一个进程有不可恢复的错误(即我们在前一节中捕获的错误),那么它将与主进程断开连接,发送500响应并退出。当主进程看到工作进程断开连接时,它将知道发生了错误并启动一个新的工作进程。由于同时运行多个工作进程,如果其中一个进程失败,不会出现丢失传入连接的问题。
示例代码,从这里复制:
var cluster = require('cluster');
var PORT = +process.env.PORT || 1337;
if (cluster.isMaster) {
cluster.fork();
cluster.fork();
cluster.on('disconnect', function(worker) {
console.error('disconnect!');
cluster.fork();
});
} else {
var domain = require('domain');
var server = require('http').createServer(function(req, res) {
var d = domain.create();
d.on('error', function(er) {
console.error('error', er.stack);
try {
var killtimer = setTimeout(function() {
process.exit(1);
}, 30000);
killtimer.unref();
server.close();
cluster.worker.disconnect();
res.statusCode = 500;
res.setHeader('content-type', 'text/plain');
res.end('Oops, there was a problem!\n');
} catch (er2) {
console.error('Error sending 500!', er2.stack);
}
});
d.add(req);
d.add(res);
d.run(function() {
handleRequest(req, res);
});
});
server.listen(PORT);
}
function handleRequest(req, res) {
switch(req.url) {
case '/error':
setTimeout(function() {
flerb.bark();
});
break;
default:
res.end('ok');
}
}
注意:我仍然强调您应该查看domain
模块文档,并查看其中的示例和说明。它解释了大部分,如果不是全部,关于此内容的原因以及您可能遇到的其他情况。
domain
模块。 - Paramore