Skip to content

Commit

Permalink
[api] Polyfill patches to node.js core net module to support both `…
Browse files Browse the repository at this point in the history
…0.4.x` and `0.5.x || 0.6.x`
  • Loading branch information
indexzero committed Jan 6, 2012
1 parent 8e50dd1 commit 2eaccf4
Show file tree
Hide file tree
Showing 3 changed files with 233 additions and 41 deletions.
20 changes: 16 additions & 4 deletions lib/carapace.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,9 @@ var fs = require('fs'),
events = require('eventemitter2');

var carapace = module.exports = new events.EventEmitter2({
delimeter: '::',
wildcard: true
});
delimeter: '::',
wildcard: true
});

if (process.send) {
carapace.send = process.send;
Expand All @@ -30,14 +30,20 @@ carapace.onAny(function (data) {
}
});

//
// Cache the version of node running in case a user-space
// script attempts to overwrite it;
//
var nodeVersion = process.version;

//
// Require the `net` module for observing and overriding
// relevant events and functions in the core
// node.js `net` module
// Do the require prior to any chroot or chdir
// Only override once all plugins have been loaded
//
var overrideNet = require('./net');
var overrideNet = require('./net').getOverride(nodeVersion);

//
// Expose the `cli` module for default options
Expand All @@ -53,6 +59,12 @@ carapace.argv = [];
//
carapace.plugins = {};

//
// Internal mapping of server instances to
// ports that have been mapped via `.listen()`.
//
carapace.servers = {};

//
// Internal state for managing various carapace operations:
// * carapace.running: Value indicating if the target script has started.
Expand Down
249 changes: 214 additions & 35 deletions lib/net.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,47 +6,199 @@
*/

var net = require('net'),
semver = require('semver'),
carapace = require('./carapace');

module.exports = function overrideNet() {
//
// ### function toPort(x)
// #### @x {Number|String} Object to convert to a port number.
// Helper function from Node code to parse port arguments
// passed to net.prototype.Server.listen
//
function toPort(x) {
return (x = Number(x)) >= 0 ? x : false;
}

//
// ### function nextPort (port)
// #### @port {Number} Port to increment from.
// Gets the next port in sequence from the
// specified `port`.
//
function nextPort(port) {
if (!port) {
return 8000;
}

//
// Helper function from Node code to parse port arguments
// passed to net.prototype.Server.listen
// Find the next port that we are not supposed to ignore or cause errors on
//
function toPort(x) {
return (x = Number(x)) >= 0 ? x : false;
var unavailable = carapace.ports.ignore.concat(carapace.ports.throw);
do {
port = port + 1;
} while (unavailable.indexOf(port) !== -1);

return port;
}

//
// ### functon reservedPort
// #### @desired {Number} Desired port to bind to.
// Helper function which will emit an error if the
// server (the `this` argument) is in `carapace.ports.throw`
//
function reservedPort(desired) {
if (carapace.ports.throw.indexOf(desired) !== -1) {
this.close();
//
// Build fake error
//
err = new Error('EADDRINUSE, Address already in use');
err.code = 'EADDRINUSE';
err.syscall = 'bind';
err.errno = 100;
this.emit('error', err);
return true;
}

return false;
}

//
// ### function _doListen ()
// Override for `node@0.4.x`.
//
exports._doListen = function overrideNet() {
var binding = process.binding('net'),
socket = binding.socket,
listen = binding.listen,
dummyFD = null;

//
// ### function nextPort (port)
// #### @port {Number} Port to increment from.
// Gets the next port in sequence from the
// specified `port`.
// Helper function from node.js core for
// working with `dummyFD`
//
function nextPort(port) {
if (!port) {
return 8000;
function getDummyFD() {
if (!dummyFD) {
try { dummyFD = socket('tcp') }
catch (e) { dummyFD = null }
}
}

//
// Separate since net.Server uses a cached bind function
//
net.Server.prototype._doListen = function ourListen() {
var self = this,
desired = toPort(arguments[0]),
addr = arguments[1],
current,
actual,
err;

// Ensure we have a dummy fd for EMFILE conditions.
getDummyFD();

//
// Find the next port that we are not supposed to ignore or cause errors on
// Always throw if our desired port was one that should always throw
//
var unavailable = carapace.ports.ignore.concat(carapace.ports.throw);
do {
port = port + 1;
} while (unavailable.indexOf(port) !== -1);
if (reservedPort.call(this, desired)) {
return;
}

return port;
}
//
// Since desired is not on a throwing port
// we want to skip ports in both throw and ignore
//
current = desired ? desired : nextPort(desired);

for (;;) {
try {
binding.bind(self.fd, current, addr);
break;
}
catch (err) {
//
// Find the next port we are not supposed to throw up on or ignore
//
current = nextPort(current);

//
// Internal mapping of server instances to
// ports that have been mapped via `.listen()`.
//
carapace.servers = {};
//
// If this is not an `EADDRINUSE` error or the port is in
// `carapace.ports.throw` which should always throw an error,
// then throw the error.
//
if (err.code !== 'EADDRINUSE') {
self.close();
self.emit('error', err);
return;
}
}
}

actual = this.address().port;
if (!desired) {
desired = actual;
}

//
// Need to the listening in the nextTick so that people potentially have
// time to register 'listening' listeners.
//
process.nextTick(function () {
//
// It could be that server.close() was called between the time the
// original listen command was issued and this. Bail if that's the case.
// See test/simple/test-net-eaddrinuse.js
//
if (typeof self.fd !== 'number') {
return;
}

try {
listen(self.fd, self._backlog || 128);

//
// Store the server that has listened on the `desired` port
// on the carapace itself, indexed by port.
//
carapace.servers[desired] = self;

carapace.emit('port', {
id: carapace.id,
addr: addr,
desired: desired,
port: actual
});
}
catch (err) {
if (err.code !== 'EADDRINUSE') {
self.close();
self.emit('error', err);
}
else {
//
// Generate a new socket of the same type since we cannot use an already bound one
//
self.fd = socket(self.type);
self._doListen(desired, addr);
}

return;
}

self._startWatcher();
});
};
}

//
// ### function _listen2 ()
// Override for `node@0.5.x` and `node@0.6.x`.
//
exports._listen2 = function overrideNet() {
var _listen2 = net.Server.prototype._listen2;

net.Server.prototype._listen2 = function ourListen(address, port, addressType, desired) {
var self = this;

Expand All @@ -56,17 +208,10 @@ module.exports = function overrideNet() {
//
// Always throw if our desired port was one that should always throw
//
if (carapace.ports.throw.indexOf(port) !== -1) {
self.close();
//
// Build fake error
//
err = new Error('EADDRINUSE, Address already in use');
err.code = 'EADDRINUSE';
err.syscall = 'bind';
err.errno = 100;
return self.emit('error', err);
if (reservedPort.call(this, port)) {
return;
}

//
// Since desired is not on a throwing port
// we want to skip ports in both throw and ignore
Expand Down Expand Up @@ -150,7 +295,9 @@ module.exports = function overrideNet() {
restoreListeningListeners();

if (err.code !== 'EADDRINUSE') {
try { self.close(); } catch (e) { }
try { self.close() }
catch (ex) { }

return self.emit('error', err);
}

Expand All @@ -169,3 +316,35 @@ module.exports = function overrideNet() {
};
};

//
// Setup the valid `nodeVersion` for all of the
// monkey patches to `require('net')`.
//
exports._listen2.nodeVersion = '0.5.x || 0.6.x';
exports._doListen.nodeVersion = '0.4.x';

//
// ### function getOverride (version)
// #### @version {string} Node version to get `net` override.
// Helper function which responds with the correct `net` module
// override for the specified `version`.
//
exports.getOverride = function (version) {
var matches = Object.keys(exports).filter(function (method) {
if (!exports[method].nodeVersion) {
return false;
}

return semver.satisfies(version, exports[method].nodeVersion);
}).map(function (method) {
return exports[method];
});

if (!matches.length) {
return null;
}

return matches.length > 1
? matches
: matches[0];
};
5 changes: 3 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,9 @@
"dependencies": {
"async": "0.1.x",
"daemon": "0.4.x",
"optimist": "0.3.0",
"eventemitter2": "0.4.x"
"eventemitter2": "0.4.x",
"optimist": "0.3.x",
"semver": "1.0.x"
},
"devDependencies": {
"eyes": "0.1.x",
Expand Down

0 comments on commit 2eaccf4

Please sign in to comment.