【问题标题】:How do I stop a Node.js HTTP server programmatically such that the process exits?如何以编程方式停止 Node.js HTTP 服务器以使进程退出?
【发布时间】:2021-01-26 03:49:01
【问题描述】:

我正在编写一些测试,并希望能够以编程方式启动/停止我的 HTTP 服务器。一旦我停止 HTTP 服务器,我希望启动它的进程退出。

我的服务器是这样的:

// file: `lib/my_server.js`

var LISTEN_PORT = 3000

function MyServer() {
  http.Server.call(this, this.handle) 
}

util.inherits(MyServer, http.Server)

MyServer.prototype.handle = function(req, res) { 
  // code 
}

MyServer.prototype.start = function() {
  this.listen(LISTEN_PORT, function() {
    console.log('Listening for HTTP requests on port %d.', LISTEN_PORT)
  })
}

MyServer.prototype.stop = function() {
  this.close(function() {
    console.log('Stopped listening.')
  })
}

测试代码如下:

// file: `test.js`

var MyServer = require('./lib/my_server')
var my_server = new MyServer();

my_server.on('listening', function() {
  my_server.stop()
})

my_server.start()

现在,当我运行 node test.js 时,我得到了预期的 stdout 输出,

$ node test.js
Listening for HTTP requests on port 3000.
Stopped listening.

但我不知道如何让node test.js 产生的进程退出并返回到 shell。

现在,我(抽象地)理解,只要有针对它正在侦听的事件的绑定事件处理程序,Node 就会继续运行。为了让node test.js 在my_server.stop() 上退出到shell,我需要取消绑定某些事件吗?如果是这样,哪个事件和来自什么对象?我尝试通过从中删除所有事件侦听器来修改MyServer.prototype.stop(),但没有运气。

【问题讨论】:

  • test.js 没有产生新进程。它只是创建您的侦听器的一个实例。这正是您的代码吗?因为我会(天真地)认为 my_server.stop 会关闭监听器。
  • 这不是我的代码,但我不知道我可能会绑定哪些其他侦听器。您知道如何获取阻止进程退出的侦听器列表吗?
  • 无法复制。调用 server.close() 后,它按预期退出到 shell
  • @andrey-sidorov,有趣!这实际上是“摘要”代码。我的代码实际上更长,我试图总结它。我还通过tape 模块运行测试。也许这是相关的。

标签: node.js


【解决方案1】:

几个月来我一直在寻找这个问题的答案,但我从未见过一个不使用process.exit 的好答案。对我来说很奇怪,这是一个如此简单的请求,但似乎没有人对此有一个好的答案,或者似乎理解在不退出进程的情况下停止服务器的用例。

我相信我可能偶然发现了一个解决方案。我的免责声明是我偶然发现了这一点;它并没有反映出对实际情况的深刻理解。所以这个解决方案可能不完整,或者可能不是唯一的方法,但至少它对我来说是可靠的。为了停止服务器,你需要做两件事:

  1. 在每个打开的连接的客户端调用.end()
  2. 在服务器上调用.close()

这是一个示例,作为“磁带”测试套件的一部分:

test('mytest', function (t) {
    t.plan(1);

    var server = net.createServer(function(c) {
        console.log("Got connection");
        // Do some server stuff
    }).listen(function() {
        // Once the server is listening, connect a client to it
        var port = server.address().port;
        var sock = net.connect(port);

        // Do some client stuff for a while, then finish the test

        setTimeout(function() {
            t.pass();
            sock.end();
            server.close();
        }, 2000);

    });

});

两秒后,进程退出,测试成功结束。我还在打开多个客户端套接字的情况下对此进行了测试;只要你结束所有客户端连接,然后在服务器上调用.close(),就可以了。

【讨论】:

  • 酷!感谢您发帖。
  • 为什么要强制关闭连接套接字?
  • 如果您的服务器在您刚刚调用 sock.end() 时正在向客户端发送响应。如果你不抓住它们就会发生错误
【解决方案2】:

http.Server#close

https://nodejs.org/api/http.html#http_server_close_callback

module.exports = {
    
    server: http.createServer(app) // Express App maybe ?
                .on('error', (e) => {
                    console.log('Oops! Something happened', e));
                    this.stopServer(); // Optionally stop the server gracefully
                    process.exit(1); // Or violently
                 }),

    // Start the server
    startServer: function() {
        Configs.reload();
        this.server
            .listen(Configs.PORT)
            .once('listening', () => console.log('Server is listening on', Configs.PORT));
    },
    
    // Stop the server
    stopServer: function() {
        this.server
            .close() // Won't accept new connection
            .once('close', () => console.log('Server stopped'));
    }
}

注意事项:

  • "close" 回调仅在所有剩余连接处理完毕时触发
  • 如果您也想停止进程,请在 "close" 回调中触发 process.exit

【讨论】:

    【解决方案3】:

    要使 node.js 进程退出,请使用process.exit(status),如http://nodejs.org/api/process.html#process_process_exit_code 中所述

    更新

    我一定是误会了。

    您写道:“......但我不知道如何让节点 test.js 产生的进程退出并返回到 shell。”

    process.exit() 执行此操作。

    除非您使用 child_processes 模块,否则 node.js 在单个进程中运行。它不会“产生”任何进一步的进程。

    node.js 继续运行,即使它似乎无事可做,这是它的“事件循环”的一个特性,它不断循环,等待事件发生。

    要停止事件循环,请使用 process.exit()。

    更新

    经过一些小的修改,例如正确使用 module.exports、添加分号等,在 Linux 服务器 (Fedora 11 - Leonidas) 上运行您的示例按预期运行并尽职尽责地返回命令 shell。

    lib/my_server.js

    // file: `lib/my_server.js`
    
    var util=require('util'),
        http=require('http');
    
    var LISTEN_PORT=3000;
    
    function MyServer(){
          http.Server.call(this, this.handle);
    }
    util.inherits(MyServer, http.Server);
    
    MyServer.prototype.handle=function(req, res){
          // code
    };
    
    MyServer.prototype.start=function(){
        this.listen(LISTEN_PORT, function(){
                console.log('Listening for HTTP requests on port %d.', LISTEN_PORT)
        });
    };
    
    MyServer.prototype.stop=function(){
        this.close(function(){
            console.log('Stopped listening.');
        });
    };
    
    module.exports=MyServer;
    

    test.js

    // file: `test.js`
    
    var MyServer = require('./lib/my_server');
    
    var my_server = new MyServer();
    
    my_server.on('listening', function() {
        my_server.stop();
    });
    
    my_server.start();
    

    输出

    > node test.js
    Listening for HTTP requests on port 3000.
    Stopped listening.
    >
    

    最后的想法:

    我发现,认真使用语句结尾的分号使我避免了各种有害的、难以定位的错误。

    虽然大多数(如果不是全部)JavaScript 解释器都提供了一种称为“自动分号插入”(或 ASI)的东西,它基于一组明确定义的规则(参见 http://dailyjs.com/2012/04/19/semicolons/ 以获得出色的描述),但在某些情况下这功能可能会无意中违背程序员的意图。

    除非您非常精通 JavaScript 语法的细节,否则我强烈建议使用显式分号而不是依赖 ASI 的隐式分号。

    【讨论】:

    • 我不希望进程退出,因为它稍后会做其他事情。我想知道哪些处理程序在结束执行时阻止进程退出。
    • Rob,我不认为这是正确的——当没有侦听器时节点退出。例如,当您编写一个只记录“hello world”的脚本时,节点会退出。但是当有听众时它会挂起并等待。
    • Rob,感谢您抽出宝贵时间审阅并回答我的问题,尽管我正在寻找/希望得到不同的答案。
    • 感谢更新。投票赞成。我想知道是不是ASI。我会用它来尝试找出答案。你的结果是我所期待的。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2011-04-20
    • 2020-09-11
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2016-04-17
    • 2010-10-02
    相关资源
    最近更新 更多