本文是在阮一峰老师的科技爱好者周刊:第 96 期中介绍的文章。平时都是 Ctrl + C
简单粗暴来解决,所以来看一下正确关闭 Node.js 程序的姿式。javascript
英文水平有限,若有错误之处还请指正。html
原文地址: Shutdown correctly Node.js apps前端
正确地关闭应用,并防止其处理新的请求是很是重要的。我将以一个服务器应用为例子展开。java
const http = require('http');
const server = http.createServer(function (req, res) {
setTimeout(function () {
res.writeHead(200, {'Content-Type': 'text/plain'});
res.end('Hello World\n');
}, 4000);
}).listen(9090, function (err) {
console.log('listening http://localhost:9090/');
console.log('pid is ' + process.pid);
});
复制代码
正如咱们所见,服务器没有正确地被关闭,而且请求也没有获得正确的处理。那么为了处理这个问题,首先咱们须要明白 Node.js 是如何关闭进程的。node
当关闭进程时,进程会收到一个信号。这些信号有许多种类,在这里咱们主要关心下面三种。linux
当进程收到这些信号时,Node.js 会触发事件。所以咱们就能够对这些事件编写对应的方法。在下面这个例子中咱们将关闭服务器,它会处理完那些被挂起的请求而且不会再接受新的请求。git
// ...
function handleExit(signal) {
console.log(`Received ${signal}. Close my server properly.`)
server.close(function () {
process.exit(0);
});
}
process.on('SIGINT', handleExit);
process.on('SIGQUIT', handleExit);
process.on('SIGTERM', handleExit);
复制代码
如今咱们能够很好的处理请求并正确地关闭服务器。你能够在 Nairi Harutyunyan 的 文章 中找到更多信息。文章对于如何正确地关闭带有数据库的服务器作了详细解释。github
如今还有名为 Death 的 npm 包能够处理这些逻辑。web
const ON_DEATH = require('death')
ON_DEATH(function(signal, err) {
// clean up code here
})
复制代码
最近遇到了这样的场景,个人服务器会接受一个请求来关闭服务器。接下来我会举一些例子。我为服务器订阅了一个 webhook。而这个 webhook 所接受的订阅有限。那么当我不想超出订阅数量时,那我就须要正确地关闭服务器并取消订阅。整个步骤以下:数据库
当进程的事件循环为空时,程序就会自动关闭。正如上面的 一、2 步所示,若是事件循环为空,则进程将会终止,咱们也将没法成功取消订阅。
下面将是新的服务器代码:
const server = http.createServer(function (req, res) {
const params = qs.decode(req.url.split("?")[1]);
if (params.mode) {
res.writeHead(200);
res.write(params.challenge);
res.end();
} else {
let body = "";
req.on("data", chunk => {
body += chunk;
});
req.on("end", () => {
console.log('event', JSON.parse(body))
res.writeHead(200);
res.end();
});
}
}).listen(9090, function (err) {
console.log('listening http://localhost:9090/');
console.log('pid is ' + process.pid);
fetch('http://localhost:3000/webhook?mode=subscribe&callback=http://localhost:9090')
});
复制代码
这段代码里有两个变化。当咱们在启动服务器时,会向 webhook 服务器发送一个订阅请求。
// ...
fetch('http://localhost:3000/webhook?mode=subscribe&callback=http://localhost:9090')
// ...
复制代码
同时咱们还在服务器处理请求的方法中添加一个 challenge
的 token,来确认代表 response 来自于 webhook。
// ...
if (params.mode) {
res.writeHead(200);
res.write(params.challenge);
res.end();
} else {
// ...
}
// ...
复制代码
让咱们再来修改一下 handleExit
方法,让它向 webhook 发送一个请求,告诉它取消订阅。
function handleExit(signal) {
console.log(`Received ${signal}. Close my server properly.`)
fetch('http://localhost:3000/webhook?mode=unsubscribe&callback=http://localhost:9090')
}
复制代码
咱们须要添加代码让服务器在 webhook 取消订阅时可以关闭。
// ...
if (params.mode) {
res.writeHead(200);
res.write(params.challenge);
res.end();
if (params.mode === 'unsubscribe') {
server.close(function () {
process.exit(0);
});
}
} else {
// ...
}
// ...
复制代码
当 webhook 确认取消订阅时,咱们将关闭服务器,这样一来它就不会再处理新的请求并正常地退出。而这也是 Twitch API 中 webhook 的 subscription/unsubscription 的工做原理。
让咱们看一下实际关闭服务器时的状况。我添加了一些 log 让其更容易理解。
如图所示,服务器并不能正常关闭。服务器的进程在收到 webhook 返回的请求以前就关闭了,所以 webhook 仍然会往服务器发送请求。
为了解决这个问题,咱们须要防止 Node.js 进程退出。咱们可使用 process.stdin.resume 方法。这个方法会使进程暂停并覆盖默认方法,好比 Ctrl + C。
注:我本身跑了一下代码,即便不加 process.stdin.resume 也能够正常取消订阅,但若是不添加 handleExit,就会出现没法取消订阅的状况。
const http = require('http');
process.stdin.resume();
// ...
复制代码
那么如今再来看一下?
如今的服务器会在处理完请求后退出。
我建立了一个 Git 的 repo 上面有文中所提到的代码。
但愿能有所帮助。
欢迎关注个人公众号:此方的手帐。一个与你分享生活、共同进步的公众号。除了前端还有高达系列的分享哦~