如何以编程方式停止Node.js HTTP服务器,以使进程退出?

21

我正在编写一些测试,并希望能够以编程方式启动/停止我的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 会关闭监听器。 - Joe
使用一些测试框架,并像https://dev59.com/4mct5IYBdhLWcg3whtuh#12031279中实现before和after。 - kwarunek
这并不是我的代码,但我不知道我可能绑定了哪些其他监听器。你知道我怎么能获取阻止进程退出的监听器列表吗? - Dmitry Minkovsky
@andrey-sidorov,有趣!这实际上是“摘要”代码。我的代码实际上更长,我试图总结它。我还通过“tape”模块运行测试。也许这有关系。 - Dmitry Minkovsky
你可以使用 process._getActiveHandles()process._getActiveRequests()(也作为链接问题的答案发布)。 - Andrey Sidorov
显示剩余3条评论
3个回答

9

我已经寻找了几个月来回答这个问题,但似乎从来没有一个好的答案不使用process.exit。对我来说,这是非常奇怪的,因为这是一个非常直接的请求,但似乎没有人有一个好的答案或者似乎理解了在不退出进程的情况下停止服务器的用例。

我相信我可能已经偶然发现了一个解决方案。免责声明是,我是偶然发现这个方法的,这并不反映出我对正在发生的事情有深入的理解。所以这个解决方案可能不完整,也可能不是唯一的方法,但至少它对我来说是可靠的。要停止服务器,您需要完成两件事:

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

这里有一个示例,作为“tape”测试套件的一部分:

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(),一切都会很好。

酷!谢谢您的发布。 - Dmitry Minkovsky
为什么你想要强制关闭连接套接字? - rocketspacer
如果你在调用sock.end()时,服务器正在向客户端发送响应,则可能会出现错误。如果你没有捕获这些错误,那么问题就会发生。 - rocketspacer

5

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

4
要使node.js进程退出,请使用如下代码:process.exit(status),具体用法请参考http://nodejs.org/api/process.html#process_process_exit_code
更新:
我可能误解了。
你写道:"...但我不知道如何让由node 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的隐式分号。


1
我不希望该进程退出,因为它之后还会做其他事情。我想知道在执行完毕后,哪些处理程序阻止了该进程退出。 - Dmitry Minkovsky
Rob,我不相信那是正确的 - 当没有监听器时,节点会退出。例如,当您编写一个仅记录“hello world”的脚本时,节点会退出。但是当有监听器时,它会挂起并等待。 - Dmitry Minkovsky
Rob,感谢您抽出时间来审查并回答我的问题,尽管我正在寻找/希望得到不同的答案。 - Dmitry Minkovsky
1
感谢更新。已点赞。我在想是否是ASI的问题。我会尝试调试一下来找出原因。你的结果正是我所期望的。 - Dmitry Minkovsky

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