正确的关闭你的应用是非常重要的,它可以很好处理请求同时防止它接受新的请求。我以web服务器举例。
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进程。
进程在它即将被终止时会接收到一个信号。它们是各种不同的信号,我们将特别关注其中三个:
-
信号情报:键盘退出(Ctrl+C)。
终止信号:键盘退出(Ctrl +)。它还会生成一个核心转储文件。
软件终止:退出操作系统(例如使用kill命令)。
当进程接收到信号时,Node.js会发出事件。你可以为这些事件编写程序。在这一步中我们将关闭我们的服务器,以便处理正挂起的请求并防止接入新的请求。
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的文章中你可以了解到更多。它详细说明了如何正确地关闭带有数据库的服务器。
还存在一个node模块可以为你处理这种逻辑,你可以叫它“死亡”(由JP Richardson创造)。
const ON_DEATH = require('death')ON_DEATH(function(signal, err) { // clean up code here})
但这个可能是不够的
我最近遇到了一个情况,我的服务器需要接受新的请求,以便正确关机。我来详细解释一下,我的服务器订阅了一个webhook,这个webhook有订阅名额限制。因此,如果我不想超过此配额,则在服务器关闭时需要正确地取消订阅。这里是取消订阅的工作流:
向webhook发送一个请求取消订阅。
webhook向服务器发送一个请求来确认取消订阅。
服务器必须使用特定的令牌进行响应,以验证取消订阅。
一个Node.js进程会在它的事件循环为空时自动关闭,正如你所见到的在1和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')});
这里有两个改变,当服务器开始监听9090端口时,我们发送第一个请求使得我们的服务器订阅webhook。
//fetch('http://localhost:3000/webhook?mode=subscribe&callback=http://localhost:9090')//
我们还更改了服务器的请求处理程序,让它通过响应称为“challenge”的令牌来确认对webhook的订阅。
// ...if (params.mode) { res.writeHead(200); res.write(params.challenge); res.end();} else { // ...}// ...
改变handleExit函数来发送一个请求到我们的webhook。我们请求webhook取消我们服务器的订阅。
function handleExit(signal) { console.log(`Received ${signal}. Close my server properly.`) fetch('http://localhost:3000/webhook?mode=unsubscribe&callback=http://localhost:9090')}
我们需要更新代码,处理当webhook确认取消订阅时,服务器能通过“challenge”令牌来终止我们的进程。
// ...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 Webhooks订阅/取消订阅的工作方式。
让我们来看看当我们试图关闭服务器时服务器是如何工作的。我添加了一些日志,让它更加直观。
如您所见,它无法正常关闭。在我们收到确认取消订阅的webhook请求之前,服务器的进程已终止。因此,webhook不断将事件发送到我们的服务器。
要解决这个问题,我们需要防止Node.js进程退出。我们可以使用process.stdin.resume方法让进程暂停并且覆盖禁用默认行为(如退出时按Ctrl+C)。
const http = require('http');process.stdin.resume();// ...
那么现在呢?
不错,它现在在退出进程之前等待确认。
我用本文中提供的所有源代码创建了一个git仓库[链1]。希望能对你有所帮助。
[链1]:https://github.com/frinyvonnick/node-shutdown-properly-webhooks