diff --git a/README.md b/README.md index a5d599db..246e9436 100644 --- a/README.md +++ b/README.md @@ -136,6 +136,28 @@ The default value is `zlib.Z_DEFAULT_WINDOWBITS`, or `15`. See [Node.js documentation](http://nodejs.org/api/zlib.html#zlib_memory_usage_tuning) regarding the usage. +##### compressor + +Allows you to specify your own compression stream for a given Content-Encoding. + +```js +var opts = { + compressor: { + br: brotliStreamEncoder, + lzma: lzmaStreamEncoder, + }, +}; +``` + +Where the custom compressor is a function that returns effectively a transform +stream. ex: + +```js +function brotliStreamEncoder (opts) { + return brotli.compressStream(opts); +}; +``` + #### .filter The default `filter` function. This is used to construct a custom filter diff --git a/index.js b/index.js index acfc2a21..c325c594 100644 --- a/index.js +++ b/index.js @@ -173,7 +173,17 @@ function compression(options) { // compression method var accept = accepts(req) - var method = accept.encoding(['gzip', 'deflate', 'identity']) + var method = null + var acceptableEncodings = ['gzip', 'deflate', 'identity'] + + // If we support a custom encoding and a custom encoding was requested + if (opts.compressor) { + method = accept.encoding(Object.keys(opts.compressor).concat(acceptableEncodings)) + } + + if (!method) { + method = accept.encoding(acceptableEncodings) + } // we really don't prefer deflate if (method === 'deflate' && accept.encoding(['gzip'])) { @@ -188,9 +198,13 @@ function compression(options) { // compression stream debug('%s compression', method) - stream = method === 'gzip' - ? zlib.createGzip(opts) - : zlib.createDeflate(opts) + if (method === 'gzip') { + stream = zlib.createGzip(opts) + } else if (method === 'deflate') { + stream = zlib.createDeflate(opts) + } else if (opts.compressor && method in opts.compressor) { + stream = opts.compressor[method](opts) + } // add bufferred listeners to stream addListeners(stream, stream.on, listeners) diff --git a/package.json b/package.json index 5cf3622d..f622c113 100644 --- a/package.json +++ b/package.json @@ -19,7 +19,8 @@ "devDependencies": { "istanbul": "0.3.21", "mocha": "2.3.3", - "supertest": "1.1.0" + "supertest": "1.1.0", + "through": "2.3.8" }, "files": [ "LICENSE", diff --git a/test/compression.js b/test/compression.js index 26e0ca26..1656ab29 100644 --- a/test/compression.js +++ b/test/compression.js @@ -3,6 +3,7 @@ var bytes = require('bytes'); var crypto = require('crypto'); var http = require('http'); var request = require('supertest'); +var through = require('through'); var compression = require('..'); @@ -478,6 +479,14 @@ describe('compression()', function(){ }) }) + function createCompressor () { + return through(function (d) { + this.queue(d.toString().split('').reverse().join('')) + }, function () { + this.queue(null) + }) + } + describe('when "Accept-Encoding: deflate, gzip"', function () { it('should respond with gzip', function (done) { var server = createServer({ threshold: 0 }, function (req, res) { @@ -490,6 +499,76 @@ describe('compression()', function(){ .set('Accept-Encoding', 'deflate, gzip') .expect('Content-Encoding', 'gzip', done) }) + + it('should respond with gzip even when a custom compressor is specified but not requested', function (done) { + var opts = { + threshold: 0, + compressor: { + 'bingo': createCompressor + } + } + var server = createServer(opts, 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: custom"', function () { + it('should not use content encoding without a custom compressor function', 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', 'custom') + .expect(shouldNotHaveHeader('Content-Encoding')) + .expect(200, 'hello, world', done) + }) + + it('should use content encoding with a custom compressor function', function (done) { + var opts = { + threshold: 0, + compressor: { + 'bingo': createCompressor + } + } + var server = createServer(opts, function (req, res) { + res.setHeader('Content-Type', 'text/plain') + res.end('hello, world') + }) + + request(server) + .get('/') + .set('Accept-Encoding', 'bingo, gzip') + .expect('Content-Encoding', 'bingo') + .expect(200, 'dlrow ,olleh', done) + }) + + it('should obey q= priorities', function (done) { + var opts = { + threshold: 0, + compressor: { + 'bingo': createCompressor + } + } + var server = createServer(opts, function (req, res) { + res.setHeader('Content-Type', 'text/plain') + res.end('hello, world') + }) + + request(server) + .get('/') + .set('Accept-Encoding', 'bingo;q=0.001, gzip') + .expect('Content-Encoding', 'gzip', done) + }) }) describe('when "Cache-Control: no-transform" response header', function () {