You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

218 lines
5.3 KiB

8 years ago
  1. /*
  2. * portfinder.js: A simple tool to find an open port on the current machine.
  3. *
  4. * (C) 2011, Charlie Robbins
  5. *
  6. */
  7. var fs = require('fs'),
  8. net = require('net'),
  9. path = require('path'),
  10. async = require('async'),
  11. mkdirp = require('mkdirp').mkdirp;
  12. //
  13. // ### @basePort {Number}
  14. // The lowest port to begin any port search from
  15. //
  16. exports.basePort = 8000;
  17. //
  18. // ### @basePath {string}
  19. // Default path to begin any socket search from
  20. //
  21. exports.basePath = '/tmp/portfinder'
  22. //
  23. // ### function getPort (options, callback)
  24. // #### @options {Object} Settings to use when finding the necessary port
  25. // #### @callback {function} Continuation to respond to when complete.
  26. // Responds with a unbound port on the current machine.
  27. //
  28. exports.getPort = function (options, callback) {
  29. if (!callback) {
  30. callback = options;
  31. options = {};
  32. }
  33. options.port = options.port || exports.basePort;
  34. options.host = options.host || null;
  35. options.server = options.server || net.createServer(function () {
  36. //
  37. // Create an empty listener for the port testing server.
  38. //
  39. });
  40. function onListen () {
  41. options.server.removeListener('error', onError);
  42. options.server.close();
  43. callback(null, options.port)
  44. }
  45. function onError (err) {
  46. options.server.removeListener('listening', onListen);
  47. if (err.code !== 'EADDRINUSE' && err.code !== 'EACCES') {
  48. return callback(err);
  49. }
  50. exports.getPort({
  51. port: exports.nextPort(options.port),
  52. host: options.host,
  53. server: options.server
  54. }, callback);
  55. }
  56. options.server.once('error', onError);
  57. options.server.once('listening', onListen);
  58. options.server.listen(options.port, options.host);
  59. };
  60. //
  61. // ### function getPorts (count, options, callback)
  62. // #### @count {Number} The number of ports to find
  63. // #### @options {Object} Settings to use when finding the necessary port
  64. // #### @callback {function} Continuation to respond to when complete.
  65. // Responds with an array of unbound ports on the current machine.
  66. //
  67. exports.getPorts = function (count, options, callback) {
  68. if (!callback) {
  69. callback = options;
  70. options = {};
  71. }
  72. var lastPort = null;
  73. async.timesSeries(count, function(index, asyncCallback) {
  74. if (lastPort) {
  75. options.port = exports.nextPort(lastPort);
  76. }
  77. exports.getPort(options, function (err, port) {
  78. if (err) {
  79. asyncCallback(err);
  80. } else {
  81. lastPort = port;
  82. asyncCallback(null, port);
  83. }
  84. });
  85. }, callback);
  86. };
  87. //
  88. // ### function getSocket (options, callback)
  89. // #### @options {Object} Settings to use when finding the necessary port
  90. // #### @callback {function} Continuation to respond to when complete.
  91. // Responds with a unbound socket using the specified directory and base
  92. // name on the current machine.
  93. //
  94. exports.getSocket = function (options, callback) {
  95. if (!callback) {
  96. callback = options;
  97. options = {};
  98. }
  99. options.mod = options.mod || 0755;
  100. options.path = options.path || exports.basePath + '.sock';
  101. //
  102. // Tests the specified socket
  103. //
  104. function testSocket () {
  105. fs.stat(options.path, function (err) {
  106. //
  107. // If file we're checking doesn't exist (thus, stating it emits ENOENT),
  108. // we should be OK with listening on this socket.
  109. //
  110. if (err) {
  111. if (err.code == 'ENOENT') {
  112. callback(null, options.path);
  113. }
  114. else {
  115. callback(err);
  116. }
  117. }
  118. else {
  119. //
  120. // This file exists, so it isn't possible to listen on it. Lets try
  121. // next socket.
  122. //
  123. options.path = exports.nextSocket(options.path);
  124. exports.getSocket(options, callback);
  125. }
  126. });
  127. }
  128. //
  129. // Create the target `dir` then test connection
  130. // against the socket.
  131. //
  132. function createAndTestSocket (dir) {
  133. mkdirp(dir, options.mod, function (err) {
  134. if (err) {
  135. return callback(err);
  136. }
  137. options.exists = true;
  138. testSocket();
  139. });
  140. }
  141. //
  142. // Check if the parent directory of the target
  143. // socket path exists. If it does, test connection
  144. // against the socket. Otherwise, create the directory
  145. // then test connection.
  146. //
  147. function checkAndTestSocket () {
  148. var dir = path.dirname(options.path);
  149. fs.stat(dir, function (err, stats) {
  150. if (err || !stats.isDirectory()) {
  151. return createAndTestSocket(dir);
  152. }
  153. options.exists = true;
  154. testSocket();
  155. });
  156. }
  157. //
  158. // If it has been explicitly stated that the
  159. // target `options.path` already exists, then
  160. // simply test the socket.
  161. //
  162. return options.exists
  163. ? testSocket()
  164. : checkAndTestSocket();
  165. };
  166. //
  167. // ### function nextPort (port)
  168. // #### @port {Number} Port to increment from.
  169. // Gets the next port in sequence from the
  170. // specified `port`.
  171. //
  172. exports.nextPort = function (port) {
  173. return port + 1;
  174. };
  175. //
  176. // ### function nextSocket (socketPath)
  177. // #### @socketPath {string} Path to increment from
  178. // Gets the next socket path in sequence from the
  179. // specified `socketPath`.
  180. //
  181. exports.nextSocket = function (socketPath) {
  182. var dir = path.dirname(socketPath),
  183. name = path.basename(socketPath, '.sock'),
  184. match = name.match(/^([a-zA-z]+)(\d*)$/i),
  185. index = parseInt(match[2]),
  186. base = match[1];
  187. if (isNaN(index)) {
  188. index = 0;
  189. }
  190. index += 1;
  191. return path.join(dir, base + index + '.sock');
  192. };