21

I'm writing some tests and would like to be able to start/stop my HTTP server programmatically. Once I stop the HTTP server, I would like the process that started it to exit.

My server is like:

// 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.')
  })
}

The test code is like:

// 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()

Now, when I run node test.js, I get the stdout output that I expect,

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

but I have no idea how to get the process spawned by node test.js to exit and return back to the shell.

Now, I understand (abstractly) that Node keeps running as long as there are bound event handlers for events that it's listening for. In order for node test.js to exit to the shell upon my_server.stop(), do I need to unbind some event? If so, which event and from what object? I have tried modifying MyServer.prototype.stop() by removing all event listeners from it but have had no luck.

Dmitry Minkovsky
  • 36,185
  • 26
  • 116
  • 160
  • `test.js` is not spawning a new process. It is simply creating an instance of your listener. Is this exactly your code? Because I would (naively) think that the my_server.stop would close the listener. – Joe Jul 30 '13 at 22:51
  • Use some test framework and implement before after like in [http://stackoverflow.com/questions/12030670/trying-to-test-a-node-js-server-process-using-mocha/12031279#12031279][1] [1]: http://stackoverflow.com/questions/12030670/trying-to-test-a-node-js-server-process-using-mocha/12031279#12031279 – kwarunek Jul 30 '13 at 22:55
  • This isn't my code exactly, but I don't know what other listeners I may be binding. Do you know how I can get a list of listeners that keep the process from exiting? – Dmitry Minkovsky Jul 30 '13 at 23:42
  • Can't reproduce. It exits to shell as expected after call to `server.close()` – Andrey Sidorov Jul 31 '13 at 00:51
  • @andrey-sidorov, interesting! This is actually "summary" code. My code is actually longer and I was trying to summarize it. I am also running the test via the `tape` module. Maybe that is related. – Dmitry Minkovsky Jul 31 '13 at 01:12
  • @joe this is summary code. – Dmitry Minkovsky Jul 31 '13 at 01:15
  • @AndreySidorov: Indeed, it exits. In my real code I had a mongoose connection that was keeping this process/event loop alive. I have re-stated this question at http://stackoverflow.com/questions/17960452/how-can-i-get-a-list-of-bound-callbacks-in-node-js – Dmitry Minkovsky Jul 31 '13 at 02:41
  • You can use `process._getActiveHandles()` and `process._getActiveRequests()` (also posted as answer to linked question) – Andrey Sidorov Jul 31 '13 at 02:53

3 Answers3

9

I've been looking for an answer to this question for months and I've never yet seen a good answer that doesn't use process.exit. It's quite strange to me that it is such a straightforward request but no one seems to have a good answer for it or seems to understand the use case for stopping a server without exiting the process.

I believe I might have stumbled across a solution. My disclaimer is that I discovered this by chance; it doesn't reflect a deep understanding of what's actually going on. So this solution may be incomplete or maybe not the only way of doing it, but at least it works reliably for me. In order to stop the server, you need to do two things:

  1. Call .end() on the client side of every opened connection
  2. Call .close() on the server

Here's an example, as part of a "tape" test suite:

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);

    });

});

After the two seconds, the process will exit and the test will end successfully. I've also tested this with multiple client sockets open; as long as you end all client-side connections and then call .close() on the server, you are good.

user2041338
  • 211
  • 3
  • 6
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'));
    }
}

Notes:

  • "close" callback only triggers when all leftover connections have finished processing
  • Trigger process.exit in "close" callback if you want to stop the process too
tu4n
  • 4,200
  • 6
  • 36
  • 49
4

To cause the node.js process to exit, use process.exit(status) as described in http://nodejs.org/api/process.html#process_process_exit_code

Update

I must have misunderstood.

You wrote: "...but I have no idea how to get the process spawned by node test.js to exit and return back to the shell."

process.exit() does this.

Unless you're using the child_processes module, node.js runs in a single process. It does not "spawn" any further processes.

The fact that node.js continues to run even though there appears to be nothing for it to do is a feature of its "event loop" which continually loops, waiting for events to occur.

To halt the event loop, use process.exit().

UPDATE

After a few small modifications, such as the proper use of module.exports, addition of semicolons, etc., running your example on a Linux server (Fedora 11 - Leonidas) runs as expected and dutifully returns to the command 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();

Output

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

Final thoughts:

I've found that the conscientious use of statement-ending semicolons has saved me from a wide variety of pernicious, difficult to locate bugs.

While most (if not all) JavaScript interpreters provide something called "automatic semicolon insertion" (or ASI) based upon a well-defined set of rules (See http://dailyjs.com/2012/04/19/semicolons/ for an excellent description), there are several instances where this feature can inadvertently work against the intent of the programmer.

Unless you are very well versed in the minutia of JavaScript syntax, I would strongly recommend the use of explicit semicolons rather than relying upon ASI's implicit ones.

Rob Raisch
  • 17,040
  • 4
  • 48
  • 58
  • 1
    I don't want the process to exit, because it does other things later. I want to know which handlers are keeping then process from exiting when it ends executing. – Dmitry Minkovsky Jul 30 '13 at 23:38
  • Rob, I don't believe that is correct--node exits when there are no listeners. For example, node exits when you write a script that just logs "hello world". But it hangs around and waits when there are listeners. – Dmitry Minkovsky Jul 30 '13 at 23:57
  • Rob, thank you for taking the time to review and answer my question, even though I am looking/hoping for a different answer. – Dmitry Minkovsky Jul 31 '13 at 01:11
  • 1
    Thanks the updates. Voted up. I do wonder if it was the ASI. I will play with it to try to find out. Your results are what I was expecting. – Dmitry Minkovsky Jan 25 '14 at 14:39