diff --git a/doc/ws.md b/doc/ws.md index 401688806..7740866ad 100644 --- a/doc/ws.md +++ b/doc/ws.md @@ -63,6 +63,7 @@ If `handleProtocols` is not set then the handshake is accepted regardless the va * `serverMaxWindowBits` Number: The value of windowBits. * `clientMaxWindowBits` Number: The value of max windowBits to be requested to clients. * `memLevel` Number: The value of memLevel. +* `threshold` Number: Payloads smaller than this will not be compressed. Default 1024 bytes. If a property is empty then either an offered configuration or a default value is used. diff --git a/lib/PerMessageDeflate.js b/lib/PerMessageDeflate.js index 8ae51f356..397377b91 100644 --- a/lib/PerMessageDeflate.js +++ b/lib/PerMessageDeflate.js @@ -18,6 +18,7 @@ class PerMessageDeflate { this._deflate = null; this.params = null; this._maxPayload = maxPayload || 0; + this.threshold = this._options.threshold === undefined ? 1024 : this._options.threshold; } /** diff --git a/lib/Sender.js b/lib/Sender.js index f68036a58..5e2535c4c 100644 --- a/lib/Sender.js +++ b/lib/Sender.js @@ -78,7 +78,7 @@ class Sender { */ doPing (data, options) { var mask = options && options.mask; - this.frameAndSend(0x9, data || '', true, mask); + this.frameAndSend(0x9, data ? Buffer.from(data.toString()) : null, true, mask); if (this.extensions[PerMessageDeflate.extensionName]) { this.messageHandlerCallback(); } @@ -104,7 +104,7 @@ class Sender { */ doPong (data, options) { var mask = options && options.mask; - this.frameAndSend(0xa, data || '', true, mask); + this.frameAndSend(0xa, data ? Buffer.from(data.toString()) : null, true, mask); if (this.extensions[PerMessageDeflate.extensionName]) { this.messageHandlerCallback(); } @@ -129,6 +129,17 @@ class Sender { } if (finalFragment) this.firstFragment = true; + if (data && !Buffer.isBuffer(data)) { + if ((data.buffer || data) instanceof ArrayBuffer) { + data = getBufferFromNative(data); + } else { + if (typeof data === 'number') { + data = data.toString(); + } + data = Buffer.from(data); + } + } + if (this.extensions[PerMessageDeflate.extensionName]) { this.enqueue([this.sendCompressed, [opcode, data, finalFragment, mask, compress, cb]]); } else { @@ -142,6 +153,11 @@ class Sender { * @api private */ sendCompressed (opcode, data, finalFragment, mask, compress, cb) { + if (data && data.length < this.extensions[PerMessageDeflate.extensionName].threshold) { + this.frameAndSend(opcode, data, finalFragment, mask, false, cb); + this.messageHandlerCallback(); + return; + } this.applyExtensions(data, finalFragment, this.compress, (err, data) => { if (err) { if (cb) cb(err); @@ -159,8 +175,6 @@ class Sender { * @api private */ frameAndSend (opcode, data, finalFragment, maskData, compressed, cb) { - var canModifyData = false; - if (!data) { var buff = [opcode | (finalFragment ? 0x80 : 0), 0 | (maskData ? 0x80 : 0)] .concat(maskData ? [0, 0, 0, 0] : []); @@ -168,23 +182,6 @@ class Sender { return; } - if (!Buffer.isBuffer(data)) { - if ((data.buffer || data) instanceof ArrayBuffer) { - data = getBufferFromNative(data); - } else { - canModifyData = true; - // - // If people want to send a number, this would allocate the number in - // bytes as memory size instead of storing the number as buffer value. So - // we need to transform it to string in order to prevent possible - // vulnerabilities / memory attacks. - // - if (typeof data === 'number') data = data.toString(); - - data = new Buffer(data); - } - } - var dataLength = data.length; var dataOffset = maskData ? 6 : 2; var secondByte = dataLength; @@ -197,6 +194,7 @@ class Sender { secondByte = 126; } + var canModifyData = opcode === 1 || compressed; var mergeBuffers = dataLength < 32768 || (maskData && !canModifyData); var totalLength = mergeBuffers ? dataLength + dataOffset : dataOffset; var outputBuffer = new Buffer(totalLength); @@ -276,9 +274,6 @@ class Sender { */ applyExtensions (data, fin, compress, callback) { if (compress && data) { - if ((data.buffer || data) instanceof ArrayBuffer) { - data = getBufferFromNative(data); - } this.extensions[PerMessageDeflate.extensionName].compress(data, fin, callback); } else { callback(null, data); diff --git a/test/Sender.test.js b/test/Sender.test.js index 42b23ff7d..dc82c4eb0 100644 --- a/test/Sender.test.js +++ b/test/Sender.test.js @@ -30,7 +30,7 @@ describe('Sender', function() { it('does not modify a masked text buffer', function() { var sender = new Sender({ write: function() {} }); var text = 'hi there'; - sender.frameAndSend(1, text, true, true); + sender.frameAndSend(1, Buffer.from(text), true, true); text.should.eql('hi there'); }); @@ -41,13 +41,13 @@ describe('Sender', function() { done(); } }); - sender.frameAndSend(1, 'hi', true, false, true); + sender.frameAndSend(1, Buffer.from('hi'), true, false, true); }); }); describe('#send', function() { it('compresses data if compress option is enabled', function(done) { - var perMessageDeflate = new PerMessageDeflate(); + var perMessageDeflate = new PerMessageDeflate({ threshold: 0 }); perMessageDeflate.accept([{}]); var sender = new Sender({ @@ -61,6 +61,21 @@ describe('Sender', function() { sender.send('hi', { compress: true }); }); + it('does not compress data for small payloads', function(done) { + var perMessageDeflate = new PerMessageDeflate(); + perMessageDeflate.accept([{}]); + + var sender = new Sender({ + write: function(data) { + (data[0] & 0x40).should.not.equal(0x40); + done(); + } + }, { + 'permessage-deflate': perMessageDeflate + }); + sender.send('hi', { compress: true }); + }); + it('Should be able to handle many send calls while processing without crashing on flush', function(done) { var messageCount = 0; var maxMessages = 5000; diff --git a/test/WebSocket.test.js b/test/WebSocket.test.js index 6049b4dda..2c72f2eaa 100644 --- a/test/WebSocket.test.js +++ b/test/WebSocket.test.js @@ -2226,7 +2226,7 @@ describe('WebSocket', function() { describe('#terminate', function() { it('will raise error callback, if any, if called during send data', function(done) { var wss = new WebSocketServer({port: ++port, perMessageDeflate: true}, function() { - var ws = new WebSocket('ws://localhost:' + port, {perMessageDeflate: true}); + var ws = new WebSocket('ws://localhost:' + port, {perMessageDeflate: { threshold: 0 }}); var errorGiven = false; ws.on('open', function() { ws.send('hi', function(error) {