Skip to content

Commit

Permalink
Merge pull request #1 from leonardoventurini/threshold
Browse files Browse the repository at this point in the history
feat: threshold
  • Loading branch information
leonardoventurini authored Dec 4, 2024
2 parents 68ceed8 + f915d6b commit 2e5ba20
Show file tree
Hide file tree
Showing 11 changed files with 288 additions and 82 deletions.
29 changes: 29 additions & 0 deletions .github/workflows/test.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
name: Test

on:
- pull_request

jobs:
test:
name: Node.js v${{ matrix.node }}
runs-on: ubuntu-latest

strategy:
fail-fast: false
matrix:
node: ['20', '22', '23']

steps:
- uses: actions/checkout@v4

- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: ${{ matrix.node }}
cache: 'npm'

- name: Install dependencies
run: npm ci

- name: Run tests
run: npm test
41 changes: 0 additions & 41 deletions .github/workflows/test.yml

This file was deleted.

1 change: 0 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
@@ -1,2 +1 @@
node_modules
package-lock.json
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
### 0.1.8 / 2024-11-09

- Add `threshold` option to control when compression is applied

### 0.1.7 / 2019-06-10

- Use the `Buffer.alloc()` and `Buffer.from()` functions instead of the unsafe
Expand Down
6 changes: 4 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ WebSocket protocol extension as a plugin for
## Installation

```
$ npm install permessage-deflate
$ npm install permessage-deflate2
```

## Usage
Expand All @@ -32,7 +32,8 @@ var Extensions = require('websocket-extensions'),

deflate = deflate.configure({
level: zlib.Z_BEST_COMPRESSION,
maxWindowBits: 13
maxWindowBits: 13,
threshold: 100 // optional, defaults to 0 (always compress)
});

var exts = new Extensions();
Expand All @@ -45,6 +46,7 @@ the peer, and those that are negotiated as part of the protocol. The settings
only affecting the compressor are described fully in the [zlib
documentation](http://nodejs.org/api/zlib.html#zlib_options):

- `threshold`: sets the minimum size of messages to be compressed, defaults to 0
- `level`: sets the compression level, can be an integer from `0` to `9`, or one
of the constants `zlib.Z_NO_COMPRESSION`, `zlib.Z_BEST_SPEED`,
`zlib.Z_BEST_COMPRESSION`, or `zlib.Z_DEFAULT_COMPRESSION`
Expand Down
1 change: 1 addition & 0 deletions lib/permessage_deflate.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ var VALID_OPTIONS = [
'maxWindowBits',
'requestNoContextTakeover',
'requestMaxWindowBits',
'threshold',
'zlib'
];

Expand Down
38 changes: 32 additions & 6 deletions lib/session.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ 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._threshold = common.fetch(options, 'threshold', 0);

this._acceptNoContextTakeover = common.fetch(options, 'noContextTakeover', false);
this._acceptMaxWindowBits = common.fetch(options, 'maxWindowBits', undefined);
Expand Down Expand Up @@ -67,12 +68,18 @@ Session.prototype.processIncomingMessage = function(message, callback) {
};

Session.prototype.processOutgoingMessage = function(message, callback) {
// Skip compression for small messages
if (message.data.length < this._threshold) {
message.rsv1 = false;
return callback(null, message);
}

if (this._lockOut) return this._queueOut.push([message, callback]);

var deflate = this._getDeflate(),
chunks = [],
length = 0,
self = this;
totalLength = 0,
chunks = [],
self = this;

if (this._deflate) this._lockOut = true;

Expand All @@ -92,10 +99,11 @@ Session.prototype.processOutgoingMessage = function(message, callback) {

var onData = function(data) {
chunks.push(data);
length += data.length;
totalLength += data.length;
};

var onError = function(error) {
chunks = null; // Help GC
return_(error, null);
};

Expand All @@ -104,8 +112,26 @@ Session.prototype.processOutgoingMessage = function(message, callback) {
deflate.write(message.data);

var onFlush = function() {
var data = Buffer.concat(chunks, length);
message.data = data.slice(0, data.length - 4);
if (totalLength === 0) {
chunks = null;
message.data = Buffer.alloc(0);
} else {
// allocUnsafe is safe here because:
// 1. We know the exact size needed (totalLength)
// 2. We immediately fill the entire buffer with our compressed data
// 3. No uninitialized memory is exposed to users
var finalBuffer = Buffer.allocUnsafe(totalLength);
var offset = 0;

for (var i = 0; i < chunks.length; i++) {
chunks[i].copy(finalBuffer, offset);
offset += chunks[i].length;
}

chunks = null; // Help GC
message.data = finalBuffer.slice(0, finalBuffer.length - 4);
}

message.rsv1 = true;
return_(null, message);
};
Expand Down
80 changes: 80 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

64 changes: 32 additions & 32 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,34 +1,34 @@
{
"name": "permessage-deflate",
"description": "Per-message DEFLATE compression extension for WebSocket connections",
"homepage": "https://github.com/faye/permessage-deflate-node",
"author": "James Coglan <[email protected]> (http://jcoglan.com/)",
"keywords": [
"websocket",
"compression",
"deflate"
],
"license": "Apache-2.0",
"version": "0.1.7",
"engines": {
"node": ">=0.8.0"
},
"files": [
"lib"
],
"main": "./lib/permessage_deflate",
"dependencies": {
"safe-buffer": "*"
},
"devDependencies": {
"jstest": "*"
},
"scripts": {
"test": "jstest spec/runner.js"
},
"repository": {
"type": "git",
"url": "git://github.com/faye/permessage-deflate-node.git"
},
"bugs": "https://github.com/faye/permessage-deflate-node/issues"
"name": "permessage-deflate2",
"description": "Per-message DEFLATE compression extension for WebSocket connections, with threshold!",
"homepage": "https://github.com/leonardoventurini/permessage-deflate2",
"author": "James Coglan <[email protected]> (http://jcoglan.com/), Leonardo Venturini <[email protected]>",
"keywords": [
"websocket",
"compression",
"deflate"
],
"license": "Apache-2.0",
"version": "0.1.8-alpha.1",
"engines": {
"node": ">=20.0.0"
},
"files": [
"lib"
],
"main": "./lib/permessage_deflate",
"dependencies": {
"safe-buffer": "*"
},
"devDependencies": {
"jstest": "*"
},
"scripts": {
"test": "jstest spec/runner.js"
},
"repository": {
"type": "git",
"url": "git://github.com/leonardoventurini/permessage-deflate2.git"
},
"bugs": "https://github.com/leonardoventurini/permessage-deflate2/issues"
}
53 changes: 53 additions & 0 deletions spec/client_session_spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -346,4 +346,57 @@ test.describe("ClientSession", function() { with(this) {
processOutgoingMessage()
}})
}})

describe("with threshold", function() { with(this) {
define("options", { threshold: 100 })

it("does not compress messages smaller than threshold", function() { with(this) {
this.message = { data: Buffer.alloc(50).fill("x"), rsv1: true }
activate()
expect(zlib, "createDeflateRaw").exactly(0)
processOutgoingMessage()
}})

it("compresses messages larger than threshold", function() { with(this) {
this.message = { data: Buffer.alloc(150).fill("x"), rsv1: true }
activate()
expect(zlib, "createDeflateRaw").given({ windowBits: 15, level: level, memLevel: memLevel, strategy: strategy }).returns(deflate)
processOutgoingMessage()
}})

it("sets rsv1 to false when skipping compression", function() { with(this) {
this.message = { data: Buffer.alloc(50).fill("x"), rsv1: true }
activate()
processOutgoingMessage(function(error, message) {
assertEqual(false, message.rsv1)
})
}})

it("maintains rsv1 when compressing", function() { with(this) {
this.message = { data: Buffer.alloc(150).fill("x"), rsv1: true }
activate()
stub(zlib, "createDeflateRaw").returns(deflate)
processOutgoingMessage(function(error, message) {
assertEqual(true, message.rsv1)
})
}})

it("handles threshold of 0 (always compress)", function() { with(this) {
this.ext = PermessageDeflate.configure({ threshold: 0 })
this.session = ext.configure({ zlib: zlib }).createClientSession()
this.message = { data: Buffer.alloc(1).fill("x"), rsv1: true }
activate()
expect(zlib, "createDeflateRaw").given({ windowBits: 15, level: level, memLevel: memLevel, strategy: strategy }).returns(deflate)
processOutgoingMessage()
}})

it("handles undefined threshold (always compress)", function() { with(this) {
this.ext = PermessageDeflate.configure({})
this.session = ext.configure({ zlib: zlib }).createClientSession()
this.message = { data: Buffer.alloc(1).fill("x"), rsv1: true }
activate()
expect(zlib, "createDeflateRaw").given({ windowBits: 15, level: level, memLevel: memLevel, strategy: strategy }).returns(deflate)
processOutgoingMessage()
}})
}})
}})
Loading

0 comments on commit 2e5ba20

Please sign in to comment.