UNPKG

jschan

Version:

node.js port of docker/libchan

263 lines (211 loc) 6.1 kB
'use strict'; var EventEmitter = require('events').EventEmitter; var channels = require('./channels'); var inherits = require('inherits'); var through = require('through2'); var ReadChannel = channels.ReadChannel; var WriteChannel = channels.WriteChannel; var ByteStream = channels.ByteStream; var encoder = require('../encoder'); var PassThrough = require('readable-stream').PassThrough; var async = require('async'); function StreamSession(inStream, outStream, opts, server) { if (!(this instanceof StreamSession)) { return new StreamSession(inStream, outStream, opts, server); } opts = opts || {}; this._isServer = opts.server || false; if (opts.header === false) { this._haveHeaders = false; } else { this._haveHeaders = true; } this._inStream = inStream; this._outStream = outStream; this._delayedChannels = []; this._streams = {}; this._nextId = this._isServer ? 0 : 1; this._toBeWritten = []; this._encoder = encoder(this, channels); var that = this; this._readPipe = inStream .pipe(this._encoder.decoder(opts)) .pipe(through.obj(function(chunk, enc, done) { if (chunk.id === undefined) { that.emit('error', new Error('wrong message format, missing id')); return; } var count = EventEmitter.listenerCount(that, 'channel'); var stream = that._streams[chunk.id]; if (!stream && !chunk.parent) { stream = new ReadChannel(that, chunk.id); that._streams[stream.id] = stream; stream.on('close', function() { delete that._streams[stream.id]; }); if (count > 0 ) { that.emit('channel', stream); } else { that._delayedChannels.push(stream); } } if (!stream) { // we need to queue data stream = new PassThrough({ objectMode: true, highWaterMark: 16 }); stream.id = chunk.id; that._streams[stream.id] = stream; } if (stream.dispatch) { stream.dispatch(chunk.data, done); } else if (chunk.data) { stream.write(chunk.data, null, done); } else { stream.end(); done(); } })); this.on('newListener', function(event, listener) { var chan; if (event === 'channel') { while ((chan = this._delayedChannels.pop())) { listener(chan); } } }); this._encoder.on('channel', function(chan) { if (that._streams[chan.id]) { that._streams[chan.id].pipe(chan, { end: false }); } that._streams[chan.id] = chan; }); if (server) { this.on('channel', server); } this._inStream.on('error', this.emit.bind(this, 'error')); if (this._inStream !== this._outStream) { this._outStream.on('error', this.emit.bind(this, 'error')); } var count = 2; function complete() { /*jshint validthis:true */ this.removeListener('finish', complete); this.removeListener('end', complete); this.removeListener('close', complete); if (--count === 0) { that._closing = false; that._closed = true; that.emit('close'); } } that._inStream.on('end', complete); that._outStream.on('finish', complete); that._inStream.on('close', complete); that._outStream.on('close', complete); } inherits(StreamSession, EventEmitter); function createChannel(session, Type, parent) { var chan = new Type(session, session._nextId); if (parent) { chan.parentId = parent.id; } session._nextId += 2; session._streams[chan.id] = chan; return chan; } StreamSession.prototype._createWriteChannel = function(parent) { var chan = createChannel(this, WriteChannel, parent); chan.on('finish', function() { delete this._session._streams[chan.id]; }); return chan; }; StreamSession.prototype._createReadChannel = function(parent) { var chan = createChannel(this, ReadChannel, parent); var that = this; chan.on('close', function() { delete that._streams[chan.id]; }); return chan; }; StreamSession.prototype._createByteStream = function(parent) { return createChannel(this, ByteStream, parent); }; StreamSession.prototype.WriteChannel = function WriteChannel() { return this._createWriteChannel(); }; StreamSession.prototype._dispatch = function dispatch(obj, chan, done) { if (this._closing) { if (done) { // we are closing everything anyway done(); } return this; } try { var encoded = this._encoder.encode(obj, chan).slice(0); // header logic copied from msgpack5.encoder if (this._haveHeaders) { var header = new Buffer(4); header.writeUInt32BE(encoded.length, 0); this._outStream.write(header); } if (this._outStream.write.length >= 2 ) { this._outStream.write(encoded, done); } else { this._outStream.write(encoded); done(); } } catch(err) { done(); // swallow any closing error this.emit('error', err); } return this; }; StreamSession.prototype.destroy = function close(wait, done) { if (typeof wait === 'function') { done = wait; wait = false; } if (this._closing) { return done && this.on('close', done) || this; } else if (this._closed) { return done && done() || this; } var that = this; if (done) { this.on('close', done); } that._closing = true; async.forEach(Object.keys(this._streams), function(id, cb) { if (wait) { that._streams[id].on('close', cb); } else { that._streams[id].destroy(cb); } }, function() { that._inStream.end(); if (that._inStream.destroy) { that._inStream.destroy(); } that._outStream.end(); if (that._outStream.destroy) { that._outStream.destroy(); } // consume all awaiting messages try { that._inStream.resume(); } catch(err) {} try { that._outStream.resume(); } catch(err) {} }); return this; }; StreamSession.prototype.close = function(cb) { this.destroy(true, cb); }; module.exports = StreamSession;