Force close all connections in a node.js http serv

2019-02-04 07:34发布

问题:

I have an http server created using:

var server = http.createServer()

I want to shut down the server. Presumably I'd do this by calling:

server.close()

However, this only prevents the server from receiving any new http connections. It does not close any that are still open. http.close() takes a callback, and that callback does not get executed until all open connections have actually disconnected. Is there a way to force close everything?

The root of the problem for me is that I have Mocha tests that start up an http server in their setup (beforeEach()) and then shut it down in their teardown (afterEach()). But since just calling server.close() won't fully shut things down, the subsequent http.createServer() often results in an EADDRINUSE error. Waiting for close() to finish also isn't an option, since open connections might take a really long time to time out.

I need some way to force-close connections. I'm able to do this client-side, but forcing all of my test connections to close, but I'd rather do it server-side, i.e. to just tell the http server to hard-close all sockets.

回答1:

You need to

  1. subscribe to the connection event of the server and add opened sockets to an array
  2. keep track of the open sockets by subscribing to their close event and removing the closed ones from your array
  3. call destroy on all of the remaining open sockets when you need to terminate the server

You also have the chance to run the server in a child process and exit that process when you need.



回答2:

For reference for others who stumble accross this question, the https://github.com/isaacs/server-destroy library provides an easy way to destroy() a server (using the approach described by Ege).



回答3:

I usually use something similar to this:

var express = require('express');
var server = express();

/* a dummy route */
server.get('/', function (req, res) {
    res.send('Hello World!');
});

/* handle SIGTERM and SIGINT (ctrl-c) nicely */
process.once('SIGTERM', end);
process.once('SIGINT', end);


var listener = server.listen(8000, function(err) {
    if (err) throw err;

    var host = listener.address().address;
    var port = listener.address().port;

    console.log('Server listening at http://%s:%s', host, port);
});


var lastSocketKey = 0;
var socketMap = {};
listener.on('connection', function(socket) {
    /* generate a new, unique socket-key */
    var socketKey = ++lastSocketKey;
    /* add socket when it is connected */
    socketMap[socketKey] = socket;
    socket.on('close', function() {
        /* remove socket when it is closed */
        delete socketMap[socketKey];
    });
});

function end() {
    /* loop through all sockets and destroy them */
    Object.keys(socketMap).forEach(function(socketKey){
        socketMap[socketKey].destroy();
    });

    /* after all the sockets are destroyed, we may close the server! */
    listener.close(function(err){
        if(err) throw err();

        console.log('Server stopped');
        /* exit gracefully */
        process.exit(0);
    });
}

it's like Ege Özcan says, simply collect the sockets on the connection event, and when closing the server, destroy them.



回答4:

My approach comes from this one and it basically does what @Ege Özcan said.

The only addition is that I set a route to switch off my server because node wasn't getting the signals from my terminal ('SIGTERM' and 'SIGINT').

Well, node was getting the signals from my terminal when doing node whatever.js but when delegating that task to a script (like the 'start' script in package.json --> npm start) it failed to be switched off by Ctrl+C, so this approach worked for me.

Please note I am under Cygwin and for me killing a server before this meant to close the terminal and reopen it again.

Also note that I am using express for the routing stuff.

var http=require('http');
var express= require('express');

var app= express();

app.get('/', function (req, res) {
    res.send('I am alive but if you want to kill me just go to <a href="/exit">/exit</a>');
});

app.get('/exit', killserver);

var server =http.createServer(app).listen(3000, function(){
  console.log('Express server listening on port 3000');
  /*console.log(process);*/
});


// Maintain a hash of all connected sockets
var sockets = {}, nextSocketId = 0;
server.on('connection', function (socket) {
  // Add a newly connected socket
  var socketId = nextSocketId++;
  sockets[socketId] = socket;
  console.log('socket', socketId, 'opened');

  // Remove the socket when it closes
  socket.on('close', function () {
    console.log('socket', socketId, 'closed');
    delete sockets[socketId];
  });

  // Extend socket lifetime for demo purposes
  socket.setTimeout(4000);
});


// close the server and destroy all the open sockets
function killserver() {
    console.log("U killed me but I'll take my revenge soon!!");
  // Close the server
  server.close(function () { console.log('Server closed!'); });
  // Destroy all open sockets
  for (var socketId in sockets) {
    console.log('socket', socketId, 'destroyed');
    sockets[socketId].destroy();
  }
};


回答5:

I've rewriten original answers using modern JS:

const server1 = http.createServer(/*....*/);
const server1Sockets = new Set();

server1.on("connection", socket => {
    server1Sockets.add(socket);
    socket.on("close", () => {
        server1Sockets.delete(socket);
    });
});

function destroySockets(sockets) {
    for (const socket of sockets.values()) {
        socket.destroy();
    }
}

destroySockets(server1Sockets);


标签: node.js http