UNPKG

@wearemothership/socket.io-stream

Version:
266 lines (220 loc) 5.27 kB
var util = require('util'); var Duplex = require('stream').Duplex; var bind = require('component-bind'); var uuid = require('./uuid'); var debug = require('debug')('socket.io-stream:iostream'); module.exports = IOStream; util.inherits(IOStream, Duplex); /** * Duplex * * @param {Object} options * @api private */ function IOStream(options) { if (!(this instanceof IOStream)) { return new IOStream(options); } IOStream.super_.call(this, options); this.options = options; this.id = uuid(); this.socket = null; // Buffers this.pushBuffer = []; this.writeBuffer = []; // Op states this._readable = false; this._writable = false; this.destroyed = false; // default to *not* allowing half open sockets this.allowHalfOpen = options && options.allowHalfOpen || false; this.on('finish', this._onfinish); this.on('end', this._onend); this.on('error', this._onerror); } /** * Ensures that no more I/O activity happens on this stream. * Not necessary in the usual case. * * @api public */ IOStream.prototype.destroy = function() { debug('destroy'); if (this.destroyed) { debug('already destroyed'); return; } this.readable = this.writable = false; if (this.socket) { debug('clean up'); this.socket.cleanup(this.id); this.socket = null; } this.destroyed = true; }; /** * Local read * * @api private */ IOStream.prototype._read = function(size) { var push; // We can not read from the socket if it's destroyed obviously ... if (this.destroyed) return; if (this.pushBuffer.length) { // flush buffer and end if it exists. while (push = this.pushBuffer.shift()) { if (!push()) break; } return; } this._readable = true; // Go get data from remote stream // Calls // ._onread remotely // then // ._onwrite locally this.socket._read(this.id, size); }; /** * Read from remote stream * * @api private */ IOStream.prototype._onread = function(size) { var write = this.writeBuffer.shift(); if (write) return write(); this._writable = true; }; /** * Write local data to remote stream * Calls * remtote ._onwrite * * @api private */ IOStream.prototype._write = function(chunk, encoding, callback) { var self = this; function write() { // We can not write to the socket if it's destroyed obviously ... if (self.destroyed) return; self._writable = false; self.socket._write(self.id, chunk, encoding, callback); } if (this._writable) { write(); } else { this.writeBuffer.push(write); } }; /** * Write the data fetched remotely * so that we can now read locally * * @api private */ IOStream.prototype._onwrite = function(chunk, encoding, callback) { var self = this; function push() { self._readable = false; var ret = self.push(chunk || '', encoding); callback(); return ret; } if (this._readable) { push(); } else { this.pushBuffer.push(push); } }; /** * When ending send 'end' event to remote stream * * @api private */ IOStream.prototype._end = function() { if (this.pushBuffer.length) { // end after flushing buffer. this.pushBuffer.push(bind(this, '_done')); } else { this._done(); } }; /** * Remote stream just ended * * @api private */ IOStream.prototype._done = function() { this._readable = false; // signal the end of the data. return this.push(null); }; /** * the user has called .end(), and all the bytes have been * sent out to the other side. * If allowHalfOpen is false, or if the readable side has * ended already, then destroy. * If allowHalfOpen is true, then we need to set writable false, * so that only the writable side will be cleaned up. * * @api private */ IOStream.prototype._onfinish = function() { debug('_onfinish'); // Local socket just finished // send 'end' event to remote if (this.socket) { this.socket._end(this.id); } this.writable = false; this._writableState.ended = true; if (!this.readable || this._readableState.ended) { debug('_onfinish: ended, destroy %s', this._readableState); return this.destroy(); } debug('_onfinish: not ended'); if (!this.allowHalfOpen) { this.push(null); // just in case we're waiting for an EOF. if (this.readable && !this._readableState.endEmitted) { this.read(0); } } }; /** * the EOF has been received, and no more bytes are coming. * if the writable side has ended already, then clean everything * up. * * @api private */ IOStream.prototype._onend = function() { debug('_onend'); this.readable = false; this._readableState.ended = true; if (!this.writable || this._writableState.finished) { debug('_onend: %s', this._writableState); return this.destroy(); } debug('_onend: not finished'); if (!this.allowHalfOpen) { this.end(); } }; /** * When error in local stream * notyify remote * if err.remote = true * then error happened on remote stream * * @api private */ IOStream.prototype._onerror = function(err) { // check if the error came from remote stream. if (!err.remote && this.socket) { // notify the error to the corresponding remote stream. this.socket._error(this.id, err); } this.destroy(); };