438 lines
9.5 KiB
JavaScript
438 lines
9.5 KiB
JavaScript
|
var assert = require('assert');
|
||
|
var util = require('util');
|
||
|
|
||
|
var EventEmitter = require('events').EventEmitter;
|
||
|
var Buffer = require('buffer').Buffer;
|
||
|
|
||
|
var Queue = require('./queue');
|
||
|
|
||
|
// Node.js version
|
||
|
var mode = /^v0\.8\./.test(process.version) ? 'rusty' :
|
||
|
/^v0\.(9|10)\./.test(process.version) ? 'old' :
|
||
|
'modern';
|
||
|
|
||
|
function Handle(stream, options) {
|
||
|
EventEmitter.call(this);
|
||
|
|
||
|
this._stream = stream;
|
||
|
this._flowing = false;
|
||
|
this._reading = false;
|
||
|
this._options = options || {};
|
||
|
|
||
|
this.onread = null;
|
||
|
|
||
|
// Pending requests
|
||
|
this.pending = new Queue();
|
||
|
|
||
|
// Start handle once `onread` is set
|
||
|
if (mode === 'rusty') {
|
||
|
var self = this;
|
||
|
Object.defineProperty(this, 'onread', {
|
||
|
set: function(value) {
|
||
|
Object.defineProperty(self, 'onread', {
|
||
|
value: value
|
||
|
});
|
||
|
process.nextTick(function() {
|
||
|
self.readStart();
|
||
|
});
|
||
|
}
|
||
|
});
|
||
|
}
|
||
|
|
||
|
// NOTE: v0.8 has some odd .pause()/.resume() semantics in http.js
|
||
|
if (mode === 'rusty')
|
||
|
this.writeQueueSize = 0;
|
||
|
else if (mode !== 'modern')
|
||
|
this.writeQueueSize = 1;
|
||
|
|
||
|
if (mode === 'rusty') {
|
||
|
if (this._stream)
|
||
|
this._rustyInit();
|
||
|
else
|
||
|
this.once('stream', this._rustyInit);
|
||
|
}
|
||
|
}
|
||
|
util.inherits(Handle, EventEmitter);
|
||
|
module.exports = Handle;
|
||
|
|
||
|
Handle.mode = mode;
|
||
|
|
||
|
Handle.create = function create(stream, options) {
|
||
|
return new Handle(stream, options);
|
||
|
};
|
||
|
|
||
|
Handle.prototype._queueReq = function _queueReq(type, req) {
|
||
|
return this.pending.append(type, req);
|
||
|
};
|
||
|
|
||
|
Handle.prototype._pendingList = function _pendingList() {
|
||
|
var list = [];
|
||
|
while (!this.pending.isEmpty())
|
||
|
list.push(this.pending.first().dequeue());
|
||
|
return list;
|
||
|
};
|
||
|
|
||
|
Handle.prototype.setStream = function setStream(stream) {
|
||
|
assert(this._stream === null, 'Can\'t set stream two times');
|
||
|
this._stream = stream;
|
||
|
|
||
|
this.emit('stream', stream);
|
||
|
};
|
||
|
|
||
|
Handle.prototype.readStart = function readStart() {
|
||
|
this._reading = true;
|
||
|
|
||
|
if (!this._stream) {
|
||
|
this.once('stream', this.readStart);
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
if (!this._flowing) {
|
||
|
this._flowing = true;
|
||
|
this._flow();
|
||
|
}
|
||
|
|
||
|
this._stream.resume();
|
||
|
return 0;
|
||
|
};
|
||
|
|
||
|
Handle.prototype.readStop = function readStop() {
|
||
|
this._reading = false;
|
||
|
|
||
|
if (!this._stream) {
|
||
|
this.once('stream', this.readStop);
|
||
|
return 0;
|
||
|
}
|
||
|
this._stream.pause();
|
||
|
return 0;
|
||
|
};
|
||
|
|
||
|
if (mode === 'modern') {
|
||
|
var uv = process.binding('uv');
|
||
|
|
||
|
Handle.prototype._flow = function flow() {
|
||
|
var self = this;
|
||
|
this._stream.on('data', function(chunk) {
|
||
|
self.onread(chunk.length, chunk);
|
||
|
});
|
||
|
|
||
|
this._stream.on('end', function() {
|
||
|
self.onread(uv.UV_EOF, new Buffer(0));
|
||
|
});
|
||
|
|
||
|
this._stream.on('close', function() {
|
||
|
setImmediate(function() {
|
||
|
if (self._reading)
|
||
|
self.onread(uv.UV_ECONNRESET, new Buffer(0));
|
||
|
});
|
||
|
});
|
||
|
};
|
||
|
|
||
|
Handle.prototype._close = function _close() {
|
||
|
var list = this._pendingList();
|
||
|
|
||
|
var self = this;
|
||
|
setImmediate(function() {
|
||
|
for (var i = 0; i < list.length; i++) {
|
||
|
var req = list[i];
|
||
|
req.oncomplete(uv.UV_ECANCELED, self, req);
|
||
|
}
|
||
|
});
|
||
|
|
||
|
this.readStop();
|
||
|
};
|
||
|
} else if (mode === 'old') {
|
||
|
Handle.prototype._flow = function flow() {
|
||
|
var self = this;
|
||
|
this._stream.on('data', function(chunk) {
|
||
|
self.onread(chunk, 0, chunk.length);
|
||
|
});
|
||
|
|
||
|
this._stream.on('end', function() {
|
||
|
var errno = process._errno;
|
||
|
process._errno = 'EOF';
|
||
|
self.onread(null, 0, 0);
|
||
|
if (process._errno === 'EOF')
|
||
|
process._errno = errno;
|
||
|
});
|
||
|
|
||
|
this._stream.on('close', function() {
|
||
|
setImmediate(function() {
|
||
|
if (!self._reading)
|
||
|
return;
|
||
|
|
||
|
var errno = process._errno;
|
||
|
process._errno = 'ECONNRESET';
|
||
|
self.onread(null, 0, 0);
|
||
|
if (process._errno === 'ECONNRESET')
|
||
|
process._errno = errno;
|
||
|
});
|
||
|
});
|
||
|
};
|
||
|
|
||
|
Handle.prototype._close = function _close() {
|
||
|
var list = this._pendingList();
|
||
|
|
||
|
var self = this;
|
||
|
setImmediate(function() {
|
||
|
for (var i = 0; i < list.length; i++) {
|
||
|
process._errno = 'CANCELED';
|
||
|
var req = list[i];
|
||
|
req.oncomplete(-1, self, req);
|
||
|
}
|
||
|
});
|
||
|
|
||
|
this.readStop();
|
||
|
};
|
||
|
} else {
|
||
|
Handle.prototype._rustyInit = function _rustyInit() {
|
||
|
var self = this;
|
||
|
|
||
|
this._stream.on('close', function() {
|
||
|
process.nextTick(function() {
|
||
|
if (!self._reading)
|
||
|
return;
|
||
|
|
||
|
var errno = global.errno;
|
||
|
global.errno = 'ECONNRESET';
|
||
|
self.onread(null, 0, 0);
|
||
|
if (global.errno === 'ECONNRESET')
|
||
|
global.errno = errno;
|
||
|
});
|
||
|
});
|
||
|
};
|
||
|
|
||
|
Handle.prototype._flow = function flow() {
|
||
|
var self = this;
|
||
|
this._stream.on('data', function(chunk) {
|
||
|
self.onread(chunk, 0, chunk.length);
|
||
|
});
|
||
|
|
||
|
this._stream.on('end', function() {
|
||
|
var errno = global.errno;
|
||
|
global.errno = 'EOF';
|
||
|
self.onread(null, 0, 0);
|
||
|
if (global.errno === 'EOF')
|
||
|
global.errno = errno;
|
||
|
});
|
||
|
};
|
||
|
|
||
|
Handle.prototype._close = function _close() {
|
||
|
var list = this._pendingList();
|
||
|
|
||
|
var self = this;
|
||
|
process.nextTick(function() {
|
||
|
for (var i = 0; i < list.length; i++) {
|
||
|
var req = list[i];
|
||
|
global.errno = 'CANCELED';
|
||
|
req.oncomplete(-1, self, req);
|
||
|
}
|
||
|
});
|
||
|
|
||
|
this.readStop();
|
||
|
};
|
||
|
}
|
||
|
|
||
|
if (mode === 'modern') {
|
||
|
Handle.prototype.shutdown = function shutdown(req) {
|
||
|
var wrap = this._queueReq('shutdown', req);
|
||
|
|
||
|
if (!this._stream) {
|
||
|
this.once('stream', function() {
|
||
|
this._shutdown(wrap);
|
||
|
});
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
return this._shutdown(wrap);
|
||
|
};
|
||
|
|
||
|
Handle.prototype._shutdown = function _shutdown(wrap) {
|
||
|
var self = this;
|
||
|
this._stream.end(function() {
|
||
|
var req = wrap.dequeue();
|
||
|
if (!req)
|
||
|
return;
|
||
|
|
||
|
req.oncomplete(0, self, req);
|
||
|
});
|
||
|
return 0;
|
||
|
};
|
||
|
} else {
|
||
|
Handle.prototype.shutdown = function shutdown(req) {
|
||
|
if (!req)
|
||
|
req = {};
|
||
|
|
||
|
var wrap = this._queueReq('shutdown', req);
|
||
|
|
||
|
if (!this._stream) {
|
||
|
this.once('stream', function() {
|
||
|
this._shutdown(wrap);
|
||
|
});
|
||
|
return req;
|
||
|
}
|
||
|
|
||
|
this._shutdown(wrap);
|
||
|
|
||
|
return req;
|
||
|
};
|
||
|
|
||
|
Handle.prototype._shutdown = function _shutdown(wrap) {
|
||
|
var self = this;
|
||
|
this._stream.end(function() {
|
||
|
var req = wrap.dequeue();
|
||
|
if (!req)
|
||
|
return;
|
||
|
req.oncomplete(0, self, req);
|
||
|
});
|
||
|
};
|
||
|
}
|
||
|
|
||
|
if (mode !== 'rusty') {
|
||
|
Handle.prototype.close = function close(callback) {
|
||
|
this._close();
|
||
|
|
||
|
if (!this._stream) {
|
||
|
this.once('stream', function() {
|
||
|
this.close(callback);
|
||
|
});
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
if (this._options.close)
|
||
|
this._options.close(callback);
|
||
|
else
|
||
|
process.nextTick(callback);
|
||
|
|
||
|
return 0;
|
||
|
};
|
||
|
} else {
|
||
|
Handle.prototype.close = function close() {
|
||
|
this._close();
|
||
|
|
||
|
if (!this._stream)
|
||
|
this.once('stream', this.close);
|
||
|
else if (this._options.close)
|
||
|
this._options.close(function() {});
|
||
|
|
||
|
return 0;
|
||
|
};
|
||
|
}
|
||
|
|
||
|
if (mode === 'modern') {
|
||
|
Handle.prototype.writeEnc = function writeEnc(req, data, enc) {
|
||
|
var wrap = this._queueReq('write', req);
|
||
|
|
||
|
if (!this._stream) {
|
||
|
this.once('stream', function() {
|
||
|
this._writeEnc(wrap, req, data, enc);
|
||
|
});
|
||
|
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
return this._writeEnc(wrap, req, data, enc);
|
||
|
};
|
||
|
|
||
|
Handle.prototype._writeEnc = function _writeEnc(wrap, req, data, enc) {
|
||
|
var self = this;
|
||
|
|
||
|
req.async = true;
|
||
|
req.bytes = data.length;
|
||
|
|
||
|
if (wrap.isEmpty())
|
||
|
return 0;
|
||
|
|
||
|
this._stream.write(data, enc, function() {
|
||
|
var req = wrap.dequeue();
|
||
|
if (!req)
|
||
|
return;
|
||
|
req.oncomplete(0, self, req);
|
||
|
});
|
||
|
|
||
|
return 0;
|
||
|
};
|
||
|
} else {
|
||
|
Handle.prototype.writeEnc = function writeEnc(data, ignored, enc, req) {
|
||
|
if (!req)
|
||
|
req = { bytes: data.length };
|
||
|
|
||
|
var wrap = this._queueReq('write', req);
|
||
|
|
||
|
if (!this._stream) {
|
||
|
this.once('stream', function() {
|
||
|
this._writeEnc(data, ignored, enc, wrap);
|
||
|
});
|
||
|
return req;
|
||
|
}
|
||
|
|
||
|
this._writeEnc(data, ignored, enc, wrap);
|
||
|
return req;
|
||
|
};
|
||
|
|
||
|
Handle.prototype._writeEnc = function _writeEnc(data, ignored, enc, wrap) {
|
||
|
var self = this;
|
||
|
var buffer = new Buffer(data, enc);
|
||
|
|
||
|
if (wrap.isEmpty())
|
||
|
return;
|
||
|
|
||
|
this._stream.write(buffer, function() {
|
||
|
var req = wrap.dequeue();
|
||
|
if (!req)
|
||
|
return;
|
||
|
req.oncomplete(0, self, req);
|
||
|
});
|
||
|
};
|
||
|
}
|
||
|
|
||
|
Handle.prototype.writeBuffer = function writeBuffer(req, data) {
|
||
|
return this.writeEnc(req, data, null);
|
||
|
};
|
||
|
|
||
|
Handle.prototype.writeAsciiString = function writeAsciiString(req, data) {
|
||
|
return this.writeEnc(req, data, 'ascii');
|
||
|
};
|
||
|
|
||
|
Handle.prototype.writeUtf8String = function writeUtf8String(req, data) {
|
||
|
return this.writeEnc(req, data, 'utf8');
|
||
|
};
|
||
|
|
||
|
Handle.prototype.writeUcs2String = function writeUcs2String(req, data) {
|
||
|
return this.writeEnc(req, data, 'ucs2');
|
||
|
};
|
||
|
|
||
|
Handle.prototype.writeBinaryString = function writeBinaryString(req, data) {
|
||
|
return this.writeEnc(req, data, 'binary');
|
||
|
};
|
||
|
|
||
|
Handle.prototype.writeLatin1String = function writeLatin1String(req, data) {
|
||
|
return this.writeEnc(req, data, 'binary');
|
||
|
};
|
||
|
|
||
|
// v0.8
|
||
|
Handle.prototype.getsockname = function getsockname() {
|
||
|
if (this._options.getPeerName)
|
||
|
return this._options.getPeerName();
|
||
|
return null;
|
||
|
};
|
||
|
|
||
|
if (mode === 'modern') {
|
||
|
Handle.prototype.getpeername = function getpeername(out) {
|
||
|
var res = this.getsockname();
|
||
|
if (!res)
|
||
|
return -1;
|
||
|
|
||
|
Object.keys(res).forEach(function(key) {
|
||
|
out[key] = res[key];
|
||
|
});
|
||
|
|
||
|
return 0;
|
||
|
};
|
||
|
} else {
|
||
|
// v0.10
|
||
|
Handle.prototype.getpeername = function getpeername() {
|
||
|
return this.getsockname();
|
||
|
};
|
||
|
}
|