如何立即关闭 Node.js 的 http(s) 服务器?

71

我有一个包含http(s)服务器的Node.js应用程序。

在某种情况下,我需要以编程方式关闭此服务器。目前我正在调用它的close()函数,但这并没有帮助,因为它会等待任何保持活动状态的连接先完成。

所以,基本上这会关闭服务器,但只有在最少等待120秒后才会关闭。但是我想要立即关闭服务器——即使这意味着中断当前处理的请求。

我不能做的事情很简单。

process.exit();

由于服务器仅是应用程序的一部分,其他部分应该保持运行。我要找的是在概念上类似于server.destroy();之类的东西。

我该如何实现?

PS:通常需要连接保持活动超时时间,因此减少此时间不是可行的选择。


2
这个问题在Node中存在:https://github.com/nodejs/node/issues/2642 - hunterloftis
11个回答

83

关键是您需要订阅服务器的connection事件,以获取新连接的socket 。您需要记住此socket,并在调用server.close()后直接使用socket.destroy()销毁该socket。

此外,您需要监听socket的close事件,以便在它自然离开时从数组中删除它,因为它的keep-alive超时时间到了。

我编写了一个小示例应用程序,您可以使用它来演示这种行为:

// Create a new server on port 4000
var http = require('http');
var server = http.createServer(function (req, res) {
  res.end('Hello world!');
}).listen(4000);

// Maintain a hash of all connected sockets
var sockets = {}, nextSocketId = 0;
server.on('connection', function (socket) {
  // Add a newly connected socket
  var socketId = nextSocketId++;
  sockets[socketId] = socket;
  console.log('socket', socketId, 'opened');

  // Remove the socket when it closes
  socket.on('close', function () {
    console.log('socket', socketId, 'closed');
    delete sockets[socketId];
  });

  // Extend socket lifetime for demo purposes
  socket.setTimeout(4000);
});

// Count down from 10 seconds
(function countDown (counter) {
  console.log(counter);
  if (counter > 0)
    return setTimeout(countDown, 1000, counter - 1);

  // Close the server
  server.close(function () { console.log('Server closed!'); });
  // Destroy all open sockets
  for (var socketId in sockets) {
    console.log('socket', socketId, 'destroyed');
    sockets[socketId].destroy();
  }
})(10);

基本上,它的作用是启动一个新的HTTP服务器,在10秒内从10数到0,然后关闭服务器。如果没有建立连接,则服务器立即关闭。

如果已经建立连接且仍然打开,则连接会被终止。 如果已经自然死亡,则仅在那个时间点打印出一条消息。


2
socket.on("close",...) 应该改为 socket.once("close",...) 吗? - jpillora
1
或者,如下面的评论所提到的,使用 https://github.com/hunterloftis/stoppable - 它可能比上面的代码更好用,并且可以节省你的墨水! - Izhaki
1
答案可以更新:sockets 变量可以使用 Set,像这样 const sockets = new Set(); sockets.add(socket); sockets.forEach((socket) => socket.destroy()); - Alexander Shutau
1
要是我能给你一百万个赞就好了。我写了一个小 Express.js 测试程序,当加载包含外部 JavaScript 并使用 JSDOM 解析它们的页面时,它变得非常缓慢。我认为 JSDOM 进行了一些保持连接的花招。但是按照你所建议的销毁套接字可以解决这个问题。 - Paul D. Waite

24

我找到了一种方法,可以在不追踪连接或强制关闭它们的情况下完成此操作。我不确定这在Node版本之间的可靠性如何,或者是否存在任何负面后果,但对于我所做的事情来说,它似乎完美地工作。诀窍是在调用close方法后立即使用setImmediate触发“close”事件。代码示例如下:

server.close(callback);
setImmediate(function(){server.emit('close')});

对我来说,这样做最终会释放端口,以便在调用回调函数时可以启动新的HTTP(S)服务(几乎瞬间完成)。现有的连接保持打开状态。我使用这种方法在更新Let's Encrypt证书后自动重新启动HTTPS服务。


根据您的回答,我使用 server.close(); setImmediate(callback); - adabru
你能详细说明一下你在这里做什么以及为什么它有效吗?在我的看法中,调用server.close()并在回调函数中打开一个新的服务器将允许您保留现有连接同时监听新连接。你的代码示例似乎有些不可靠...你不应该像这样在服务器上发出事件,所以我很好奇你是如何得出这个结论的。 - Brad
@Brad,我已经坦诚使用这个的可疑性了。那只是我当时试图解决回调不会被调用直到所有连接关闭的问题(正如问题和其他答案所证明的那样)的一个想法。当然,我本来可以像你上面的评论建议的那样直接使用 setImmediate 的回调,但这并不能保证不会出现问题,因为一些重要的清理操作可能依赖于关闭事件。因此,我决定发出事件并不是一个坏主意。 - Jonathan Gray
非常好,它能够正常工作,并且不需要跟踪连接并确保生产性能不受影响。 - MyUserInStackOverflow

14

如果您需要在关闭服务器后仍然保持进程运行,那么 Golo Roden 的解决方案可能是最好的。

但是,如果您正在关闭服务器作为进程优雅关闭的一部分,则只需使用以下内容:

var server = require('http').createServer(myFancyServerLogic);

server.on('connection', function (socket) {socket.unref();});
server.listen(80);

function myFancyServerLogic(req, res) {
    req.connection.ref();

    res.end('Hello World!', function () {
        req.connection.unref();
    });
}

基本上,您的服务器使用的套接字仅在它们实际提供请求时保持进程活动状态。当它们因为Keep-Alive连接而闲置时,只要没有其他东西使进程保持活动状态,对server.close()的调用将关闭该进程。如果您需要在服务器关闭后执行其他操作以完成优雅的关闭过程,则可以钩入process.on('beforeExit', callback)来完成您的优雅关闭过程。


1
这看起来是个好主意,但不知为何在 Node v7.10.0 和 Express 4.15.2 上无法运行。你测试过吗? - Slava Fomin II
刚刚在使用Node 14时对我很有效,但我没有使用任何框架。这太棒了,谢谢! - Alberto González Palomo

8
该库提供了一种简单的方式来使用destroy()关闭服务器,并在服务器销毁时跟踪打开的连接并销毁它们,如其他答案所述。库的地址是:https://github.com/isaacs/server-destroy

2
一个维护良好的替代方案,https://github.com/thedillonb/http-shutdown 和 https://github.com/hunterloftis/stoppable。 - Gajus
2
我已经开发了https://github.com/gajus/http-terminator。与其他库不同,`http-terminator`不会对HTTP服务器实例进行monkey-patch。 - Gajus

6

正如其他人所说的那样,解决方案是跟踪所有打开的套接字并手动关闭它们。我的Node包killable可以为您完成此操作。以下是一个示例(使用express,但您可以在任何http.server实例上调用use killable):

var killable = require('killable');

var app = require('express')();
var server;

app.route('/', function (req, res, next) {
  res.send('Server is going down NOW!');

  server.kill(function () {
    //the server is down when this is called. That won't take long.
  });
});

var server = app.listen(8080);
killable(server);

3

另一个用于执行关闭连接的nodejs包是http-shutdown,在编写本文时(2016年9月)似乎依然得到良好维护,并可在NodeJS 6.x上正常工作。

根据文档:

使用方法

目前有两种使用该库的方式。第一种是显式地包装Server对象:

// Create the http server
var server = require('http').createServer(function(req, res) {
  res.end('Good job!');
});

// Wrap the server object with additional functionality.
// This should be done immediately after server construction, or before you start listening.
// Additional functionailiy needs to be added for http server events to properly shutdown.
server = require('http-shutdown')(server);

// Listen on a port and start taking requests.
server.listen(3000);

// Sometime later... shutdown the server.
server.shutdown(function() {
  console.log('Everything is cleanly shutdown.');
});

第二个是将原型功能隐式添加到Server对象中:
// .extend adds a .withShutdown prototype method to the Server object
require('http-shutdown').extend();

var server = require('http').createServer(function(req, res) {
  res.end('God job!');
}).withShutdown(); // <-- Easy to chain. Returns the Server object

// Sometime later, shutdown the server.
server.shutdown(function() {
  console.log('Everything is cleanly shutdown.');
});

2
我已经在不同的支持渠道上回答了很多关于“如何终止HTTP服务器”的变体问题。不幸的是,我无法推荐任何现有的库,因为它们在某种程度上存在缺陷。自那以后,我已经编写了一个包,(我相信)可以处理所有优雅的HTTP服务器终止所需的情况。

https://github.com/gajus/http-terminator

http-terminator 的主要优点有:
  • 它不会对 Node.js API 进行 monkey-patch。
  • 它可以立即销毁所有没有附加 HTTP 请求的套接字。
  • 它可以对具有进行中的 HTTP 请求的套接字进行优雅的超时处理。
  • 它可以正确地处理 HTTPS 连接。
  • 通过设置 connection: close 标头,它通知使用 keep-alive 的连接服务器正在关闭。
  • 它不会终止 Node.js 进程。
用法:
import http from 'http';
import {
  createHttpTerminator,
} from 'http-terminator';

const server = http.createServer();

const httpTerminator = createHttpTerminator({
  server,
});

await httpTerminator.terminate();


2

我的最佳猜测是手动关闭连接(即强制关闭其套接字)。

理想情况下,应该通过深入服务器内部并手动关闭其套接字来完成此操作。 或者,可以运行一个shell命令来完成相同的操作(前提是服务器具有适当的特权等)。


1
问题是:我如何在不使用任何未经记录的行为的情况下访问连接的套接字? - Golo Roden
基本上,你的回答帮助我走上了正确的轨道。看看我的答案,以及我如何解决这个问题(而没有使用任何未经记录的行为 ;-))。无论如何,给你一个赞,因为你提供了正确的思路 :-) - Golo Roden
这就是为什么我之前说得比较含糊。这取决于你使用了哪些封装库(例如 Express)以及你使用的 Node.js/库的版本(API 随时间变化而有所不同)。 - Morten Siebuhr

0

const Koa = require('koa')
const app = new Koa()

let keepAlive = true
app.use(async (ctx) => {
  let url = ctx.request.url

  // destroy socket
  if (keepAlive === false) {
    ctx.response.set('Connection', 'close')
  }
  switch (url) {
    case '/restart':
      ctx.body = 'success'
      process.send('restart')
      break;
    default:
      ctx.body = 'world-----' + Date.now()
  }
})
const server = app.listen(9011)

process.on('message', (data, sendHandle) => {
  if (data == 'stop') {
    keepAlive = false
    server.close();
  }
})


0

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接