发布于 2026-01-06 3 阅读
0

正确关闭 Node.js 应用

正确关闭 Node.js 应用

照片由Unsplash上的Aleksandar Cvetanovic拍摄


正确关闭应用程序至关重要,这样才能确保应用程序能够顺利处理请求并防止其接受新的请求。我以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);
});
Enter fullscreen mode Exit fullscreen mode

终止服务器时出错

正如我们所见,服务器无法正常关闭,处理请求也无法获得正确响应。首先,我们需要了解 Node 进程是如何终止的,才能解决这个问题。

当一个进程即将被终止时,它会收到一个信号。这些信号有很多种。我们将重点介绍其中的三种:

  • 信号情报:通过键盘退出(Ctrl + C)。
  • SIGQUIT:通过键盘退出(Ctrl + \)。它还会生成一个核心转储文件。
  • SIGTERM:退出操作系统(例如使用 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);
Enter fullscreen mode Exit fullscreen mode

妥善处理服务器崩溃问题

现在我们的服务器能够正确处理请求并正常关闭。您可以阅读Nairi Harutyunyan精彩文章了解更多信息。文章详细解释了如何正确关闭带有数据库的服务器。

此外,还有一个名为death的节点模块可以为你处理这种逻辑(由JP Richardson制作)。

const ON_DEATH = require('death')

ON_DEATH(function(signal, err) {
  // clean up code here
})
Enter fullscreen mode Exit fullscreen mode

有时这还不够。

最近我遇到一个问题,我的服务器需要接受新的请求才能正常关闭。我来解释一下。我的服务器订阅了一个 webhook。这个 webhook 的订阅数量有限制。所以,为了避免超出限制,我需要在服务器关闭时正确地取消订阅。以下是取消订阅的工作流程:

  1. 向 webhook 发送取消订阅请求
  2. webhook 向服务器发送请求以确认取消订阅
  3. 服务器必须返回一个特定的令牌来验证取消订阅。

如果 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')
});
Enter fullscreen mode Exit fullscreen mode

有两个变化。当服务器开始监听 9090 端口时,我们会发送第一个请求,将我们的服务器订阅到 webhook 中。

// ...

fetch('http://localhost:3000/webhook?mode=subscribe&callback=http://localhost:9090')

// ...
Enter fullscreen mode Exit fullscreen mode

我们还更改了服务器的请求处理程序,以便他可以通过回复名为 的令牌来确认对 webhook 的订阅challenge

// ...

if (params.mode) {
  res.writeHead(200);
  res.write(params.challenge);
  res.end();
} else {
 // ...
}

// ...
Enter fullscreen mode Exit fullscreen mode

让我们修改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')
}
Enter fullscreen mode Exit fullscreen mode

我们需要更新代码,以便在 webhook 确认取消订阅时,能够响应并终止服务器进程的挑战。

// ...

if (params.mode) {
  res.writeHead(200);
  res.write(params.challenge);
  res.end();
  if (params.mode === 'unsubscribe') {
    server.close(function () {
      process.exit(0);
    });
  }
} else {
  // ...
}

// ...
Enter fullscreen mode Exit fullscreen mode

当 webhook 确认取消订阅后,我们会关闭服务器,使其停止接收新的请求并正常退出进程。这就是Twitch API webhook 订阅/取消订阅的工作原理。

我们来看看尝试关闭服务器时服务器的运行情况。我添加了一些日志,以便更直观地展示情况。

使用 webhook 终止服务器时出错

如您所见,服务器并未正常关闭。在收到确认取消订阅的 webhook 请求之前,服务器进程就被终止了。因此,webhook 会持续向我们的服务器发送事件。

为了解决这个问题,我们需要阻止 Node.js 进程退出。我们可以使用process.stdin.resume使进程暂停的方法,并覆盖禁用默认行为,例如在特定情况下退出Ctrl+C

const http = require('http');

process.stdin.resume();

// ...
Enter fullscreen mode Exit fullscreen mode

现在呢?

使用 webhook 正确终止服务器时出错

很好,现在它会在退出进程前等待确认。

我创建了一个存储库,其中包含了本文中提到的所有资源。

希望对你有帮助🙌


非常感谢您的反馈🙏 如果您有任何问题,请在推特上联系我@YvonnickFrin

文章来源:https://dev.to/yvonnickfrin/shutdown- Correctly-node-js-app-39jc