Files
2023-03-07 10:52:05 +07:00

370 lines
12 KiB
JavaScript

/**
* @fileOverview
* A simple promises-based check to see if a TCP port is already in use.
*/
'use strict';
// define the exports first to avoid cyclic dependencies.
exports.check = check;
exports.waitUntilFreeOnHost = waitUntilFreeOnHost;
exports.waitUntilFree = waitUntilFree;
exports.waitUntilUsedOnHost = waitUntilUsedOnHost;
exports.waitUntilUsed = waitUntilUsed;
exports.waitForStatus = waitForStatus;
var is = require('is2');
var net = require('net');
var util = require('util');
var debug = require('debug')('tcp-port-used');
// Global Values
var TIMEOUT = 2000;
var RETRYTIME = 250;
function getDeferred() {
var resolve, reject, promise = new Promise(function(res, rej) {
resolve = res;
reject = rej;
});
return {
resolve: resolve,
reject: reject,
promise: promise
};
}
/**
* Creates an options object from all the possible arguments
* @private
* @param {Number} port a valid TCP port number
* @param {String} host The DNS name or IP address.
* @param {Boolean} status The desired in use status to wait for: false === not in use, true === in use
* @param {Number} retryTimeMs the retry interval in milliseconds - defaultis is 200ms
* @param {Number} timeOutMs the amount of time to wait until port is free default is 1000ms
* @return {Object} An options object with all the above parameters as properties.
*/
function makeOptionsObj(port, host, inUse, retryTimeMs, timeOutMs) {
var opts = {};
opts.port = port;
opts.host = host;
opts.inUse = inUse;
opts.retryTimeMs = retryTimeMs;
opts.timeOutMs = timeOutMs;
return opts;
}
/**
* Checks if a TCP port is in use by creating the socket and binding it to the
* target port. Once bound, successfully, it's assume the port is availble.
* After the socket is closed or in error, the promise is resolved.
* Note: you have to be super user to correctly test system ports (0-1023).
* @param {Number|Object} port The port you are curious to see if available. If an object, must have the parameters as properties.
* @param {String} [host] May be a DNS name or IP address. Default '127.0.0.1'
* @return {Object} A deferred Q promise.
*
* Example usage:
*
* var tcpPortUsed = require('tcp-port-used');
* tcpPortUsed.check(22, '127.0.0.1')
* .then(function(inUse) {
* debug('Port 22 usage: '+inUse);
* }, function(err) {
* console.error('Error on check: '+util.inspect(err));
* });
*/
function check(port, host) {
var deferred = getDeferred();
var inUse = true;
var client;
var opts;
if (!is.obj(port)) {
opts = makeOptionsObj(port, host);
} else {
opts = port;
}
if (!is.port(opts.port)) {
debug('Error invalid port: '+util.inspect(opts.port));
deferred.reject(new Error('invalid port: '+util.inspect(opts.port)));
return deferred.promise;
}
if (is.nullOrUndefined(opts.host)) {
debug('set host address to default 127.0.0.1');
opts.host = '127.0.0.1';
}
function cleanUp() {
if (client) {
client.removeAllListeners('connect');
client.removeAllListeners('error');
client.end();
client.destroy();
client.unref();
}
//debug('listeners removed from client socket');
}
function onConnectCb() {
//debug('check - promise resolved - in use');
deferred.resolve(inUse);
cleanUp();
}
function onErrorCb(err) {
if (err.code !== 'ECONNREFUSED') {
//debug('check - promise rejected, error: '+err.message);
deferred.reject(err);
} else {
//debug('ECONNREFUSED');
inUse = false;
//debug('check - promise resolved - not in use');
deferred.resolve(inUse);
}
cleanUp();
}
client = new net.Socket();
client.once('connect', onConnectCb);
client.once('error', onErrorCb);
client.connect({port: opts.port, host: opts.host}, function() {});
return deferred.promise;
}
/**
* Creates a deferred promise and fulfills it only when the socket's usage
* equals status in terms of 'in use' (false === not in use, true === in use).
* Will retry on an interval specified in retryTimeMs. Note: you have to be
* super user to correctly test system ports (0-1023).
* @param {Number|Object} port a valid TCP port number, if an object, has all the parameters described as properties.
* @param {String} host The DNS name or IP address.
* @param {Boolean} status The desired in use status to wait for false === not in use, true === in use
* @param {Number} [retryTimeMs] the retry interval in milliseconds - defaultis is 200ms
* @param {Number} [timeOutMs] the amount of time to wait until port is free default is 1000ms
* @return {Object} A deferred promise from the Q library.
*
* Example usage:
*
* var tcpPortUsed = require('tcp-port-used');
* tcpPortUsed.waitForStatus(44204, 'some.host.com', true, 500, 4000)
* .then(function() {
* console.log('Port 44204 is now in use.');
* }, function(err) {
* console.log('Error: ', error.message);
* });
*/
function waitForStatus(port, host, inUse, retryTimeMs, timeOutMs) {
var deferred = getDeferred();
var timeoutId;
var timedout = false;
var retryId;
// the first arument may be an object, if it is not, make an object
var opts;
if (is.obj(port)) {
opts = port;
} else {
opts = makeOptionsObj(port, host, inUse, retryTimeMs, timeOutMs);
}
//debug('opts:'+util.inspect(opts);
if (!is.bool(opts.inUse)) {
deferred.reject(new Error('inUse must be a boolean'));
return deferred.promise;
}
if (!is.positiveInt(opts.retryTimeMs)) {
opts.retryTimeMs = RETRYTIME;
debug('set retryTime to default '+RETRYTIME+'ms');
}
if (!is.positiveInt(opts.timeOutMs)) {
opts.timeOutMs = TIMEOUT;
debug('set timeOutMs to default '+TIMEOUT+'ms');
}
function cleanUp() {
if (timeoutId) {
clearTimeout(timeoutId);
}
if (retryId) {
clearTimeout(retryId);
}
}
function timeoutFunc() {
timedout = true;
cleanUp();
deferred.reject(new Error('timeout'));
}
timeoutId = setTimeout(timeoutFunc, opts.timeOutMs);
function doCheck() {
check(opts.port, opts.host)
.then(function(inUse) {
if (timedout) {
return;
}
//debug('doCheck inUse: '+inUse);
//debug('doCheck opts.inUse: '+opts.inUse);
if (inUse === opts.inUse) {
deferred.resolve();
cleanUp();
return;
} else {
retryId = setTimeout(function() { doCheck(); }, opts.retryTimeMs);
return;
}
}, function(err) {
if (timedout) {
return;
}
deferred.reject(err);
cleanUp();
});
}
doCheck();
return deferred.promise;
}
/**
* Creates a deferred promise and fulfills it only when the socket is free.
* Will retry on an interval specified in retryTimeMs.
* Note: you have to be super user to correctly test system ports (0-1023).
* @param {Number} port a valid TCP port number
* @param {String} [host] The hostname or IP address of where the socket is.
* @param {Number} [retryTimeMs] the retry interval in milliseconds - defaultis is 100ms.
* @param {Number} [timeOutMs] the amount of time to wait until port is free. Default 300ms.
* @return {Object} A deferred promise from the q library.
*
* Example usage:
*
* var tcpPortUsed = require('tcp-port-used');
* tcpPortUsed.waitUntilFreeOnHost(44203, 'some.host.com', 500, 4000)
* .then(function() {
* console.log('Port 44203 is now free.');
* }, function(err) {
* console.loh('Error: ', error.message);
* });
*/
function waitUntilFreeOnHost(port, host, retryTimeMs, timeOutMs) {
// the first arument may be an object, if it is not, make an object
var opts;
if (is.obj(port)) {
opts = port;
opts.inUse = false;
} else {
opts = makeOptionsObj(port, host, false, retryTimeMs, timeOutMs);
}
return waitForStatus(opts);
}
/**
* For compatibility with previous version of the module, that did not provide
* arguements for hostnames. The host is set to the localhost '127.0.0.1'.
* @param {Number|Object} port a valid TCP port number. If an object, must contain all the parameters as properties.
* @param {Number} [retryTimeMs] the retry interval in milliseconds - defaultis is 100ms.
* @param {Number} [timeOutMs] the amount of time to wait until port is free. Default 300ms.
* @return {Object} A deferred promise from the q library.
*
* Example usage:
*
* var tcpPortUsed = require('tcp-port-used');
* tcpPortUsed.waitUntilFree(44203, 500, 4000)
* .then(function() {
* console.log('Port 44203 is now free.');
* }, function(err) {
* console.loh('Error: ', error.message);
* });
*/
function waitUntilFree(port, retryTimeMs, timeOutMs) {
// the first arument may be an object, if it is not, make an object
var opts;
if (is.obj(port)) {
opts = port;
opts.host = '127.0.0.1';
opts.inUse = false;
} else {
opts = makeOptionsObj(port, '127.0.0.1', false, retryTimeMs, timeOutMs);
}
return waitForStatus(opts);
}
/**
* Creates a deferred promise and fulfills it only when the socket is used.
* Will retry on an interval specified in retryTimeMs.
* Note: you have to be super user to correctly test system ports (0-1023).
* @param {Number|Object} port a valid TCP port number. If an object, must contain all the parameters as properties.
* @param {Number} [retryTimeMs] the retry interval in milliseconds - defaultis is 500ms
* @param {Number} [timeOutMs] the amount of time to wait until port is free
* @return {Object} A deferred promise from the q library.
*
* Example usage:
*
* var tcpPortUsed = require('tcp-port-used');
* tcpPortUsed.waitUntilUsedOnHost(44204, 'some.host.com', 500, 4000)
* .then(function() {
* console.log('Port 44204 is now in use.');
* }, function(err) {
* console.log('Error: ', error.message);
* });
*/
function waitUntilUsedOnHost(port, host, retryTimeMs, timeOutMs) {
// the first arument may be an object, if it is not, make an object
var opts;
if (is.obj(port)) {
opts = port;
opts.inUse = true;
} else {
opts = makeOptionsObj(port, host, true, retryTimeMs, timeOutMs);
}
return waitForStatus(opts);
}
/**
* For compatibility to previous version of module which did not have support
* for host addresses. This function works only for localhost.
* @param {Number} port a valid TCP port number. If an Object, must contain all the parameters as properties.
* @param {Number} [retryTimeMs] the retry interval in milliseconds - defaultis is 500ms
* @param {Number} [timeOutMs] the amount of time to wait until port is free
* @return {Object} A deferred promise from the q library.
*
* Example usage:
*
* var tcpPortUsed = require('tcp-port-used');
* tcpPortUsed.waitUntilUsed(44204, 500, 4000)
* .then(function() {
* console.log('Port 44204 is now in use.');
* }, function(err) {
* console.log('Error: ', error.message);
* });
*/
function waitUntilUsed(port, retryTimeMs, timeOutMs) {
// the first arument may be an object, if it is not, make an object
var opts;
if (is.obj(port)) {
opts = port;
opts.host = '127.0.0.1';
opts.inUse = true;
} else {
opts = makeOptionsObj(port, '127.0.0.1', true, retryTimeMs, timeOutMs);
}
return waitUntilUsedOnHost(opts);
}