From 60b83b706d788d02eb03225e8ff143904266c088 Mon Sep 17 00:00:00 2001 From: Sebastian Beltran Date: Mon, 7 Apr 2025 13:46:18 -0500 Subject: [PATCH 1/9] refactor: enhance encoding options handling in compression function --- index.js | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/index.js b/index.js index 5dd09c7b..6950b350 100644 --- a/index.js +++ b/index.js @@ -51,11 +51,12 @@ var encodingSupported = ['gzip', 'deflate', 'identity', 'br'] function compression (options) { var opts = options || {} + const encodingOpts = opts?.encodings var optsBrotli = { - ...opts.brotli, + ...encodingOpts?.brotli, params: { [zlib.constants.BROTLI_PARAM_QUALITY]: 4, // set the default level to a reasonable value with balanced speed/ratio - ...opts.brotli?.params + ...encodingOpts?.brotli?.params } } @@ -202,11 +203,13 @@ function compression (options) { // compression stream debug('%s compression', method) - stream = method === 'gzip' - ? zlib.createGzip(opts) - : method === 'br' - ? zlib.createBrotliCompress(optsBrotli) - : zlib.createDeflate(opts) + if (method === 'gzip') { + stream = zlib.createGzip(encodingOpts?.gzip) + } else if (method === 'br') { + stream = zlib.createBrotliCompress(optsBrotli) + } else { + stream = zlib.createDeflate(encodingOpts?.deflate) + } // add buffered listeners to stream addListeners(stream, stream.on, listeners) From e87943b75f10d1333b65d103ae5f87df45251a5e Mon Sep 17 00:00:00 2001 From: Sebastian Beltran Date: Wed, 16 Apr 2025 17:36:51 -0500 Subject: [PATCH 2/9] feat: ignore encondig when is false Signed-off-by: Sebastian Beltran --- index.js | 15 +- test/compression.js | 457 +++++++++++++++++++++++++++----------------- 2 files changed, 287 insertions(+), 185 deletions(-) diff --git a/index.js b/index.js index 6950b350..fa5fd8f5 100644 --- a/index.js +++ b/index.js @@ -36,11 +36,9 @@ module.exports.filter = shouldCompress * @private */ var cacheControlNoTransformRegExp = /(?:^|,)\s*?no-transform\s*?(?:,|$)/ -var SUPPORTED_ENCODING = ['br', 'gzip', 'deflate', 'identity'] +var ENCONDING_OPTIONS = ['br', 'gzip', 'deflate'] var PREFERRED_ENCODING = ['br', 'gzip'] -var encodingSupported = ['gzip', 'deflate', 'identity', 'br'] - /** * Compress response data with gzip / deflate. * @@ -50,13 +48,14 @@ var encodingSupported = ['gzip', 'deflate', 'identity', 'br'] */ function compression (options) { - var opts = options || {} + const opts = options || {} const encodingOpts = opts?.encodings - var optsBrotli = { - ...encodingOpts?.brotli, + const encodingSupported = ENCONDING_OPTIONS.filter(enc => encodingOpts?.[enc] !== false) + const optsBrotli = { + ...encodingOpts?.br, params: { [zlib.constants.BROTLI_PARAM_QUALITY]: 4, // set the default level to a reasonable value with balanced speed/ratio - ...encodingOpts?.brotli?.params + ...encodingOpts?.br?.params } } @@ -188,7 +187,7 @@ function compression (options) { // compression method var negotiator = new Negotiator(req) - var method = negotiator.encoding(SUPPORTED_ENCODING, PREFERRED_ENCODING) + var method = negotiator.encoding(encodingSupported, PREFERRED_ENCODING) // if no method is found, use the default encoding if (!req.headers['accept-encoding'] && encodingSupported.indexOf(enforceEncoding) !== -1) { diff --git a/test/compression.js b/test/compression.js index 78173c33..252934f6 100644 --- a/test/compression.js +++ b/test/compression.js @@ -51,6 +51,20 @@ describe('compression()', function () { .expect(200, 'hello, world', done) }) + it('should skip if content-encondig is identity', function (done) { + var server = createServer({ threshold: 0 }, function (req, res) { + res.setHeader('Content-Type', 'text/plain') + res.end('hello, world') + }) + + request(server) + .get('/') + .set('Accept-Encoding', 'identity') + .expect(shouldNotHaveHeader('Content-Encoding')) + .expect(200, done) + }) + + it('should set Vary', function (done) { var server = createServer({ threshold: 0 }, function (req, res) { res.setHeader('Content-Type', 'text/plain') @@ -459,218 +473,307 @@ describe('compression()', function () { }) }) - describe('when "Accept-Encoding: gzip"', function () { - it('should respond with gzip', function (done) { - var server = createServer({ threshold: 0 }, function (req, res) { - res.setHeader('Content-Type', 'text/plain') - res.end('hello, world') + describe('encondigs', function () { + describe('when "Accept-Encoding: gzip"', function () { + it('when gzip is disable', function (done) { + var server = createServer({ threshold: 0, encodings: { gzip: false } }, function (req, res) { + res.setHeader('Content-Type', 'text/plain') + res.end('hello, world') + }) + + request(server) + .get('/') + .set('Accept-Encoding', 'gzip') + .expect(shouldNotHaveHeader('Content-Encoding')) + .expect(200, 'hello, world', done) }) - request(server) - .get('/') - .set('Accept-Encoding', 'gzip') - .expect('Content-Encoding', 'gzip', done) - }) - - it('should return false writing after end', function (done) { - var server = createServer({ threshold: 0 }, function (req, res) { - res.setHeader('Content-Type', 'text/plain') - res.end('hello, world') - assert.ok(res.write() === false) - assert.ok(res.end() === false) + it('should respond with gzip', function (done) { + var server = createServer({ threshold: 0 }, function (req, res) { + res.setHeader('Content-Type', 'text/plain') + res.end('hello, world') + }) + + request(server) + .get('/') + .set('Accept-Encoding', 'gzip') + .expect('Content-Encoding', 'gzip', done) }) - - request(server) - .get('/') - .set('Accept-Encoding', 'gzip') - .expect('Content-Encoding', 'gzip', done) - }) - }) - - describe('when "Accept-Encoding: deflate"', function () { - it('should respond with deflate', function (done) { - var server = createServer({ threshold: 0 }, function (req, res) { - res.setHeader('Content-Type', 'text/plain') - res.end('hello, world') + + it('should return false writing after end', function (done) { + var server = createServer({ threshold: 0 }, function (req, res) { + res.setHeader('Content-Type', 'text/plain') + res.end('hello, world') + assert.ok(res.write() === false) + assert.ok(res.end() === false) + }) + + request(server) + .get('/') + .set('Accept-Encoding', 'gzip') + .expect('Content-Encoding', 'gzip', done) }) - - request(server) - .get('/') - .set('Accept-Encoding', 'deflate') - .expect('Content-Encoding', 'deflate', done) }) - }) - - describe('when "Accept-Encoding: br"', function () { - it('should respond with br', function (done) { - var server = createServer({ threshold: 0 }, function (req, res) { - res.setHeader('Content-Type', 'text/plain') - res.end('hello, world') + + describe('when "Accept-Encoding: deflate"', function () { + it('should respond with deflate', function (done) { + var server = createServer({ threshold: 0 }, function (req, res) { + res.setHeader('Content-Type', 'text/plain') + res.end('hello, world') + }) + + request(server) + .get('/') + .set('Accept-Encoding', 'deflate') + .expect('Content-Encoding', 'deflate', done) }) - request(server) - .get('/') - .set('Accept-Encoding', 'br') - .expect('Content-Encoding', 'br', done) - }) - }) - - describe('when "Accept-Encoding: br" and passing compression level', function () { - it('should respond with br', function (done) { - var params = {} - params[zlib.constants.BROTLI_PARAM_QUALITY] = 11 - - var server = createServer({ threshold: 0, brotli: { params: params } }, function (req, res) { - res.setHeader('Content-Type', 'text/plain') - res.end('hello, world') + it('when deflate is disable', function (done) { + var server = createServer({ threshold: 0, encodings: { deflate: false } }, function (req, res) { + res.setHeader('Content-Type', 'text/plain') + res.end('hello, world') + }) + + request(server) + .get('/') + .set('Accept-Encoding', 'deflate') + .expect(shouldNotHaveHeader('Content-Encoding')) + .expect(200, 'hello, world', done) }) - - request(server) - .get('/') - .set('Accept-Encoding', 'br') - .expect('Content-Encoding', 'br', done) }) - - it('shouldn\'t break compression when gzip is requested', function (done) { - var params = {} - params[zlib.constants.BROTLI_PARAM_QUALITY] = 8 - - var server = createServer({ threshold: 0, brotli: { params: params } }, function (req, res) { - res.setHeader('Content-Type', 'text/plain') - res.end('hello, world') + + describe('when "Accept-Encoding: br"', function () { + it('when brotli is disable should not compress', function (done) { + var server = createServer({ threshold: 0, encodings: { br: false }}, function (req, res) { + res.setHeader('Content-Type', 'text/plain') + res.end('hello, world') + }) + + request(server) + .get('/') + .set('Accept-Encoding', 'br') + .expect(shouldNotHaveHeader('Content-Encoding')) + .expect(200, 'hello, world', done) }) - request(server) - .get('/') - .set('Accept-Encoding', 'gzip') - .expect('Content-Encoding', 'gzip', done) + it('should respond with br', function (done) { + var server = createServer({ threshold: 0 }, function (req, res) { + res.setHeader('Content-Type', 'text/plain') + res.end('hello, world') + }) + + request(server) + .get('/') + .set('Accept-Encoding', 'br') + .expect('Content-Encoding', 'br', done) + }) }) - }) - - describe('when "Accept-Encoding: gzip, deflate"', function () { - it('should respond with gzip', function (done) { - var server = createServer({ threshold: 0 }, function (req, res) { - res.setHeader('Content-Type', 'text/plain') - res.end('hello, world') + + describe('when "Accept-Encoding: br" and passing compression level', function () { + it('should respond with br', function (done) { + var params = {} + params[zlib.constants.BROTLI_PARAM_QUALITY] = 11 + + var server = createServer({ threshold: 0, encodings: { br: { params: params } }}, function (req, res) { + res.setHeader('Content-Type', 'text/plain') + res.end('hello, world') + }) + + request(server) + .get('/') + .set('Accept-Encoding', 'br') + .expect('Content-Encoding', 'br', done) + }) + + it('shouldn\'t break compression when gzip is requested', function (done) { + var params = {} + params[zlib.constants.BROTLI_PARAM_QUALITY] = 8 + + var server = createServer({ threshold: 0, encodings: { br: { params: params } }}, function (req, res) { + res.setHeader('Content-Type', 'text/plain') + res.end('hello, world') + }) + + request(server) + .get('/') + .set('Accept-Encoding', 'gzip') + .expect('Content-Encoding', 'gzip', done) }) - - request(server) - .get('/') - .set('Accept-Encoding', 'gzip, deflate') - .expect('Content-Encoding', 'gzip', done) }) - }) - - describe('when "Accept-Encoding: deflate, gzip"', function () { - it('should respond with gzip', function (done) { - var server = createServer({ threshold: 0 }, function (req, res) { - res.setHeader('Content-Type', 'text/plain') - res.end('hello, world') + + describe('when "Accept-Encoding: gzip, deflate"', function () { + it('should respond with deflate when gzip is disable', function (done) { + var server = createServer({ threshold: 0, encodings: { gzip: false } }, function (req, res) { + res.setHeader('Content-Type', 'text/plain') + res.end('hello, world') + }) + + request(server) + .get('/') + .set('Accept-Encoding', 'gzip, deflate') + .expect('Content-Encoding', 'deflate', done) }) - request(server) - .get('/') - .set('Accept-Encoding', 'deflate, gzip') - .expect('Content-Encoding', 'gzip', done) + it('should respond with gzip', function (done) { + var server = createServer({ threshold: 0 }, function (req, res) { + res.setHeader('Content-Type', 'text/plain') + res.end('hello, world') + }) + + request(server) + .get('/') + .set('Accept-Encoding', 'gzip, deflate') + .expect('Content-Encoding', 'gzip', done) + }) }) - }) - - describe('when "Accept-Encoding: gzip, br"', function () { - it('should respond with br', function (done) { - var server = createServer({ threshold: 0 }, function (req, res) { - res.setHeader('Content-Type', 'text/plain') - res.end('hello, world') + + describe('when "Accept-Encoding: deflate, gzip"', function () { + it('should respond with gzip', function (done) { + var server = createServer({ threshold: 0 }, function (req, res) { + res.setHeader('Content-Type', 'text/plain') + res.end('hello, world') + }) + + request(server) + .get('/') + .set('Accept-Encoding', 'deflate, gzip') + .expect('Content-Encoding', 'gzip', done) }) - - request(server) - .get('/') - .set('Accept-Encoding', 'gzip, br') - .expect('Content-Encoding', 'br', done) }) - - it.skip('should respond with gzip', function (done) { - var server = createServer({ threshold: 0 }, function (req, res) { - res.setHeader('Content-Type', 'text/plain') - res.end('hello, world') + + describe('when "Accept-Encoding: gzip, br"', function () { + it('should respond with gzip when brotli is disable', function (done) { + var server = createServer({ threshold: 0, encodings: { br: false } }, function (req, res) { + res.setHeader('Content-Type', 'text/plain') + res.end('hello, world') + }) + + request(server) + .get('/') + .set('Accept-Encoding', 'gzip, br') + .expect('Content-Encoding', 'gzip', done) }) - request(server) - .get('/') - .set('Accept-Encoding', 'br, gzip') - .expect('Content-Encoding', 'gzip', done) - }) - }) + it('should respond with br', function (done) { + var server = createServer({ threshold: 0 }, function (req, res) { + res.setHeader('Content-Type', 'text/plain') + res.end('hello, world') + }) + + request(server) + .get('/') + .set('Accept-Encoding', 'gzip, br') + .expect('Content-Encoding', 'br', done) + }) + }) + + describe('when "Accept-Encoding: deflate, gzip, br"', function () { + it('should respond with gzip when brotli is disable', function (done) { + var server = createServer({ threshold: 0, encodings: { br: false } }, function (req, res) { + res.setHeader('Content-Type', 'text/plain') + res.end('hello, world') + }) + + request(server) + .get('/') + .set('Accept-Encoding', 'deflate, gzip, br') + .expect('Content-Encoding', 'gzip', done) + }) - describe('when "Accept-Encoding: deflate, gzip, br"', function () { - it('should respond with br', function (done) { - var server = createServer({ threshold: 0 }, function (req, res) { - res.setHeader('Content-Type', 'text/plain') - res.end('hello, world') + it('should respond with deflate when brotli and gzip is disable', function (done) { + var server = createServer({ threshold: 0, encodings: { br: false, gzip: false } }, function (req, res) { + res.setHeader('Content-Type', 'text/plain') + res.end('hello, world') + }) + + request(server) + .get('/') + .set('Accept-Encoding', 'deflate, gzip, br') + .expect('Content-Encoding', 'deflate', done) }) - request(server) - .get('/') - .set('Accept-Encoding', 'deflate, gzip, br') - .expect('Content-Encoding', 'br', done) + it('should respond with br', function (done) { + var server = createServer({ threshold: 0 }, function (req, res) { + res.setHeader('Content-Type', 'text/plain') + res.end('hello, world') + }) + + request(server) + .get('/') + .set('Accept-Encoding', 'deflate, gzip, br') + .expect('Content-Encoding', 'br', done) + }) }) - }) - - describe('when "Accept-Encoding: gzip;q=1, br;q=0.3"', function () { - it('should respond with gzip', function (done) { - var server = createServer({ threshold: 0 }, function (req, res) { - res.setHeader('Content-Type', 'text/plain') - res.end('hello, world') + + describe('when "Accept-Encoding: gzip;q=1, br;q=0.3"', function () { + it('should respond with gzip', function (done) { + var server = createServer({ threshold: 0 }, function (req, res) { + res.setHeader('Content-Type', 'text/plain') + res.end('hello, world') + }) + + request(server) + .get('/') + .set('Accept-Encoding', 'gzip;q=1, br;q=0.3') + .expect('Content-Encoding', 'gzip', done) }) - - request(server) - .get('/') - .set('Accept-Encoding', 'gzip;q=1, br;q=0.3') - .expect('Content-Encoding', 'gzip', done) }) - }) - - describe('when "Accept-Encoding: gzip, br;q=0.8"', function () { - it('should respond with gzip', function (done) { - var server = createServer({ threshold: 0 }, function (req, res) { - res.setHeader('Content-Type', 'text/plain') - res.end('hello, world') + + describe('when "Accept-Encoding: gzip, br;q=0.8"', function () { + it('should respond with gzip', function (done) { + var server = createServer({ threshold: 0 }, function (req, res) { + res.setHeader('Content-Type', 'text/plain') + res.end('hello, world') + }) + + request(server) + .get('/') + .set('Accept-Encoding', 'gzip, br;q=0.8') + .expect('Content-Encoding', 'gzip', done) }) - - request(server) - .get('/') - .set('Accept-Encoding', 'gzip, br;q=0.8') - .expect('Content-Encoding', 'gzip', done) }) - }) - - describe('when "Accept-Encoding: gzip;q=0.001"', function () { - it('should respond with gzip', function (done) { - var server = createServer({ threshold: 0 }, function (req, res) { - res.setHeader('Content-Type', 'text/plain') - res.end('hello, world') + + describe('when "Accept-Encoding: gzip;q=0.001"', function () { + it('should respond with gzip', function (done) { + var server = createServer({ threshold: 0 }, function (req, res) { + res.setHeader('Content-Type', 'text/plain') + res.end('hello, world') + }) + + request(server) + .get('/') + .set('Accept-Encoding', 'gzip;q=0.001') + .expect('Content-Encoding', 'gzip', done) }) - - request(server) - .get('/') - .set('Accept-Encoding', 'gzip;q=0.001') - .expect('Content-Encoding', 'gzip', done) }) - }) - - describe('when "Accept-Encoding: deflate, br"', function () { - it('should respond with br', function (done) { - var server = createServer({ threshold: 0 }, function (req, res) { - res.setHeader('Content-Type', 'text/plain') - res.end('hello, world') + + describe('when "Accept-Encoding: deflate, br"', function () { + it('should respond with deflate when brotli is disable', function (done) { + var server = createServer({ threshold: 0, encodings: { br: false } }, function (req, res) { + res.setHeader('Content-Type', 'text/plain') + res.end('hello, world') + }) + + request(server) + .get('/') + .set('Accept-Encoding', 'deflate, br') + .expect('Content-Encoding', 'deflate', done) }) - request(server) - .get('/') - .set('Accept-Encoding', 'deflate, br') - .expect('Content-Encoding', 'br', done) + it('should respond with br', function (done) { + var server = createServer({ threshold: 0 }, function (req, res) { + res.setHeader('Content-Type', 'text/plain') + res.end('hello, world') + }) + + request(server) + .get('/') + .set('Accept-Encoding', 'deflate, br') + .expect('Content-Encoding', 'br', done) + }) }) }) - + describe('when "Cache-Control: no-transform" response header', function () { it('should not compress response', function (done) { var server = createServer({ threshold: 0 }, function (req, res) { From e1c09a5698ec12e5e6d7af96779c9f77b048e7ed Mon Sep 17 00:00:00 2001 From: Sebastian Beltran Date: Wed, 16 Apr 2025 18:00:09 -0500 Subject: [PATCH 3/9] improve logic Signed-off-by: Sebastian Beltran --- index.js | 2 +- test/compression.js | 87 ++++++++++++++++++++++----------------------- 2 files changed, 44 insertions(+), 45 deletions(-) diff --git a/index.js b/index.js index fa5fd8f5..9dc38115 100644 --- a/index.js +++ b/index.js @@ -206,7 +206,7 @@ function compression (options) { stream = zlib.createGzip(encodingOpts?.gzip) } else if (method === 'br') { stream = zlib.createBrotliCompress(optsBrotli) - } else { + } else if (method === 'deflate') { stream = zlib.createDeflate(encodingOpts?.deflate) } diff --git a/test/compression.js b/test/compression.js index 252934f6..17d7d92b 100644 --- a/test/compression.js +++ b/test/compression.js @@ -64,7 +64,6 @@ describe('compression()', function () { .expect(200, done) }) - it('should set Vary', function (done) { var server = createServer({ threshold: 0 }, function (req, res) { res.setHeader('Content-Type', 'text/plain') @@ -480,7 +479,7 @@ describe('compression()', function () { res.setHeader('Content-Type', 'text/plain') res.end('hello, world') }) - + request(server) .get('/') .set('Accept-Encoding', 'gzip') @@ -493,13 +492,13 @@ describe('compression()', function () { res.setHeader('Content-Type', 'text/plain') res.end('hello, world') }) - + request(server) .get('/') .set('Accept-Encoding', 'gzip') .expect('Content-Encoding', 'gzip', done) }) - + it('should return false writing after end', function (done) { var server = createServer({ threshold: 0 }, function (req, res) { res.setHeader('Content-Type', 'text/plain') @@ -507,21 +506,21 @@ describe('compression()', function () { assert.ok(res.write() === false) assert.ok(res.end() === false) }) - + request(server) .get('/') .set('Accept-Encoding', 'gzip') .expect('Content-Encoding', 'gzip', done) }) }) - + describe('when "Accept-Encoding: deflate"', function () { it('should respond with deflate', function (done) { var server = createServer({ threshold: 0 }, function (req, res) { res.setHeader('Content-Type', 'text/plain') res.end('hello, world') }) - + request(server) .get('/') .set('Accept-Encoding', 'deflate') @@ -533,7 +532,7 @@ describe('compression()', function () { res.setHeader('Content-Type', 'text/plain') res.end('hello, world') }) - + request(server) .get('/') .set('Accept-Encoding', 'deflate') @@ -541,14 +540,14 @@ describe('compression()', function () { .expect(200, 'hello, world', done) }) }) - + describe('when "Accept-Encoding: br"', function () { - it('when brotli is disable should not compress', function (done) { - var server = createServer({ threshold: 0, encodings: { br: false }}, function (req, res) { + it('when brotli is disable should not compress', function (done) { + var server = createServer({ threshold: 0, encodings: { br: false } }, function (req, res) { res.setHeader('Content-Type', 'text/plain') res.end('hello, world') }) - + request(server) .get('/') .set('Accept-Encoding', 'br') @@ -561,53 +560,53 @@ describe('compression()', function () { res.setHeader('Content-Type', 'text/plain') res.end('hello, world') }) - + request(server) .get('/') .set('Accept-Encoding', 'br') .expect('Content-Encoding', 'br', done) }) }) - + describe('when "Accept-Encoding: br" and passing compression level', function () { it('should respond with br', function (done) { var params = {} params[zlib.constants.BROTLI_PARAM_QUALITY] = 11 - - var server = createServer({ threshold: 0, encodings: { br: { params: params } }}, function (req, res) { + + var server = createServer({ threshold: 0, encodings: { br: { params: params } } }, function (req, res) { res.setHeader('Content-Type', 'text/plain') res.end('hello, world') }) - + request(server) .get('/') .set('Accept-Encoding', 'br') .expect('Content-Encoding', 'br', done) }) - + it('shouldn\'t break compression when gzip is requested', function (done) { var params = {} params[zlib.constants.BROTLI_PARAM_QUALITY] = 8 - - var server = createServer({ threshold: 0, encodings: { br: { params: params } }}, function (req, res) { + + var server = createServer({ threshold: 0, encodings: { br: { params: params } } }, function (req, res) { res.setHeader('Content-Type', 'text/plain') res.end('hello, world') }) - + request(server) .get('/') .set('Accept-Encoding', 'gzip') .expect('Content-Encoding', 'gzip', done) }) }) - + describe('when "Accept-Encoding: gzip, deflate"', function () { it('should respond with deflate when gzip is disable', function (done) { var server = createServer({ threshold: 0, encodings: { gzip: false } }, function (req, res) { res.setHeader('Content-Type', 'text/plain') res.end('hello, world') }) - + request(server) .get('/') .set('Accept-Encoding', 'gzip, deflate') @@ -619,35 +618,35 @@ describe('compression()', function () { res.setHeader('Content-Type', 'text/plain') res.end('hello, world') }) - + request(server) .get('/') .set('Accept-Encoding', 'gzip, deflate') .expect('Content-Encoding', 'gzip', done) }) }) - + describe('when "Accept-Encoding: deflate, gzip"', function () { it('should respond with gzip', function (done) { var server = createServer({ threshold: 0 }, function (req, res) { res.setHeader('Content-Type', 'text/plain') res.end('hello, world') }) - + request(server) .get('/') .set('Accept-Encoding', 'deflate, gzip') .expect('Content-Encoding', 'gzip', done) }) }) - + describe('when "Accept-Encoding: gzip, br"', function () { it('should respond with gzip when brotli is disable', function (done) { var server = createServer({ threshold: 0, encodings: { br: false } }, function (req, res) { res.setHeader('Content-Type', 'text/plain') res.end('hello, world') }) - + request(server) .get('/') .set('Accept-Encoding', 'gzip, br') @@ -659,21 +658,21 @@ describe('compression()', function () { res.setHeader('Content-Type', 'text/plain') res.end('hello, world') }) - + request(server) .get('/') .set('Accept-Encoding', 'gzip, br') .expect('Content-Encoding', 'br', done) - }) + }) }) - + describe('when "Accept-Encoding: deflate, gzip, br"', function () { it('should respond with gzip when brotli is disable', function (done) { var server = createServer({ threshold: 0, encodings: { br: false } }, function (req, res) { res.setHeader('Content-Type', 'text/plain') res.end('hello, world') }) - + request(server) .get('/') .set('Accept-Encoding', 'deflate, gzip, br') @@ -685,7 +684,7 @@ describe('compression()', function () { res.setHeader('Content-Type', 'text/plain') res.end('hello, world') }) - + request(server) .get('/') .set('Accept-Encoding', 'deflate, gzip, br') @@ -697,63 +696,63 @@ describe('compression()', function () { res.setHeader('Content-Type', 'text/plain') res.end('hello, world') }) - + request(server) .get('/') .set('Accept-Encoding', 'deflate, gzip, br') .expect('Content-Encoding', 'br', done) }) }) - + describe('when "Accept-Encoding: gzip;q=1, br;q=0.3"', function () { it('should respond with gzip', function (done) { var server = createServer({ threshold: 0 }, function (req, res) { res.setHeader('Content-Type', 'text/plain') res.end('hello, world') }) - + request(server) .get('/') .set('Accept-Encoding', 'gzip;q=1, br;q=0.3') .expect('Content-Encoding', 'gzip', done) }) }) - + describe('when "Accept-Encoding: gzip, br;q=0.8"', function () { it('should respond with gzip', function (done) { var server = createServer({ threshold: 0 }, function (req, res) { res.setHeader('Content-Type', 'text/plain') res.end('hello, world') }) - + request(server) .get('/') .set('Accept-Encoding', 'gzip, br;q=0.8') .expect('Content-Encoding', 'gzip', done) }) }) - + describe('when "Accept-Encoding: gzip;q=0.001"', function () { it('should respond with gzip', function (done) { var server = createServer({ threshold: 0 }, function (req, res) { res.setHeader('Content-Type', 'text/plain') res.end('hello, world') }) - + request(server) .get('/') .set('Accept-Encoding', 'gzip;q=0.001') .expect('Content-Encoding', 'gzip', done) }) }) - + describe('when "Accept-Encoding: deflate, br"', function () { it('should respond with deflate when brotli is disable', function (done) { var server = createServer({ threshold: 0, encodings: { br: false } }, function (req, res) { res.setHeader('Content-Type', 'text/plain') res.end('hello, world') }) - + request(server) .get('/') .set('Accept-Encoding', 'deflate, br') @@ -765,7 +764,7 @@ describe('compression()', function () { res.setHeader('Content-Type', 'text/plain') res.end('hello, world') }) - + request(server) .get('/') .set('Accept-Encoding', 'deflate, br') @@ -773,7 +772,7 @@ describe('compression()', function () { }) }) }) - + describe('when "Cache-Control: no-transform" response header', function () { it('should not compress response', function (done) { var server = createServer({ threshold: 0 }, function (req, res) { From d059d123520237ddd6d9988ee25ab3765f96576b Mon Sep 17 00:00:00 2001 From: Sebastian Beltran Date: Sat, 3 May 2025 17:41:40 -0500 Subject: [PATCH 4/9] test: add test for enforceEnconding Signed-off-by: Sebastian Beltran --- index.js | 2 +- test/compression.js | 99 ++++++++++++++++++++++++++++++++------------- 2 files changed, 73 insertions(+), 28 deletions(-) diff --git a/index.js b/index.js index 9dc38115..1da8659f 100644 --- a/index.js +++ b/index.js @@ -190,7 +190,7 @@ function compression (options) { var method = negotiator.encoding(encodingSupported, PREFERRED_ENCODING) // if no method is found, use the default encoding - if (!req.headers['accept-encoding'] && encodingSupported.indexOf(enforceEncoding) !== -1) { + if (!req.headers['accept-encoding'] && encodingSupported.includes(enforceEncoding)) { method = enforceEncoding } diff --git a/test/compression.js b/test/compression.js index 17d7d92b..b21c9e98 100644 --- a/test/compression.js +++ b/test/compression.js @@ -1102,43 +1102,88 @@ describe('compression()', function () { .expect(200, 'hello, world', done) }) - it('should compress when enforceEncoding is gzip', function (done) { - var server = createServer({ threshold: 0, enforceEncoding: 'gzip' }, function (req, res) { - res.setHeader('Content-Type', 'text/plain') - res.end('hello, world') + describe('gzip', function () { + it('should compress when enforceEncoding is gzip', function (done) { + var server = createServer({ threshold: 0, enforceEncoding: 'gzip' }, function (req, res) { + res.setHeader('Content-Type', 'text/plain') + res.end('hello, world') + }) + + request(server) + .get('/') + .set('Accept-Encoding', '') + .expect('Content-Encoding', 'gzip') + .expect(200, 'hello, world', done) }) - request(server) - .get('/') - .set('Accept-Encoding', '') - .expect('Content-Encoding', 'gzip') - .expect(200, 'hello, world', done) + it('should skip when gzip is disabled', function (done) { + var server = createServer({ threshold: 0, enforceEncoding: 'gzip', encodings: { gzip: false } }, function (req, res) { + res.setHeader('Content-Type', 'text/plain') + res.end('hello, world') + }) + + request(server) + .get('/') + .set('Accept-Encoding', '') + .expect(shouldNotHaveHeader('Content-Encoding')) + .expect(200, 'hello, world', done) + }) }) - it('should compress when enforceEncoding is deflate', function (done) { - var server = createServer({ threshold: 0, enforceEncoding: 'deflate' }, function (req, res) { - res.setHeader('Content-Type', 'text/plain') - res.end('hello, world') + describe('deflate', function () { + it('should compress when enforceEncoding is deflate', function (done) { + var server = createServer({ threshold: 0, enforceEncoding: 'deflate' }, function (req, res) { + res.setHeader('Content-Type', 'text/plain') + res.end('hello, world') + }) + + request(server) + .get('/') + .set('Accept-Encoding', '') + .expect('Content-Encoding', 'deflate') + .expect(200, 'hello, world', done) }) - request(server) - .get('/') - .set('Accept-Encoding', '') - .expect('Content-Encoding', 'deflate') - .expect(200, 'hello, world', done) + it('should skip when defalte is disabled', function (done) { + var server = createServer({ threshold: 0, enforceEncoding: 'deflate', encodings: { deflate: false } }, function (req, res) { + res.setHeader('Content-Type', 'text/plain') + res.end('hello, world') + }) + + request(server) + .get('/') + .set('Accept-Encoding', '') + .expect(shouldNotHaveHeader('Content-Encoding')) + .expect(200, 'hello, world', done) + }) }) - it('should compress when enforceEncoding is brotli', function (done) { - var server = createServer({ threshold: 0, enforceEncoding: 'br' }, function (req, res) { - res.setHeader('Content-Type', 'text/plain') - res.end('hello, world') + describe('brotli', function () { + it('should skip when brotli is disabled', function (done) { + var server = createServer({ threshold: 0, enforceEncoding: 'br', encodings: { br: false } }, function (req, res) { + res.setHeader('Content-Type', 'text/plain') + res.end('hello, world') + }) + + request(server) + .get('/') + .set('Accept-Encoding', '') + .expect(shouldNotHaveHeader('Content-Encoding')) + .expect(200, 'hello, world', done) }) - request(server) - .get('/') - .set('Accept-Encoding', '') - .expect('Content-Encoding', 'br') - .expect(200, done) + it('should compress when enforceEncoding is brotli', function (done) { + var server = createServer({ threshold: 0, enforceEncoding: 'br' }, function (req, res) { + res.setHeader('Content-Type', 'text/plain') + res.end('hello, world') + }) + + request(server) + .get('/') + .set('Accept-Encoding', '') + .expect('Content-Encoding', 'br') + .expect(200, done) + }) }) it('should not compress when enforceEncoding is unknown', function (done) { From 0572bcb29e6a74ef13522e8db8193eb997ca8a9e Mon Sep 17 00:00:00 2001 From: Sebastian Beltran Date: Sat, 3 May 2025 17:58:09 -0500 Subject: [PATCH 5/9] fix: correct spelling of 'encoding' in variable names and test descriptions Signed-off-by: Sebastian Beltran --- index.js | 4 ++-- test/compression.js | 6 +++--- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/index.js b/index.js index 1da8659f..c0f380d1 100644 --- a/index.js +++ b/index.js @@ -36,7 +36,7 @@ module.exports.filter = shouldCompress * @private */ var cacheControlNoTransformRegExp = /(?:^|,)\s*?no-transform\s*?(?:,|$)/ -var ENCONDING_OPTIONS = ['br', 'gzip', 'deflate'] +var ENCODING_OPTIONS = ['br', 'gzip', 'deflate'] var PREFERRED_ENCODING = ['br', 'gzip'] /** @@ -50,7 +50,7 @@ var PREFERRED_ENCODING = ['br', 'gzip'] function compression (options) { const opts = options || {} const encodingOpts = opts?.encodings - const encodingSupported = ENCONDING_OPTIONS.filter(enc => encodingOpts?.[enc] !== false) + const encodingSupported = ENCODING_OPTIONS.filter(enc => encodingOpts?.[enc] !== false) const optsBrotli = { ...encodingOpts?.br, params: { diff --git a/test/compression.js b/test/compression.js index b21c9e98..b89d9427 100644 --- a/test/compression.js +++ b/test/compression.js @@ -472,9 +472,9 @@ describe('compression()', function () { }) }) - describe('encondigs', function () { + describe('encodings', function () { describe('when "Accept-Encoding: gzip"', function () { - it('when gzip is disable', function (done) { + it('when gzip is disabled', function (done) { var server = createServer({ threshold: 0, encodings: { gzip: false } }, function (req, res) { res.setHeader('Content-Type', 'text/plain') res.end('hello, world') @@ -641,7 +641,7 @@ describe('compression()', function () { }) describe('when "Accept-Encoding: gzip, br"', function () { - it('should respond with gzip when brotli is disable', function (done) { + it('should respond with gzip when brotli is disabled', function (done) { var server = createServer({ threshold: 0, encodings: { br: false } }, function (req, res) { res.setHeader('Content-Type', 'text/plain') res.end('hello, world') From 9f0513e4044d81eff5cb827ee5c74c6eb19ab161 Mon Sep 17 00:00:00 2001 From: Sebastian Beltran Date: Sat, 3 May 2025 18:30:02 -0500 Subject: [PATCH 6/9] docs: update README Signed-off-by: Sebastian Beltran --- README.md | 130 ++++++++++++++++++------------------------------------ 1 file changed, 42 insertions(+), 88 deletions(-) diff --git a/README.md b/README.md index 682517b4..b271c30f 100644 --- a/README.md +++ b/README.md @@ -43,102 +43,64 @@ as compressing will transform the body. #### Options -`compression()` accepts these properties in the options object. In addition to -those listed below, [zlib](http://nodejs.org/api/zlib.html) options may be -passed in to the options object or -[brotli](https://nodejs.org/api/zlib.html#zlib_class_brotlioptions) options. +`compression()` accepts these properties in the options object. -##### chunkSize +##### encodings -Type: `Number`
-Default: `zlib.constants.Z_DEFAULT_CHUNK`, or `16384`. +Type: `Object`
-See [Node.js documentation](http://nodejs.org/api/zlib.html#zlib_memory_usage_tuning) -regarding the usage. +Handles the configurations for each encoding. Setting options.encodings[encoding] = false will disable that encoding. -##### filter - -Type: `Function` - -A function to decide if the response should be considered for compression. -This function is called as `filter(req, res)` and is expected to return -`true` to consider the response for compression, or `false` to not compress -the response. - -The default filter function uses the [compressible](https://www.npmjs.com/package/compressible) -module to determine if `res.getHeader('Content-Type')` is compressible. - -##### level - -Type: `Number`
-Default: `zlib.constants.Z_DEFAULT_COMPRESSION`, or `-1` - -The level of zlib compression to apply to responses. A higher level will result -in better compression, but will take longer to complete. A lower level will -result in less compression, but will be much faster. - -This is an integer in the range of `0` (no compression) to `9` (maximum -compression). The special value `-1` can be used to mean the "default -compression level", which is a default compromise between speed and -compression (currently equivalent to level 6). - - - `-1` Default compression level (also `zlib.constants.Z_DEFAULT_COMPRESSION`). - - `0` No compression (also `zlib.constants.Z_NO_COMPRESSION`). - - `1` Fastest compression (also `zlib.constants.Z_BEST_SPEED`). - - `2` - - `3` - - `4` - - `5` - - `6` (currently what `zlib.constants.Z_DEFAULT_COMPRESSION` points to). - - `7` - - `8` - - `9` Best compression (also `zlib.constants.Z_BEST_COMPRESSION`). - -**Note** in the list above, `zlib` is from `zlib = require('zlib')`. +Example: -##### memLevel +```js +const compression = require('compression') +const express = require('express') + +const app = express() + +app.use( + compression({ + encodings: { + br: { + params: { + [zlib.constants.BROTLI_PARAM_QUALITY]: 6, + }, + }, + gzip: { + level: zlib.constants.Z_BEST_SPEED, + }, + deflate: false, // Disable Deflate compression + }, + }) +) +``` -Type: `Number`
-Default: `zlib.constants.Z_DEFAULT_MEMLEVEL`, or `8` +###### Supported Encodings -This specifies how much memory should be allocated for the internal compression -state and is an integer in the range of `1` (minimum level) and `9` (maximum -level). +- **`br` (Brotli):** -See [Node.js documentation](http://nodejs.org/api/zlib.html#zlib_memory_usage_tuning) -regarding the usage. +This specifies the options for configuring Brotli. See [Node.js documentation](https://nodejs.org/api/zlib.html#class-brotlioptions) for a complete list of available options. -##### brotli +- **gzip:** -Type: `Object` +This specifies the options for configuring gzip. See [Node.js documentation](https://nodejs.org/api/zlib.html#class-options) for a complete list of available options. -This specifies the options for configuring Brotli. See [Node.js documentation](https://nodejs.org/api/zlib.html#class-brotlioptions) for a complete list of available options. +- **deflate:** +This specifies the options for configuring deflate. See [Node.js documentation](https://nodejs.org/api/zlib.html#class-options) for a complete list of available options. -##### strategy - -Type: `Number`
-Default: `zlib.constants.Z_DEFAULT_STRATEGY` +##### filter -This is used to tune the compression algorithm. This value only affects the -compression ratio, not the correctness of the compressed output, even if it -is not set appropriately. +Type: `Function` - - `zlib.constants.Z_DEFAULT_STRATEGY` Use for normal data. - - `zlib.constants.Z_FILTERED` Use for data produced by a filter (or predictor). - Filtered data consists mostly of small values with a somewhat random - distribution. In this case, the compression algorithm is tuned to - compress them better. The effect is to force more Huffman coding and less - string matching; it is somewhat intermediate between `zlib.constants.Z_DEFAULT_STRATEGY` - and `zlib.constants.Z_HUFFMAN_ONLY`. - - `zlib.constants.Z_FIXED` Use to prevent the use of dynamic Huffman codes, allowing - for a simpler decoder for special applications. - - `zlib.constants.Z_HUFFMAN_ONLY` Use to force Huffman encoding only (no string match). - - `zlib.constants.Z_RLE` Use to limit match distances to one (run-length encoding). - This is designed to be almost as fast as `zlib.constants.Z_HUFFMAN_ONLY`, but give - better compression for PNG image data. +A function to decide if the response should be considered for compression. +This function is called as `filter(req, res)` and is expected to return +`true` to consider the response for compression, or `false` to not compress +the response. -**Note** in the list above, `zlib` is from `zlib = require('zlib')`. +The default filter function uses the [compressible](https://www.npmjs.com/package/compressible) +module to determine if `res.getHeader('Content-Type')` is compressible. ##### threshold @@ -154,14 +116,6 @@ at the time the response headers are written, then it is assumed the response is _over_ the threshold. To guarantee the response size can be determined, be sure set a `Content-Length` response header. -##### windowBits - -Type: `Number`
-Default: `zlib.constants.Z_DEFAULT_WINDOWBITS`, or `15` - -See [Node.js documentation](http://nodejs.org/api/zlib.html#zlib_memory_usage_tuning) -regarding the usage. - ##### enforceEncoding Type: `String`
From 08c5829e3eb647fdcafd627028177e6b4c88bbba Mon Sep 17 00:00:00 2001 From: Sebastian Beltran Date: Sat, 3 May 2025 18:34:08 -0500 Subject: [PATCH 7/9] fix: correct spelling of 'encoding' in test descriptions Signed-off-by: Sebastian Beltran --- test/compression.js | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/test/compression.js b/test/compression.js index b89d9427..b724fa8d 100644 --- a/test/compression.js +++ b/test/compression.js @@ -51,7 +51,7 @@ describe('compression()', function () { .expect(200, 'hello, world', done) }) - it('should skip if content-encondig is identity', function (done) { + it('should skip if content-encoding is identity', function (done) { var server = createServer({ threshold: 0 }, function (req, res) { res.setHeader('Content-Type', 'text/plain') res.end('hello, world') @@ -527,7 +527,7 @@ describe('compression()', function () { .expect('Content-Encoding', 'deflate', done) }) - it('when deflate is disable', function (done) { + it('when deflate is disabled', function (done) { var server = createServer({ threshold: 0, encodings: { deflate: false } }, function (req, res) { res.setHeader('Content-Type', 'text/plain') res.end('hello, world') @@ -542,7 +542,7 @@ describe('compression()', function () { }) describe('when "Accept-Encoding: br"', function () { - it('when brotli is disable should not compress', function (done) { + it('when brotli is disabled should not compress', function (done) { var server = createServer({ threshold: 0, encodings: { br: false } }, function (req, res) { res.setHeader('Content-Type', 'text/plain') res.end('hello, world') @@ -601,7 +601,7 @@ describe('compression()', function () { }) describe('when "Accept-Encoding: gzip, deflate"', function () { - it('should respond with deflate when gzip is disable', function (done) { + it('should respond with deflate when gzip is disabled', function (done) { var server = createServer({ threshold: 0, encodings: { gzip: false } }, function (req, res) { res.setHeader('Content-Type', 'text/plain') res.end('hello, world') @@ -667,7 +667,7 @@ describe('compression()', function () { }) describe('when "Accept-Encoding: deflate, gzip, br"', function () { - it('should respond with gzip when brotli is disable', function (done) { + it('should respond with gzip when brotli is disabled', function (done) { var server = createServer({ threshold: 0, encodings: { br: false } }, function (req, res) { res.setHeader('Content-Type', 'text/plain') res.end('hello, world') @@ -679,7 +679,7 @@ describe('compression()', function () { .expect('Content-Encoding', 'gzip', done) }) - it('should respond with deflate when brotli and gzip is disable', function (done) { + it('should respond with deflate when brotli and gzip is disabled', function (done) { var server = createServer({ threshold: 0, encodings: { br: false, gzip: false } }, function (req, res) { res.setHeader('Content-Type', 'text/plain') res.end('hello, world') @@ -747,7 +747,7 @@ describe('compression()', function () { }) describe('when "Accept-Encoding: deflate, br"', function () { - it('should respond with deflate when brotli is disable', function (done) { + it('should respond with deflate when brotli is disabled', function (done) { var server = createServer({ threshold: 0, encodings: { br: false } }, function (req, res) { res.setHeader('Content-Type', 'text/plain') res.end('hello, world') From e4cb454acc842beed6eaf40e42126e54ac59a8bd Mon Sep 17 00:00:00 2001 From: Sebastian Beltran Date: Sat, 3 May 2025 18:35:38 -0500 Subject: [PATCH 8/9] update readme Signed-off-by: Sebastian Beltran --- README.md | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index b271c30f..19bc7c90 100644 --- a/README.md +++ b/README.md @@ -64,14 +64,14 @@ app.use( encodings: { br: { params: { - [zlib.constants.BROTLI_PARAM_QUALITY]: 6, - }, + [zlib.constants.BROTLI_PARAM_QUALITY]: 6 + } }, gzip: { - level: zlib.constants.Z_BEST_SPEED, + level: zlib.constants.Z_BEST_SPEED }, - deflate: false, // Disable Deflate compression - }, + deflate: false // Disable Deflate compression + } }) ) ``` From 58d0a675bf166b3e421768b17fd0e01bbd6f232b Mon Sep 17 00:00:00 2001 From: Sebastian Beltran Date: Sat, 3 May 2025 18:38:21 -0500 Subject: [PATCH 9/9] Update test/compression.js Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- test/compression.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/compression.js b/test/compression.js index b724fa8d..6f855989 100644 --- a/test/compression.js +++ b/test/compression.js @@ -1144,7 +1144,7 @@ describe('compression()', function () { .expect(200, 'hello, world', done) }) - it('should skip when defalte is disabled', function (done) { + it('should skip when deflate is disabled', function (done) { var server = createServer({ threshold: 0, enforceEncoding: 'deflate', encodings: { deflate: false } }, function (req, res) { res.setHeader('Content-Type', 'text/plain') res.end('hello, world')