goldilocks.js/lib/servers.js

180 行
4.3 KiB
JavaScript

'use strict';
var serversMap = module.exports._serversMap = {};
var dgramMap = module.exports._dgramMap = {};
var PromiseA = require('bluebird');
module.exports.addTcpListener = function (port, handler) {
return new PromiseA(function (resolve, reject) {
var stat = serversMap[port];
if (stat) {
if (stat._closing) {
stat.server.destroy();
} else {
// We're already listening on the port, so we only have 2 options. We can either
// replace the handler or reject with an error. (Though neither is really needed
// if the handlers are the same). Until there is reason to do otherwise we are
// opting for the replacement.
stat.handler = handler;
resolve();
return;
}
}
var enableDestroy = require('server-destroy');
var net = require('net');
var resolved;
var server = net.createServer({allowHalfOpen: true});
stat = serversMap[port] = {
server: server
, handler: handler
, _closing: false
};
// Add .destroy so we can close all open connections. Better if added before listen
// to eliminate any possibility of it missing an early connection in it's records.
enableDestroy(server);
server.on('connection', function (conn) {
conn.__port = port;
conn.__proto = 'tcp';
stat.handler(conn);
});
server.on('close', function () {
console.log('TCP server on port %d closed', port);
delete serversMap[port];
});
server.on('error', function (e) {
if (!resolved) {
reject(e);
} else if (handler.onError) {
handler.onError(e);
} else {
throw e;
}
});
server.listen(port, function () {
resolved = true;
resolve();
});
});
};
module.exports.closeTcpListener = function (port, timeout) {
return new PromiseA(function (resolve) {
var stat = serversMap[port];
if (!stat) {
resolve();
return;
}
stat._closing = true;
var timeoutId;
if (timeout) {
timeoutId = setTimeout(() => stat.server.destroy(), timeout);
}
stat.server.once('close', function () {
clearTimeout(timeoutId);
resolve();
});
stat.server.close();
});
};
module.exports.destroyTcpListener = function (port) {
var stat = serversMap[port];
if (stat) {
stat.server.destroy();
}
};
module.exports.listTcpListeners = function () {
return Object.keys(serversMap).map(Number).filter(function (port) {
return port && !serversMap[port]._closing;
});
};
module.exports.addUdpListener = function (port, handler) {
return new PromiseA(function (resolve, reject) {
var stat = dgramMap[port];
if (stat) {
// we'll replace the current listener
stat.handler = handler;
resolve();
return;
}
var dgram = require('dgram');
var server = dgram.createSocket({type: 'udp4', reuseAddr: true});
var resolved = false;
stat = dgramMap[port] = {
server: server
, handler: handler
};
server.on('message', function (msg, rinfo) {
msg._size = rinfo.size;
msg._remoteFamily = rinfo.family;
msg._remoteAddress = rinfo.address;
msg._remotePort = rinfo.port;
msg._port = port;
stat.handler(msg);
});
server.on('error', function (err) {
if (!resolved) {
delete dgramMap[port];
reject(err);
}
else if (stat.handler.onError) {
stat.handler.onError(err);
}
else {
throw err;
}
});
server.on('close', function () {
delete dgramMap[port];
});
server.bind(port, function () {
resolved = true;
resolve();
});
});
};
module.exports.closeUdpListener = function (port) {
var stat = dgramMap[port];
if (!stat) {
return PromiseA.resolve();
}
return new PromiseA(function (resolve) {
stat.server.once('close', resolve);
stat.server.close();
});
};
module.exports.listUdpListeners = function () {
return Object.keys(dgramMap).map(Number).filter(Boolean);
};
module.exports.listeners = {
tcp: {
add: module.exports.addTcpListener
, close: module.exports.closeTcpListener
, destroy: module.exports.destroyTcpListener
, list: module.exports.listTcpListeners
}
, udp: {
add: module.exports.addUdpListener
, close: module.exports.closeUdpListener
, list: module.exports.listUdpListeners
}
};