【译】正确地关闭 Node.js app

本文是在阮一峰老师的科技爱好者周刊:第 96 期中介绍的文章。平时都是 Ctrl + C 简单粗暴来解决,所以来看一下正确关闭 Node.js 程序的姿式。javascript

英文水平有限,若有错误之处还请指正。html


正文:正确地关闭 Node.js app

原文地址: 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

  • SIGINT: 来自键盘 Ctrl + C 的组合(中断进程)
  • SIGQUIT: 来自键盘 Ctrl + \(关闭进程)这个操做同时也会生成一个 core 文件
  • SIGTERM: 经过系统操做退出(例如使用 kill 命令)

当进程收到这些信号时,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 所接受的订阅有限。那么当我不想超出订阅数量时,那我就须要正确地关闭服务器并取消订阅。整个步骤以下:数据库

  1. 向 webhook 发送取消订阅的请求
  2. webhook 则会向服务器发送确认收到取消订阅的请求
  3. 服务器通过校验,而后取消订阅

当进程的事件循环为空时,程序就会自动关闭。正如上面的 一、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 上面有文中所提到的代码。

但愿能有所帮助。


欢迎关注个人公众号:此方的手帐。一个与你分享生活、共同进步的公众号。除了前端还有高达系列的分享哦~

相关文章
相关标签/搜索