UNPKG

cloudworker-proxy

Version:
252 lines (211 loc) 4.96 kB
'use strict'; const util = require('util'); const Duplex = require('stream').Duplex; const uuid = require('./uuid'); 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 () { if (this.destroyed) { return; } this.readable = this.writable = false; if (this.socket) { this.socket.cleanup(this.id); this.socket = null; } this.destroyed = true; }; /** * Local read * * @api private */ IOStream.prototype._read = function (size) { let 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); }; /* eslint-disable consistent-return */ /** * Read from remote stream * * @api private */ IOStream.prototype._onread = function () { const 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) { const 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) { const self = this; function push() { self._readable = false; const 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(this._done.bind(this)); } 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 () { // 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) { return this.destroy(); } 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 () { this.readable = false; this._readableState.ended = true; if (!this.writable || this._writableState.finished) { return this.destroy(); } 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(); };