NodeJS 正常关闭节点HTTP服务器- server.close回调未触发

mefy6pfw  于 12个月前  发布在  Node.js
关注(0)|答案(1)|浏览(117)

我在内置的http.Server上实现了一个node.js(v20.4.0)服务器。在收到SIGTERMSIGINT后,我希望服务器停止接受新连接,完成所有正在进行的请求,然后在所有正在进行的请求完成后退出。
下面是我的服务器如何设置的简化版本(server.js):

const http = require("http");

const requestListener = (_, res) => {
    res.writeHead(200);
    res.end("howdy!");
};

const server = http.createServer(requestListener);
const host = 'localhost'
const port = 8888

const shutdown = () => {
  console.log(`Got SIGINT, calling server.close() ...`);
  server.close(() => {
    console.log('Server closed, exiting')
    process.exit(0)
  })
}

process.on("SIGINT", shutdown)

server.listen(port, host, () => {
    console.log(`Listening on http://${host}:${port}`);
});

如果我在一个终端中运行这个命令,然后按ctrl-c发送一个SIGINT,它就像预期的那样工作。但是,以下事件序列将导致close的回调永远不会被调用:
1.启动服务器(node server.js
1.在Chrome选项卡中打开http://localhost:8888
1.在启动服务器的终端窗口上按ctrl-c
我看到了“Got SIGINT”消息,但传递给server.close的回调函数从未触发(至少在我退出Chrome之前没有)。
server.close()的文档说:
停止服务器接受新的连接,并关闭所有连接到该服务器的连接,这些连接没有发送请求或等待响应。
Chrome做了什么让node认为仍然有一个连接打开,这是“发送请求或等待响应”?

  • 编辑 *

已通过Chrome开发工具确认Chrome正在发送包含请求的Connection: keep-alive标头,并且节点服务器正在响应以下内容:

HTTP/1.1 200 OK
Date: Fri, 06 Oct 2023 21:08:23 GMT
Connection: keep-alive
Keep-Alive: timeout=5
Transfer-Encoding: chunked
  • 编辑2*

在我看来,我遇到了https://github.com/nodejs/node/issues/41578中描述的问题。server.closeIdleConnections方法(通过https://github.com/nodejs/node/pull/42812添加到节点18.2.0中)旨在解决此问题,但在我的情况下似乎实际上不起作用。我想找一个不涉及Chrome本身的复制案例。

h4cxqtbf

h4cxqtbf1#

当客户端发送一个(告诉它支持的服务器保持活动连接)

Connection: keep-alive

头,服务器响应为

Connection: keep-alive
Keep-Alive: timeout=5s

连接不会关闭,直到超时时间过去。server.close()将等待这些打开的连接关闭。所以如果你
1.启动服务器
1.从您的Chrome向http://localhost:8888发送 * 单个 * 请求
1.立即向服务器进程发送SIGINT
服务器进程将等待这一个打开的连接关闭,然后终止服务器(即,在执行server.close的回调之前,大约需要5秒钟)
但如果你
1.启动服务器
1.从您的Chrome向http://localhost:8888发送 * 单个 * 请求
1.等待10秒,不从浏览器发送另一个请求
1.向服务器进程发送SIGINT
服务器将不再有任何打开的连接(因为keep-alive超时已经过去),并将立即终止服务器(当然也会执行server.close()的回调)
一个问题似乎是:医生只说:
停止服务器接受新连接[…]
但是只要现有的连接仍然是打开的(即保持活动超时还没有过去),即使server.close()已经被调用,服务器也会通过打开的连接继续接受请求。为什么,因为服务器只停止接受新的连接,但不接受打开的连接上的新请求。如果你有一些东西通过这样一个开放的连接定期查询你的服务器,它永远不会被关闭。
您可以尝试以下操作
1.启动服务器
1.发送请求到http://localhost:8888
1.发送SIGINT到您的服务器
1.在Chrome中快速刷新页面
1.继续按f5刷新页面
服务器不会终止。只有在您停止刷新页面后,它才会在5秒后终止。
解决这种不必要的行为的方法可能如下

let shouldCloseConnection = false;
const requestListener = (_, res) => {
    if (shouldCloseConnection) {
        res.setHeader('Connection', 'close')
    }
    res.writeHead(200);
    res.end("howdy!");
};

...

const shutdown = () => {
  console.log(`Got SIGINT, calling server.close() ...`);
  shouldCloseConnection = true;
  server.close(() => {
    console.log('Server closed, exiting')
  })
}

也就是说,一旦服务器收到SIGINT信号,它就停止发送Connection: keep-alive,而是发送Connection: close。这将导致在当前请求完成后关闭连接。因此,所有打开的连接将在超时后或下一个请求后关闭,以先发生者为准。
这样,你甚至不需要在回调中使用process.exit(0)。当然,除非你有其他开放的计时器/承诺/连接。仍在等待完成。

相关问题