permessage-deflate
Version:
Per-message DEFLATE compression extension for WebSocket connections
176 lines (132 loc) • 4.82 kB
JavaScript
'use strict';
var Buffer = require('safe-buffer').Buffer,
zlib = require('zlib'),
common = require('./common');
var Session = function(options) {
this._level = common.fetch(options, 'level', zlib.Z_DEFAULT_COMPRESSION);
this._memLevel = common.fetch(options, 'memLevel', zlib.Z_DEFAULT_MEMLEVEL);
this._strategy = common.fetch(options, 'strategy', zlib.Z_DEFAULT_STRATEGY);
this._acceptNoContextTakeover = common.fetch(options, 'noContextTakeover', false);
this._acceptMaxWindowBits = common.fetch(options, 'maxWindowBits', undefined);
this._requestNoContextTakeover = common.fetch(options, 'requestNoContextTakeover', false);
this._requestMaxWindowBits = common.fetch(options, 'requestMaxWindowBits', undefined);
this._queueIn = [];
this._queueOut = [];
this._zlib = common.fetch(options, 'zlib', zlib);
};
Session.prototype.processIncomingMessage = function(message, callback) {
if (!message.rsv1) return callback(null, message);
if (this._lockIn) return this._queueIn.push([message, callback]);
var inflate = this._getInflate(),
chunks = [],
length = 0,
self = this;
if (this._inflate) this._lockIn = true;
var return_ = function(error, message) {
return_ = function() {};
inflate.removeListener('data', onData);
inflate.removeListener('error', onError);
if (!self._inflate) self._close(inflate);
self._lockIn = false;
var next = self._queueIn.shift();
if (next) self.processIncomingMessage.apply(self, next);
callback(error, message);
};
var onData = function(data) {
chunks.push(data);
length += data.length;
};
var onError = function(error) {
return_(error, null);
};
inflate.on('data', onData);
inflate.on('error', onError);
inflate.write(message.data);
inflate.write(Buffer.from([0x00, 0x00, 0xff, 0xff]));
inflate.flush(function() {
message.data = Buffer.concat(chunks, length);
return_(null, message);
});
};
Session.prototype.processOutgoingMessage = function(message, callback) {
if (this._lockOut) return this._queueOut.push([message, callback]);
var deflate = this._getDeflate(),
chunks = [],
length = 0,
self = this;
if (this._deflate) this._lockOut = true;
var return_ = function(error, message) {
return_ = function() {};
deflate.removeListener('data', onData);
deflate.removeListener('error', onError);
if (!self._deflate) self._close(deflate);
self._lockOut = false;
var next = self._queueOut.shift();
if (next) self.processOutgoingMessage.apply(self, next);
callback(error, message);
};
var onData = function(data) {
chunks.push(data);
length += data.length;
};
var onError = function(error) {
return_(error, null);
};
deflate.on('data', onData);
deflate.on('error', onError);
deflate.write(message.data);
var onFlush = function() {
var data = Buffer.concat(chunks, length);
message.data = data.slice(0, data.length - 4);
message.rsv1 = true;
return_(null, message);
};
if (deflate.params !== undefined)
deflate.flush(zlib.Z_SYNC_FLUSH, onFlush);
else
deflate.flush(onFlush);
};
Session.prototype.close = function() {
this._close(this._inflate);
this._inflate = null;
this._close(this._deflate);
this._deflate = null;
};
Session.prototype._getInflate = function() {
if (this._inflate) return this._inflate;
var windowBits = Math.max(this._peerWindowBits, common.MIN_WINDOW_BITS),
inflate = this._zlib.createInflateRaw({windowBits: windowBits});
if (this._peerContextTakeover) this._inflate = inflate;
return inflate;
};
Session.prototype._getDeflate = function() {
if (this._deflate) return this._deflate;
var windowBits = Math.max(this._ownWindowBits, common.MIN_WINDOW_BITS);
var deflate = this._zlib.createDeflateRaw({
windowBits: windowBits,
level: this._level,
memLevel: this._memLevel,
strategy: this._strategy
});
var flush = deflate.flush;
// This monkey-patch is needed to make Node 0.10 produce optimal output.
// Without this it uses Z_FULL_FLUSH and effectively drops all its context
// state on every flush.
if (deflate._flushFlag !== undefined && deflate.params === undefined)
deflate.flush = function(callback) {
var ws = this._writableState;
if (ws.ended || ws.ending || ws.needDrain) {
flush.call(this, callback);
} else {
this._flushFlag = zlib.Z_SYNC_FLUSH;
this.write(Buffer.alloc(0), '', callback);
}
};
if (this._ownContextTakeover) this._deflate = deflate;
return deflate;
};
Session.prototype._close = function(codec) {
if (!codec || !codec.close) return;
try { codec.close() } catch (error) {}
};
module.exports = Session;