如何正确关闭Node.js Express服务器?

121

我需要在从/auth/github/callback URL获取回调后关闭服务器。使用通常的HTTP API,关闭服务器目前支持server.close([callback]) API函数,但是使用node-express服务器时,我遇到了TypeError: Object function app(req, res){ app.handle(req, res); } has no method 'close'错误。我不知道如何找到解决这个问题的信息。
我应该如何关闭express服务器?

NodeJS配置说明:

$ node --version
v0.8.17
$ npm --version
1.2.0
$ npm view express version
3.0.6

实际应用代码:

var app = express();

// configure Express
app.configure(function() {
    // … configuration
});

app.get(
    '/auth/github/callback',
    passport.authenticate('github', { failureRedirect: '/login' }),
    function(req, res) {
        res.redirect('/');

        setTimeout(function () {
            app.close();
            // TypeError: Object function app(req, res){ app.handle(req, res); } has no method 'close'
        }, 3000)
    }
);

app.listen('http://localhost:5000/');

另外,我找到了‘nodejs express close…’,但我不确定是否可以与我已有的代码一起使用:var app = express();


我正在将一个表达式转换为单元测试。在这个范围内,close()不会停止服务器。 - JRichardsz
@JRichardsz,也许API在过去的8年中已经发生了变化。 - Vladimir Starkov
@JRichardsz 请参考 https://dev59.com/KGUq5IYBdhLWcg3waPzr#21739334。 - Vladimir Starkov
我认为问题是由于mocha引起的。我将准备一个最小可运行示例。我的解决方法是process.exit(0)。 - JRichardsz
@JRichardsz 这不是mocha。使用process.exit(0)来解决问题,就像阿司匹林对头痛一样。 - Vladimir Starkov
我想说的是,在我的情况下,我正在使用Mocha测试中的Express。在这种特殊情况下,close()不起作用。只有process.exit(0)可以工作 :( - JRichardsz
7个回答

148

app.listen() 返回 http.Server。您应在该实例上调用 close(),而不是在 app 实例上调用。

示例:

app.get(
    '/auth/github/callback',
    passport.authenticate('github', { failureRedirect: '/login' }),
    function(req, res) {
        res.redirect('/');

        setTimeout(function () {
            server.close();
            // ^^^^^^^^^^^
        }, 3000)
    }
);

var server = app.listen('http://localhost:5000/');
// ^^^^^^^^^^

您可以检查源代码:/node_modules/express/lib/application.js


7
奇怪,如果我访问某些路由,它就不会关闭。 - Uday Hiwarale
1
@Uday,我也遇到过这个问题。我发现我的浏览器有一个HTTP1.1持久连接打开,所以当我按F5时,似乎服务器没有关闭。如果你尝试使用另一个浏览器,你会发现服务器套接字没有运行。 - Rick Velde
1
那是我的错误,我在调用app而不是server上的close方法。解决了我的问题。非常感谢。 - Edison Spencer
为什么会超时? - João Pimentel Ferreira
@JoãoPimentelFerreira,看起来Vladimir只是按照OP给出的示例,在3秒后关闭了。 - Oleg Valter is with Ukraine

64

在 Express v3 中,他们移除了这个函数。

你仍然可以通过给 app.listen() 函数的结果进行赋值,并对其应用 close 方法来实现相同的功能:

var server = app.listen(3000);
server.close((err) => {
  console.log('server closed')
  process.exit(err ? 1 : 0)
})

https://github.com/visionmedia/express/issues/1366


使用类似 https://github.com/gajus/http-terminator 的工具来确保在存在持久连接或请求未产生响应的情况下终止服务器。 - Gajus
short and simple - krupesh Anadkat

14

如果您的express应用程序出现任何错误,则必须关闭服务器,并且可以像下面这样执行:

var app = express();
var server = app.listen(process.env.PORT || 5000)
如果出现任何错误,我们的应用程序将收到一个名为SIGTERM 的信号。您可以在此处阅读更多关于 SIGTERM 的内容- https://www.gnu.org/software/libc/manual/html_node/Termination-Signals.html
process.on('SIGTERM', () => {
  console.info('SIGTERM signal received.');
  console.log('Closing http server.');
  server.close((err) => {
    console.log('Http server closed.');
    process.exit(err ? 1 : 0);
  });
});

1
抱歉,这与问题是关于手动服务器关闭,而不是错误处理无关。 - Vladimir Starkov
你确定 server.close 有回调函数吗?我在文档中没有看到它。 - João Pimentel Ferreira
@JoãoPimentelFerreira - 是的,它确实在这里:(https://nodejs.org/dist/latest-v16.x/docs/api/http.html#http_server_close_callback)。 - Oleg Valter is with Ukraine
@OlegValter 谢谢,但我读到了 expressJs 服务器不再继承自 nodejs http-server。 - João Pimentel Ferreira
1
@JoãoPimentelFerreira,上次我检查的时候,app.listen 调用依旧返回一个 http.Server 对象并且(对于 HTTP)是以下方便方法的快捷方式。 - Oleg Valter is with Ukraine

4

我在不同的 支持渠道上多次回答了“如何终止HTTP服务器”的变化问题。不幸的是,由于它们在某些方面存在 缺陷,我无法推荐任何现有的库。此后,我已经组合了一个软件包,(我相信)它可以处理所有优雅的express.js HTTP(S)服务器终止所期望的情况。

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

http-terminator的主要优点是:

  • 它不会对Node.js API进行猴子补丁
  • 它可以立即销毁所有没有附加HTTP请求的套接字
  • 它允许对正在进行的HTTP请求的套接字进行优雅的超时处理
  • 它可以正确地处理HTTPS连接
  • 通过设置connection: close头,它通知使用keep-alive的连接服务器正在关闭
  • 它不会终止Node.js进程

14
这仍然是一个有效的问题。 - Gajus

3

虽然这是一个老问题,但现在 Node v18.2.0 introducedserver.closeAllConnections() 方法。需要注意的是,当浏览器发送请求 Connection: keep-alive 时,server.close 永远不会运行其回调函数,因为 server.close 只停止服务器接受新连接,而不关闭旧连接。

在 Node v18.2.0 之前,我通过等待 5 秒钟让服务器关闭,然后强制退出来解决这个问题。

以下代码包含了两种情况:

process.on('SIGINT', gracefulShutdown)
process.on('SIGTERM', gracefulShutdown)

function gracefulShutdown (signal) {
  if (signal) console.log(`\nReceived signal ${signal}`)
  console.log('Gracefully closing http server')

  try {
    server.close(function (err) {
      if (err) {
        console.error('There was an error', err.message)
        process.exit(1)
      } else {
        console.log('http server closed successfully. Exiting!')
        process.exit(0)
      }
    })

    // closeAllConnections() is only available from Node v18.02
    if (server.closeAllConnections) server.closeAllConnections()
    else setTimeout(() => process.exit(0), 5000)

  } catch (err) {
    console.error('There was an error', err.message)
    setTimeout(() => process.exit(1), 500)
  }
}

我认为closeAllConnections()必须在调用close()之后被调用。 - Welcor
@Welcor 为什么这么说呢?在关闭服务器之前,我们不是应该先关闭连接吗? - João Pimentel Ferreira
close() 停止接受新的连接,但会保持所有现有连接。只要存在连接,它就不会关闭。我认为,在调用 close() 之前调用 closeAllConnections() 存在一个可能性,即在 closeAll..() 和 close() 之间创建了一个连接。然后,close() 将不会关闭,直到此连接自行关闭。 - Welcor
@Welcor 我不这么认为,如果你在调用 close 之前没有先调用 closeAllConnections,如果有一个挂起的连接(例如等待响应),server 将永远不会触发其回调函数。这就是为什么在 Node 18 之前,你必须在超时后强制退出的原因。 - João Pimentel Ferreira
在close()之后,未完成的连接将被closeAllConnections()关闭。然后触发close()回调函数。时间线:close() -> closeAllConnections() -> 触发close()回调函数。 - Welcor

2
调用server.close即可完成任务。
server.close((err) => {
  console.log('server closed')
  process.exit(err ? 1 : 0)
})

此外,也要注意监听系统(用户)信号并在收到信号时优雅地关闭程序。为此,您需要同时监听 SIGTERMSIGINT 信号。

const port = process.env.PORT || 5000;
const server = app.listen(port);
console.log(`listening on port:${port}`);
for (let signal of ["SIGTERM", "SIGINT"])
    process.on(signal, () => {
        console.info(`${signal} signal received.`);
        console.log("Closing http server.");
        server.close((err) => {
            console.log("Http server closed.");
            process.exit(err ? 1 : 0);
        });
    });


0

大多数答案都调用process.exit(),但我认为这不是一个好主意。您可能需要执行一些拆卸工作,而且它根本不必要。

const server = app.listen(port);

server.on('close', () => {
  // Perform some teardown, for example with Knex.js: knex.destroy()
});

// FYI Docker "stop" sends SIGTERM
// If SIGTERM is not catched, Docker is forced to kill the container after ~10s
// and this provokes an exit code 137 instead of 0 (success)
process.on('SIGTERM', () => server.close());

请查看 Express.js 4.x 文档: https://expressjs.com/en/advanced/healthcheck-graceful-shutdown.html#graceful-shutdown


我认为您错过了问题中的第一句话:“在从 /auth/github/callback URL 获取回调后,我需要关闭服务器。” - Vladimir Starkov

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