【问题标题】:Abandoned http requests after server.close()?在 server.close() 之后放弃了 http 请求?
【发布时间】:2022-12-09 02:52:27
【问题描述】:

我有一个像这样的普通 nodejs 服务器:

let someVar // to be set to a Promise

const getData = url => {
  return new Promise((resolve, reject) => {
    https.get(
      url,
      { headers: { ...COMMON_REQUEST_HEADERS, 'X-Request-Time': '' + Date.now() } },
      res => {
        if (res.statusCode === 401) return reject(new RequestError(INVALID_KEY, res))
        if (res.statusCode !== 200) return reject(new RequestError(BAD_REQUEST, res))

        let json = ''
        res.on('data', chunk => json += chunk)
        res.on('end', () => {
          try {
            resolve(JSON.parse(json).data)
          } catch (error) {
            return reject(new RequestError(INVALID_RESPONSE, res, json))
          }
        })
      }
    ).on('error', error => reject(new RequestError(FAILED, error)))
  })
}
const aCallback = () =>
  console.log('making api call')
  someVar = getData('someApiEndpoint')
    .then(data => { ... })
}
const main = () => {
  const server = http.createServer(handleRequest)
  anInterval = setInterval(aCallback, SOME_LENGTH_OF_TIME)

  const exit = () => {
    server.close(() => process.exit())
    log('Server is closed')
  }

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

  process.on('uncaughtException', (err, origin) => {
    log(`Process caught unhandled exception ${err} ${origin}`, 'ERROR')
  })
}
main()

我遇到了一种情况,我会 ctrl-c 并且会看到 Server is closed 日志,然后是我的命令提示符,但随后我会看到更多日志打印出来,表明正在进行更多 api 调用.

exit()内调用clearInterval(anInterval)(在server.close()之前)似乎已经解决了即使服务器关闭时间隔仍在继续的问题,所以这很好。但:

来自这些节点docs

关闭连接到此服务器的所有连接没有发送请求或等待响应.

也就是说,我假设 server.close() 不会自动终止 http 请求。

当我的计算机/节点不再跟踪变量someVar 时,http 响应信息会发生什么变化?

没有专门杀死发出 http 请求(并正在等待响应)的线程的后果是什么?

是否有取消请求的最佳做法?

这包括什么(即我最终会告诉 API 的服务器“没关系,请不要发送任何东西”,或者我会指示节点不接收任何新信息)?

【问题讨论】:

    标签: node.js http


    【解决方案1】:

    您应该注意几件事。首先,处理 SIGINT 在软件中是一件复杂的事情。接下来,您永远不需要调用process.exit(),因为节点总是会在准备就绪时退出。如果您的流程没有正确退出,则意味着您需要停止“正在完成的工作”。一旦没有更多工作要做,节点将自行安全退出。这最好用例子来解释。让我们从这个简单的程序开始:

    const interval = setInterval(() => console.log('Hello'), 5000);
    

    如果你运行这个程序然后按Ctrl+C(它发送 SIGINT 信号),节点会自动为你清除间隔并退出(好吧......它更像是一个“致命”退出,但这超出了这个答案的范围)。一旦您监听 SIGINT 事件,这种自动退出行为就会改变:

    const interval = setInterval(() => console.log('Hello'), 5000);
    
    process.on('SIGINT', () => {
      console.log('SIGINT received');
    });
    

    现在,如果你运行这个程序并按下Ctrl+C, 你会看到“收到信号”消息,但进程永远不会退出。当您侦听 SIGINT 时,您是在告诉节点“嘿,在您退出之前我需要清理一些东西”。 Node 将在退出之前等待任何“正在进行的工作”完成。如果节点最终没有自行退出,它会告诉你“嘿,我可以看到有些东西还在运行 - 你需要在我退出之前停止它们”。

    让我们看看如果我们清除间隔会发生什么:

    const interval = setInterval(() => console.log('Hello'), 5000);
    
    process.on('SIGINT', () => {
      console.log('SIGINT received');
      clearInterval(interval);
    });
    

    现在,如果你运行这个程序并按下Ctrl+C, 你会看到“收到信号”消息,该过程将很好地退出。一旦我们清除间隔,node 就足够聪明,可以看到没有任何事情发生,它就会退出。这里的重要教训是,如果您监听SIGINT,则您需要等待任何任务完成,并且您永远不需要调用process.exit()

    就这与您的代码的关系而言,您正在进行三件事:

    • http 服务器监听请求
    • 一个区间
    • 传出 https.get 请求

    当您的程序退出时,您需要清理上述项目。在最简单的情况下,您应该执行以下操作:

    • 关闭服务器:server.close();
    • 清除间隔:clearInterval(anInterval);
    • 销毁任何传出请求:request.destroy()

    您可能决定在关闭服务器之前等待所有传入请求完成,或者您可能希望监听传出请求的“关闭”事件以检测任何丢失的连接。那是你的事。您应该阅读 node http docs 中可用的方法和事件。希望现在您已经开始了解 SIGINT 在软件中是一个多么复杂的问题。祝你好运。

    【讨论】:

    • 谢谢 Ryan - 很好奇将中止控制器信号附加到请求和调用 .abort() 与 request.destroy() 之间的区别(从here 得到想法)
    • AbortControllerfetch API 的一部分,完全有效。与所有现代浏览器一样,最新版本的节点支持获取 API(哇哦!)。如果您将代码切换到获取 API,那么您应该使用 AbortController。如果您直接使用节点 http 模块,则需要在请求对象上调用 destroy() 。注意,有一个 node-fetch npm 模块也支持 AbortController。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2012-08-24
    • 2013-12-20
    • 2015-05-09
    • 1970-01-01
    • 2018-10-21
    • 2016-10-05
    • 2012-08-05
    相关资源
    最近更新 更多