mirror of
https://github.com/nodejs/node.git
synced 2024-11-30 07:27:22 +01:00
039fac633e
The event loop's reference counting scheme in this version of libuv has changed. Update the libuv bindings to reflect that fact.
1111 lines
26 KiB
JavaScript
1111 lines
26 KiB
JavaScript
// Copyright Joyent, Inc. and other Node contributors.
|
|
//
|
|
// Permission is hereby granted, free of charge, to any person obtaining a
|
|
// copy of this software and associated documentation files (the
|
|
// "Software"), to deal in the Software without restriction, including
|
|
// without limitation the rights to use, copy, modify, merge, publish,
|
|
// distribute, sublicense, and/or sell copies of the Software, and to permit
|
|
// persons to whom the Software is furnished to do so, subject to the
|
|
// following conditions:
|
|
//
|
|
// The above copyright notice and this permission notice shall be included
|
|
// in all copies or substantial portions of the Software.
|
|
//
|
|
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
|
|
// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
|
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN
|
|
// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
|
|
// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
|
|
// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
|
|
// USE OR OTHER DEALINGS IN THE SOFTWARE.
|
|
|
|
var events = require('events');
|
|
var stream = require('stream');
|
|
var timers = require('timers');
|
|
var util = require('util');
|
|
var assert = require('assert');
|
|
var cluster;
|
|
|
|
function noop() {}
|
|
|
|
// constructor for lazy loading
|
|
function createPipe() {
|
|
var Pipe = process.binding('pipe_wrap').Pipe;
|
|
return new Pipe();
|
|
}
|
|
|
|
// constructor for lazy loading
|
|
function createTCP() {
|
|
var TCP = process.binding('tcp_wrap').TCP;
|
|
return new TCP();
|
|
}
|
|
|
|
|
|
/* Bit flags for socket._flags */
|
|
var FLAG_GOT_EOF = 1 << 0;
|
|
var FLAG_SHUTDOWN = 1 << 1;
|
|
var FLAG_DESTROY_SOON = 1 << 2;
|
|
var FLAG_SHUTDOWNQUED = 1 << 3;
|
|
|
|
|
|
var debug;
|
|
if (process.env.NODE_DEBUG && /net/.test(process.env.NODE_DEBUG)) {
|
|
debug = function(x) { console.error('NET:', x); };
|
|
} else {
|
|
debug = function() { };
|
|
}
|
|
|
|
|
|
function isPipeName(s) {
|
|
return typeof s === 'string' && toNumber(s) === false;
|
|
}
|
|
|
|
|
|
exports.createServer = function() {
|
|
return new Server(arguments[0], arguments[1]);
|
|
};
|
|
|
|
|
|
// Target API:
|
|
//
|
|
// var s = net.connect({port: 80, host: 'google.com'}, function() {
|
|
// ...
|
|
// });
|
|
//
|
|
// There are various forms:
|
|
//
|
|
// connect(options, [cb])
|
|
// connect(port, [host], [cb])
|
|
// connect(path, [cb]);
|
|
//
|
|
exports.connect = exports.createConnection = function() {
|
|
var args = normalizeConnectArgs(arguments);
|
|
var s = new Socket(args[0]);
|
|
return Socket.prototype.connect.apply(s, args);
|
|
};
|
|
|
|
// Returns an array [options] or [options, cb]
|
|
// It is the same as the argument of Socket.prototype.connect().
|
|
function normalizeConnectArgs(args) {
|
|
var options = {};
|
|
|
|
if (typeof args[0] === 'object') {
|
|
// connect(options, [cb])
|
|
options = args[0];
|
|
} else if (isPipeName(args[0])) {
|
|
// connect(path, [cb]);
|
|
options.path = args[0];
|
|
} else {
|
|
// connect(port, [host], [cb])
|
|
options.port = args[0];
|
|
if (typeof args[1] === 'string') {
|
|
options.host = args[1];
|
|
}
|
|
}
|
|
|
|
var cb = args[args.length - 1];
|
|
return (typeof cb === 'function') ? [options, cb] : [options];
|
|
}
|
|
|
|
|
|
/* called when creating new Socket, or when re-using a closed Socket */
|
|
function initSocketHandle(self) {
|
|
self._pendingWriteReqs = 0;
|
|
|
|
self._flags = 0;
|
|
self._connectQueueSize = 0;
|
|
self.destroyed = false;
|
|
self.errorEmitted = false;
|
|
self.bytesRead = 0;
|
|
self._bytesDispatched = 0;
|
|
|
|
// Handle creation may be deferred to bind() or connect() time.
|
|
if (self._handle) {
|
|
self._handle.owner = self;
|
|
self._handle.onread = onread;
|
|
}
|
|
}
|
|
|
|
function Socket(options) {
|
|
if (!(this instanceof Socket)) return new Socket(options);
|
|
|
|
stream.Stream.call(this);
|
|
|
|
if (typeof options == 'number') {
|
|
// Legacy interface.
|
|
// Must support legacy interface. Old versions of npm depend on it.
|
|
// https://github.com/isaacs/npm/blob/c7824f412f0cb59d6f55cf0bc220253c39e6029f/lib/utils/output.js#L110
|
|
var fd = options;
|
|
|
|
// Uncomment the following lines after libuv backend is stable and API
|
|
// compatibile with legaacy.
|
|
// console.error('Deprecated interface net.Socket(fd).');
|
|
// console.trace();
|
|
this._handle = createPipe();
|
|
this._handle.open(fd);
|
|
this.readable = this.writable = true;
|
|
initSocketHandle(this);
|
|
} else {
|
|
// private
|
|
this._handle = options && options.handle;
|
|
initSocketHandle(this);
|
|
this.allowHalfOpen = options && options.allowHalfOpen;
|
|
}
|
|
}
|
|
util.inherits(Socket, stream.Stream);
|
|
|
|
|
|
exports.Socket = Socket;
|
|
exports.Stream = Socket; // Legacy naming.
|
|
|
|
|
|
Socket.prototype.listen = function() {
|
|
var self = this;
|
|
self.on('connection', arguments[0]);
|
|
listen(self, null, null, null);
|
|
};
|
|
|
|
|
|
Socket.prototype.setTimeout = function(msecs, callback) {
|
|
if (msecs > 0) {
|
|
timers.enroll(this, msecs);
|
|
timers.active(this);
|
|
if (callback) {
|
|
this.once('timeout', callback);
|
|
}
|
|
} else if (msecs === 0) {
|
|
timers.unenroll(this);
|
|
if (callback) {
|
|
this.removeListener('timeout', callback);
|
|
}
|
|
}
|
|
};
|
|
|
|
|
|
Socket.prototype._onTimeout = function() {
|
|
debug('_onTimeout');
|
|
this.emit('timeout');
|
|
};
|
|
|
|
|
|
Socket.prototype.setNoDelay = function(enable) {
|
|
// backwards compatibility: assume true when `enable` is omitted
|
|
if (this._handle && this._handle.setNoDelay)
|
|
this._handle.setNoDelay(typeof enable === 'undefined' ? true : !!enable);
|
|
};
|
|
|
|
|
|
Socket.prototype.setKeepAlive = function(setting, msecs) {
|
|
if (this._handle && this._handle.setKeepAlive)
|
|
this._handle.setKeepAlive(setting, ~~(msecs / 1000));
|
|
};
|
|
|
|
|
|
Socket.prototype.address = function() {
|
|
if (this._handle && this._handle.getsockname) {
|
|
return this._handle.getsockname();
|
|
}
|
|
return null;
|
|
};
|
|
|
|
|
|
Object.defineProperty(Socket.prototype, 'readyState', {
|
|
get: function() {
|
|
if (this._connecting) {
|
|
return 'opening';
|
|
} else if (this.readable && this.writable) {
|
|
return 'open';
|
|
} else if (this.readable && !this.writable) {
|
|
return 'readOnly';
|
|
} else if (!this.readable && this.writable) {
|
|
return 'writeOnly';
|
|
} else {
|
|
return 'closed';
|
|
}
|
|
}
|
|
});
|
|
|
|
|
|
Object.defineProperty(Socket.prototype, 'bufferSize', {
|
|
get: function() {
|
|
if (this._handle) {
|
|
return this._handle.writeQueueSize + this._connectQueueSize;
|
|
}
|
|
}
|
|
});
|
|
|
|
|
|
Socket.prototype.pause = function() {
|
|
if (this._handle) {
|
|
this._handle.readStop();
|
|
}
|
|
};
|
|
|
|
|
|
Socket.prototype.resume = function() {
|
|
if (this._handle) {
|
|
this._handle.readStart();
|
|
}
|
|
};
|
|
|
|
|
|
Socket.prototype.end = function(data, encoding) {
|
|
if (this._connecting && ((this._flags & FLAG_SHUTDOWNQUED) == 0)) {
|
|
// still connecting, add data to buffer
|
|
if (data) this.write(data, encoding);
|
|
this.writable = false;
|
|
this._flags |= FLAG_SHUTDOWNQUED;
|
|
}
|
|
|
|
if (!this.writable) return;
|
|
this.writable = false;
|
|
|
|
if (data) this.write(data, encoding);
|
|
DTRACE_NET_STREAM_END(this);
|
|
|
|
if (!this.readable) {
|
|
this.destroySoon();
|
|
} else {
|
|
this._flags |= FLAG_SHUTDOWN;
|
|
var shutdownReq = this._handle.shutdown();
|
|
|
|
if (!shutdownReq) {
|
|
this._destroy(errnoException(errno, 'shutdown'));
|
|
return false;
|
|
}
|
|
|
|
shutdownReq.oncomplete = afterShutdown;
|
|
}
|
|
|
|
return true;
|
|
};
|
|
|
|
|
|
function afterShutdown(status, handle, req) {
|
|
var self = handle.owner;
|
|
|
|
assert.ok(self._flags & FLAG_SHUTDOWN);
|
|
assert.ok(!self.writable);
|
|
|
|
// callback may come after call to destroy.
|
|
if (self.destroyed) {
|
|
return;
|
|
}
|
|
|
|
if (self._flags & FLAG_GOT_EOF || !self.readable) {
|
|
self._destroy();
|
|
} else {
|
|
}
|
|
}
|
|
|
|
|
|
Socket.prototype.destroySoon = function() {
|
|
this.writable = false;
|
|
this._flags |= FLAG_DESTROY_SOON;
|
|
|
|
if (this._pendingWriteReqs == 0) {
|
|
this._destroy();
|
|
}
|
|
};
|
|
|
|
|
|
Socket.prototype._connectQueueCleanUp = function(exception) {
|
|
this._connecting = false;
|
|
this._connectQueueSize = 0;
|
|
this._connectQueue = null;
|
|
};
|
|
|
|
|
|
Socket.prototype._destroy = function(exception, cb) {
|
|
var self = this;
|
|
|
|
function fireErrorCallbacks() {
|
|
if (cb) cb(exception);
|
|
if (exception && !self.errorEmitted) {
|
|
process.nextTick(function() {
|
|
self.emit('error', exception);
|
|
});
|
|
self.errorEmitted = true;
|
|
}
|
|
};
|
|
|
|
if (this.destroyed) {
|
|
fireErrorCallbacks();
|
|
return;
|
|
}
|
|
|
|
self._connectQueueCleanUp();
|
|
|
|
debug('destroy');
|
|
|
|
this.readable = this.writable = false;
|
|
|
|
timers.unenroll(this);
|
|
|
|
if (this.server) {
|
|
this.server._connections--;
|
|
this.server._emitCloseIfDrained();
|
|
}
|
|
|
|
debug('close');
|
|
if (this._handle) {
|
|
this._handle.close();
|
|
this._handle.onread = noop;
|
|
this._handle = null;
|
|
}
|
|
|
|
fireErrorCallbacks();
|
|
|
|
process.nextTick(function() {
|
|
self.emit('close', exception ? true : false);
|
|
});
|
|
|
|
this.destroyed = true;
|
|
};
|
|
|
|
|
|
Socket.prototype.destroy = function(exception) {
|
|
this._destroy(exception);
|
|
};
|
|
|
|
|
|
function onread(buffer, offset, length) {
|
|
var handle = this;
|
|
var self = handle.owner;
|
|
assert.equal(handle, self._handle);
|
|
|
|
timers.active(self);
|
|
|
|
var end = offset + length;
|
|
|
|
if (buffer) {
|
|
// Emit 'data' event.
|
|
|
|
if (self._decoder) {
|
|
// Emit a string.
|
|
var string = self._decoder.write(buffer.slice(offset, end));
|
|
if (string.length) self.emit('data', string);
|
|
} else {
|
|
// Emit a slice. Attempt to avoid slicing the buffer if no one is
|
|
// listening for 'data'.
|
|
if (self._events && self._events['data']) {
|
|
self.emit('data', buffer.slice(offset, end));
|
|
}
|
|
}
|
|
|
|
self.bytesRead += length;
|
|
|
|
// Optimization: emit the original buffer with end points
|
|
if (self.ondata) self.ondata(buffer, offset, end);
|
|
|
|
} else if (errno == 'EOF') {
|
|
// EOF
|
|
self.readable = false;
|
|
|
|
assert.ok(!(self._flags & FLAG_GOT_EOF));
|
|
self._flags |= FLAG_GOT_EOF;
|
|
|
|
// We call destroy() before end(). 'close' not emitted until nextTick so
|
|
// the 'end' event will come first as required.
|
|
if (!self.writable) self._destroy();
|
|
|
|
if (!self.allowHalfOpen) self.end();
|
|
if (self._events && self._events['end']) self.emit('end');
|
|
if (self.onend) self.onend();
|
|
} else {
|
|
// Error
|
|
if (errno == 'ECONNRESET') {
|
|
self._destroy();
|
|
} else {
|
|
self._destroy(errnoException(errno, 'read'));
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
Socket.prototype.setEncoding = function(encoding) {
|
|
var StringDecoder = require('string_decoder').StringDecoder; // lazy load
|
|
this._decoder = new StringDecoder(encoding);
|
|
};
|
|
|
|
|
|
Socket.prototype._getpeername = function() {
|
|
if (!this._handle || !this._handle.getpeername) {
|
|
return {};
|
|
}
|
|
if (!this._peername) {
|
|
this._peername = this._handle.getpeername();
|
|
}
|
|
return this._peername;
|
|
};
|
|
|
|
|
|
Socket.prototype.__defineGetter__('remoteAddress', function() {
|
|
return this._getpeername().address;
|
|
});
|
|
|
|
|
|
Socket.prototype.__defineGetter__('remotePort', function() {
|
|
return this._getpeername().port;
|
|
});
|
|
|
|
|
|
/*
|
|
* Arguments data, [encoding], [cb]
|
|
*/
|
|
Socket.prototype.write = function(data, arg1, arg2) {
|
|
var encoding, cb;
|
|
|
|
// parse arguments
|
|
if (arg1) {
|
|
if (typeof arg1 === 'string') {
|
|
encoding = arg1;
|
|
cb = arg2;
|
|
} else if (typeof arg1 === 'function') {
|
|
cb = arg1;
|
|
} else {
|
|
throw new Error('bad arg');
|
|
}
|
|
}
|
|
|
|
if (typeof data === 'string') {
|
|
encoding = (encoding || 'utf8').toLowerCase();
|
|
switch (encoding) {
|
|
case 'utf8':
|
|
case 'utf-8':
|
|
case 'ascii':
|
|
case 'ucs2':
|
|
case 'ucs-2':
|
|
case 'utf16le':
|
|
case 'utf-16le':
|
|
// This encoding can be handled in the binding layer.
|
|
break;
|
|
|
|
default:
|
|
data = new Buffer(data, encoding);
|
|
}
|
|
} else if (!Buffer.isBuffer(data)) {
|
|
throw new TypeError('First argument must be a buffer or a string.');
|
|
}
|
|
|
|
// If we are still connecting, then buffer this for later.
|
|
if (this._connecting) {
|
|
this._connectQueueSize += data.length;
|
|
if (this._connectQueue) {
|
|
this._connectQueue.push([data, encoding, cb]);
|
|
} else {
|
|
this._connectQueue = [[data, encoding, cb]];
|
|
}
|
|
return false;
|
|
}
|
|
|
|
return this._write(data, encoding, cb);
|
|
};
|
|
|
|
|
|
Socket.prototype._write = function(data, encoding, cb) {
|
|
timers.active(this);
|
|
|
|
if (!this._handle) {
|
|
this._destroy(new Error('This socket is closed.'), cb);
|
|
return false;
|
|
}
|
|
|
|
var writeReq;
|
|
|
|
if (Buffer.isBuffer(data)) {
|
|
writeReq = this._handle.writeBuffer(data);
|
|
|
|
} else {
|
|
switch (encoding) {
|
|
case 'utf8':
|
|
case 'utf-8':
|
|
writeReq = this._handle.writeUtf8String(data);
|
|
break;
|
|
|
|
case 'ascii':
|
|
writeReq = this._handle.writeAsciiString(data);
|
|
break;
|
|
|
|
case 'ucs2':
|
|
case 'ucs-2':
|
|
case 'utf16le':
|
|
case 'utf-16le':
|
|
writeReq = this._handle.writeUcs2String(data);
|
|
break;
|
|
|
|
default:
|
|
assert(0);
|
|
}
|
|
}
|
|
|
|
if (!writeReq || typeof writeReq !== 'object') {
|
|
this._destroy(errnoException(errno, 'write'), cb);
|
|
return false;
|
|
}
|
|
|
|
writeReq.oncomplete = afterWrite;
|
|
writeReq.cb = cb;
|
|
|
|
this._pendingWriteReqs++;
|
|
this._bytesDispatched += writeReq.bytes;
|
|
|
|
return this._handle.writeQueueSize == 0;
|
|
};
|
|
|
|
|
|
Socket.prototype.__defineGetter__('bytesWritten', function() {
|
|
var bytes = this._bytesDispatched,
|
|
connectQueue = this._connectQueue;
|
|
|
|
if (connectQueue) {
|
|
connectQueue.forEach(function(el) {
|
|
var data = el[0];
|
|
if (Buffer.isBuffer(data)) {
|
|
bytes += data.length;
|
|
} else {
|
|
bytes += Buffer.byteLength(data, el[1]);
|
|
}
|
|
}, this);
|
|
}
|
|
|
|
return bytes;
|
|
});
|
|
|
|
|
|
function afterWrite(status, handle, req) {
|
|
var self = handle.owner;
|
|
|
|
// callback may come after call to destroy.
|
|
if (self.destroyed) {
|
|
return;
|
|
}
|
|
|
|
if (status) {
|
|
self._destroy(errnoException(errno, 'write'), req.cb);
|
|
return;
|
|
}
|
|
|
|
timers.active(self);
|
|
|
|
self._pendingWriteReqs--;
|
|
|
|
if (self._pendingWriteReqs == 0) {
|
|
self.emit('drain');
|
|
}
|
|
|
|
if (req.cb) req.cb();
|
|
|
|
if (self._pendingWriteReqs == 0 && self._flags & FLAG_DESTROY_SOON) {
|
|
self._destroy();
|
|
}
|
|
}
|
|
|
|
|
|
function connect(self, address, port, addressType, localAddress) {
|
|
if (port) {
|
|
self.remotePort = port;
|
|
}
|
|
self.remoteAddress = address;
|
|
|
|
// TODO return promise from Socket.prototype.connect which
|
|
// wraps _connectReq.
|
|
|
|
assert.ok(self._connecting);
|
|
|
|
if (localAddress) {
|
|
var r;
|
|
if (addressType == 6) {
|
|
r = self._handle.bind6(localAddress);
|
|
} else {
|
|
r = self._handle.bind(localAddress);
|
|
}
|
|
|
|
if (r) {
|
|
self._destroy(errnoException(errno, 'bind'));
|
|
return;
|
|
}
|
|
}
|
|
|
|
var connectReq;
|
|
if (addressType == 6) {
|
|
connectReq = self._handle.connect6(address, port);
|
|
} else if (addressType == 4) {
|
|
connectReq = self._handle.connect(address, port);
|
|
} else {
|
|
connectReq = self._handle.connect(address, afterConnect);
|
|
}
|
|
|
|
if (connectReq !== null) {
|
|
connectReq.oncomplete = afterConnect;
|
|
} else {
|
|
self._destroy(errnoException(errno, 'connect'));
|
|
}
|
|
}
|
|
|
|
|
|
Socket.prototype.connect = function(options, cb) {
|
|
if (typeof options !== 'object') {
|
|
// Old API:
|
|
// connect(port, [host], [cb])
|
|
// connect(path, [cb]);
|
|
var args = normalizeConnectArgs(arguments);
|
|
return Socket.prototype.connect.apply(this, args);
|
|
}
|
|
|
|
var self = this;
|
|
var pipe = !!options.path;
|
|
|
|
if (this.destroyed || !this._handle) {
|
|
this._handle = pipe ? createPipe() : createTCP();
|
|
initSocketHandle(this);
|
|
}
|
|
|
|
if (typeof cb === 'function') {
|
|
self.on('connect', cb);
|
|
}
|
|
|
|
timers.active(this);
|
|
|
|
self._connecting = true;
|
|
self.writable = true;
|
|
|
|
if (pipe) {
|
|
connect(self, options.path);
|
|
|
|
} else if (!options.host) {
|
|
debug('connect: missing host');
|
|
connect(self, '127.0.0.1', options.port, 4);
|
|
|
|
} else {
|
|
var host = options.host;
|
|
debug('connect: find host ' + host);
|
|
require('dns').lookup(host, function(err, ip, addressType) {
|
|
// It's possible we were destroyed while looking this up.
|
|
// XXX it would be great if we could cancel the promise returned by
|
|
// the look up.
|
|
if (!self._connecting) return;
|
|
|
|
if (err) {
|
|
// net.createConnection() creates a net.Socket object and
|
|
// immediately calls net.Socket.connect() on it (that's us).
|
|
// There are no event listeners registered yet so defer the
|
|
// error event to the next tick.
|
|
process.nextTick(function() {
|
|
self.emit('error', err);
|
|
self._destroy();
|
|
});
|
|
} else {
|
|
timers.active(self);
|
|
|
|
addressType = addressType || 4;
|
|
|
|
// node_net.cc handles null host names graciously but user land
|
|
// expects remoteAddress to have a meaningful value
|
|
ip = ip || (addressType === 4 ? '127.0.0.1' : '0:0:0:0:0:0:0:1');
|
|
|
|
connect(self, ip, options.port, addressType, options.localAddress);
|
|
}
|
|
});
|
|
}
|
|
return self;
|
|
};
|
|
|
|
|
|
function afterConnect(status, handle, req, readable, writable) {
|
|
var self = handle.owner;
|
|
|
|
// callback may come after call to destroy
|
|
if (self.destroyed) {
|
|
return;
|
|
}
|
|
|
|
assert.equal(handle, self._handle);
|
|
|
|
debug('afterConnect');
|
|
|
|
assert.ok(self._connecting);
|
|
self._connecting = false;
|
|
|
|
if (status == 0) {
|
|
self.readable = readable;
|
|
self.writable = writable;
|
|
timers.active(self);
|
|
|
|
if (self.readable) {
|
|
handle.readStart();
|
|
}
|
|
|
|
if (self._connectQueue) {
|
|
debug('Drain the connect queue');
|
|
var connectQueue = self._connectQueue;
|
|
for (var i = 0; i < connectQueue.length; i++) {
|
|
self._write.apply(self, connectQueue[i]);
|
|
}
|
|
self._connectQueueCleanUp();
|
|
}
|
|
|
|
self.emit('connect');
|
|
|
|
if (self._flags & FLAG_SHUTDOWNQUED) {
|
|
// end called before connected - call end now with no data
|
|
self._flags &= ~FLAG_SHUTDOWNQUED;
|
|
self.end();
|
|
}
|
|
} else {
|
|
self._connectQueueCleanUp();
|
|
self._destroy(errnoException(errno, 'connect'));
|
|
}
|
|
}
|
|
|
|
|
|
function errnoException(errorno, syscall) {
|
|
// TODO make this more compatible with ErrnoException from src/node.cc
|
|
// Once all of Node is using this function the ErrnoException from
|
|
// src/node.cc should be removed.
|
|
var e = new Error(syscall + ' ' + errorno);
|
|
e.errno = e.code = errorno;
|
|
e.syscall = syscall;
|
|
return e;
|
|
}
|
|
|
|
|
|
|
|
|
|
function Server(/* [ options, ] listener */) {
|
|
if (!(this instanceof Server)) return new Server(arguments[0], arguments[1]);
|
|
events.EventEmitter.call(this);
|
|
|
|
var self = this;
|
|
|
|
var options;
|
|
|
|
if (typeof arguments[0] == 'function') {
|
|
options = {};
|
|
self.on('connection', arguments[0]);
|
|
} else {
|
|
options = arguments[0] || {};
|
|
|
|
if (typeof arguments[1] == 'function') {
|
|
self.on('connection', arguments[1]);
|
|
}
|
|
}
|
|
|
|
this._connections = 0;
|
|
|
|
// when server is using slaves .connections is not reliable
|
|
// so null will be return if thats the case
|
|
Object.defineProperty(this, 'connections', {
|
|
get: function() {
|
|
if (self._usingSlaves) {
|
|
return null;
|
|
}
|
|
return self._connections;
|
|
},
|
|
set: function(val) {
|
|
return (self._connections = val);
|
|
},
|
|
configurable: true, enumerable: true
|
|
});
|
|
|
|
this.allowHalfOpen = options.allowHalfOpen || false;
|
|
|
|
this._handle = null;
|
|
}
|
|
util.inherits(Server, events.EventEmitter);
|
|
exports.Server = Server;
|
|
|
|
|
|
function toNumber(x) { return (x = Number(x)) >= 0 ? x : false; }
|
|
|
|
|
|
var createServerHandle = exports._createServerHandle =
|
|
function(address, port, addressType) {
|
|
var r = 0;
|
|
// assign handle in listen, and clean up if bind or listen fails
|
|
var handle;
|
|
|
|
if (port == -1 && addressType == -1) {
|
|
handle = createPipe();
|
|
if (process.platform === 'win32') {
|
|
var instances = parseInt(process.env.NODE_PENDING_PIPE_INSTANCES);
|
|
if (!isNaN(instances)) {
|
|
handle.setPendingInstances(instances);
|
|
}
|
|
}
|
|
} else {
|
|
handle = createTCP();
|
|
}
|
|
|
|
if (address || port) {
|
|
debug('bind to ' + address);
|
|
if (addressType == 6) {
|
|
r = handle.bind6(address, port);
|
|
} else {
|
|
r = handle.bind(address, port);
|
|
}
|
|
}
|
|
|
|
if (r) {
|
|
handle.close();
|
|
handle = null;
|
|
}
|
|
|
|
return handle;
|
|
};
|
|
|
|
|
|
Server.prototype._listen2 = function(address, port, addressType, backlog) {
|
|
var self = this;
|
|
var r = 0;
|
|
|
|
// If there is not yet a handle, we need to create one and bind.
|
|
// In the case of a server sent via IPC, we don't need to do this.
|
|
if (!self._handle) {
|
|
self._handle = createServerHandle(address, port, addressType);
|
|
if (!self._handle) {
|
|
process.nextTick(function() {
|
|
self.emit('error', errnoException(errno, 'listen'));
|
|
});
|
|
return;
|
|
}
|
|
}
|
|
|
|
self._handle.onconnection = onconnection;
|
|
self._handle.owner = self;
|
|
|
|
// Use a backlog of 512 entries. We pass 511 to the listen() call because
|
|
// the kernel does: backlogsize = roundup_pow_of_two(backlogsize + 1);
|
|
// which will thus give us a backlog of 512 entries.
|
|
r = self._handle.listen(backlog || 511);
|
|
|
|
if (r) {
|
|
self._handle.close();
|
|
self._handle = null;
|
|
process.nextTick(function() {
|
|
self.emit('error', errnoException(errno, 'listen'));
|
|
});
|
|
return;
|
|
}
|
|
|
|
// generate connection key, this should be unique to the connection
|
|
this._connectionKey = addressType + ':' + address + ':' + port;
|
|
|
|
process.nextTick(function() {
|
|
self.emit('listening');
|
|
});
|
|
};
|
|
|
|
|
|
function listen(self, address, port, addressType, backlog) {
|
|
if (!cluster) cluster = require('cluster');
|
|
|
|
if (cluster.isWorker) {
|
|
cluster._getServer(self, address, port, addressType, function(handle) {
|
|
self._handle = handle;
|
|
self._listen2(address, port, addressType, backlog);
|
|
});
|
|
} else {
|
|
self._listen2(address, port, addressType, backlog);
|
|
}
|
|
}
|
|
|
|
|
|
Server.prototype.listen = function() {
|
|
var self = this;
|
|
|
|
var lastArg = arguments[arguments.length - 1];
|
|
if (typeof lastArg == 'function') {
|
|
self.once('listening', lastArg);
|
|
}
|
|
|
|
var port = toNumber(arguments[0]);
|
|
|
|
// The third optional argument is the backlog size.
|
|
// When the ip is omitted it can be the second argument.
|
|
var backlog = toNumber(arguments[1]) || toNumber(arguments[2]);
|
|
|
|
var TCP = process.binding('tcp_wrap').TCP;
|
|
|
|
if (arguments.length == 0 || typeof arguments[0] == 'function') {
|
|
// Don't bind(). OS will assign a port with INADDR_ANY.
|
|
// The port can be found with server.address()
|
|
listen(self, null, null, backlog);
|
|
|
|
} else if (arguments[0] instanceof TCP) {
|
|
self._handle = arguments[0];
|
|
listen(self, null, -1, -1, backlog);
|
|
|
|
} else if (isPipeName(arguments[0])) {
|
|
// UNIX socket or Windows pipe.
|
|
var pipeName = self._pipeName = arguments[0];
|
|
listen(self, pipeName, -1, -1, backlog);
|
|
|
|
} else if (typeof arguments[1] == 'undefined' ||
|
|
typeof arguments[1] == 'function' ||
|
|
typeof arguments[1] == 'number') {
|
|
// The first argument is the port, no IP given.
|
|
listen(self, '0.0.0.0', port, 4, backlog);
|
|
|
|
} else {
|
|
// The first argument is the port, the second an IP.
|
|
require('dns').lookup(arguments[1], function(err, ip, addressType) {
|
|
if (err) {
|
|
self.emit('error', err);
|
|
} else {
|
|
listen(self, ip || '0.0.0.0', port, ip ? addressType : 4, backlog);
|
|
}
|
|
});
|
|
}
|
|
return self;
|
|
};
|
|
|
|
Server.prototype.address = function() {
|
|
if (this._handle && this._handle.getsockname) {
|
|
return this._handle.getsockname();
|
|
} else if (this._pipeName) {
|
|
return this._pipeName;
|
|
} else {
|
|
return null;
|
|
}
|
|
};
|
|
|
|
function onconnection(clientHandle) {
|
|
var handle = this;
|
|
var self = handle.owner;
|
|
|
|
debug('onconnection');
|
|
|
|
if (!clientHandle) {
|
|
self.emit('error', errnoException(errno, 'accept'));
|
|
return;
|
|
}
|
|
|
|
if (self.maxConnections && self._connections >= self.maxConnections) {
|
|
clientHandle.close();
|
|
return;
|
|
}
|
|
|
|
var socket = new Socket({
|
|
handle: clientHandle,
|
|
allowHalfOpen: self.allowHalfOpen
|
|
});
|
|
socket.readable = socket.writable = true;
|
|
|
|
socket.resume();
|
|
|
|
self._connections++;
|
|
socket.server = self;
|
|
|
|
DTRACE_NET_SERVER_CONNECTION(socket);
|
|
self.emit('connection', socket);
|
|
socket.emit('connect');
|
|
}
|
|
|
|
|
|
Server.prototype.close = function(cb) {
|
|
if (!this._handle) {
|
|
// Throw error. Follows net_legacy behaviour.
|
|
throw new Error('Not running');
|
|
}
|
|
|
|
if (cb) {
|
|
this.once('close', cb);
|
|
}
|
|
this._handle.close();
|
|
this._handle = null;
|
|
this._emitCloseIfDrained();
|
|
|
|
// fetch new socket lists
|
|
if (this._usingSlaves) {
|
|
this._slaves.forEach(function(socketList) {
|
|
if (socketList.list.length === 0) return;
|
|
socketList.update();
|
|
});
|
|
}
|
|
|
|
return this;
|
|
};
|
|
|
|
Server.prototype._emitCloseIfDrained = function() {
|
|
var self = this;
|
|
|
|
if (self._handle || self._connections) return;
|
|
|
|
process.nextTick(function() {
|
|
self.emit('close');
|
|
});
|
|
};
|
|
|
|
|
|
Server.prototype.listenFD = function(fd, type) {
|
|
throw new Error('This API is no longer supported. See child_process.fork');
|
|
};
|
|
|
|
// when sending a socket using fork IPC this function is executed
|
|
Server.prototype._setupSlave = function(socketList) {
|
|
if (!this._usingSlaves) {
|
|
this._usingSlaves = true;
|
|
this._slaves = [];
|
|
}
|
|
this._slaves.push(socketList);
|
|
};
|
|
|
|
|
|
// TODO: isIP should be moved to the DNS code. Putting it here now because
|
|
// this is what the legacy system did.
|
|
// NOTE: This does not accept IPv6 with an IPv4 dotted address at the end,
|
|
// and it does not detect more than one double : in a string.
|
|
exports.isIP = function(input) {
|
|
if (!input) {
|
|
return 0;
|
|
} else if (/^(\d?\d?\d)\.(\d?\d?\d)\.(\d?\d?\d)\.(\d?\d?\d)$/.test(input)) {
|
|
var parts = input.split('.');
|
|
for (var i = 0; i < parts.length; i++) {
|
|
var part = parseInt(parts[i]);
|
|
if (part < 0 || 255 < part) {
|
|
return 0;
|
|
}
|
|
}
|
|
return 4;
|
|
} else if (/^::|^::1|^([a-fA-F0-9]{1,4}::?){1,7}([a-fA-F0-9]{1,4})$/.test(
|
|
input)) {
|
|
return 6;
|
|
} else {
|
|
return 0;
|
|
}
|
|
};
|
|
|
|
|
|
exports.isIPv4 = function(input) {
|
|
return exports.isIP(input) === 4;
|
|
};
|
|
|
|
|
|
exports.isIPv6 = function(input) {
|
|
return exports.isIP(input) === 6;
|
|
};
|
|
|
|
|
|
if (process.platform === 'win32') {
|
|
var simultaneousAccepts;
|
|
|
|
exports._setSimultaneousAccepts = function(handle) {
|
|
if (typeof handle === 'undefined') {
|
|
return;
|
|
}
|
|
|
|
if (typeof simultaneousAccepts === 'undefined') {
|
|
simultaneousAccepts = (process.env.NODE_MANY_ACCEPTS &&
|
|
process.env.NODE_MANY_ACCEPTS !== '0');
|
|
}
|
|
|
|
if (handle._simultaneousAccepts !== simultaneousAccepts) {
|
|
handle.setSimultaneousAccepts(simultaneousAccepts);
|
|
handle._simultaneousAccepts = simultaneousAccepts;
|
|
}
|
|
};
|
|
} else {
|
|
exports._setSimultaneousAccepts = function(handle) {};
|
|
}
|