Skip to content

Commit 42dacd1

Browse files
committed
Added support for brotli ('br') content-encoding
1 parent dd5055d commit 42dacd1

File tree

4 files changed

+104
-4
lines changed

4 files changed

+104
-4
lines changed

README.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,9 @@ The following compression codings are supported:
1111

1212
- deflate
1313
- gzip
14+
- br (brotli)
15+
16+
**Note** Brotli provides better and faster compression then gzip or deflate, but is supported only since Node.js versions v11.7.0 and v10.16.0.
1417

1518
## Install
1619

@@ -46,6 +49,9 @@ as compressing will transform the body.
4649
those listed below, [zlib](http://nodejs.org/api/zlib.html) options may be
4750
passed in to the options object.
4851

52+
As for *brotli*, a default is set to compression level 4, unless
53+
[anything else is specified](https://nodejs.org/api/zlib.html#zlib_class_brotlioptions).
54+
4955
##### chunkSize
5056

5157
The default value is `zlib.Z_DEFAULT_CHUNK`, or `16384`.

index.js

Lines changed: 31 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ var Buffer = require('safe-buffer').Buffer
1919
var bytes = require('bytes')
2020
var compressible = require('compressible')
2121
var debug = require('debug')('compression')
22+
var objectAssign = require('object-assign')
2223
var onHeaders = require('on-headers')
2324
var vary = require('vary')
2425
var zlib = require('zlib')
@@ -37,6 +38,24 @@ module.exports.filter = shouldCompress
3738

3839
var cacheControlNoTransformRegExp = /(?:^|,)\s*?no-transform\s*?(?:,|$)/
3940

41+
/**
42+
* @const
43+
* whether current node version has brotli support
44+
*/
45+
var hasBrotliSupport = 'brotli' in process.versions
46+
47+
var supportedEncodings = hasBrotliSupport
48+
? ['gzip', 'deflate', 'br', 'identity']
49+
: ['gzip', 'deflate', 'identity']
50+
51+
var supportedCompressionsNoDeflate = hasBrotliSupport
52+
? ['gzip', 'br']
53+
: ['gzip']
54+
55+
var supportedEncodingsNoDeflate = hasBrotliSupport
56+
? ['gzip', 'br', 'identity']
57+
: ['gzip', 'identity']
58+
4059
/**
4160
* Compress response data with gzip / deflate.
4261
*
@@ -48,6 +67,12 @@ var cacheControlNoTransformRegExp = /(?:^|,)\s*?no-transform\s*?(?:,|$)/
4867
function compression (options) {
4968
var opts = options || {}
5069

70+
if (hasBrotliSupport && opts.params === undefined) {
71+
opts = objectAssign({}, opts)
72+
opts.params = {}
73+
opts.params[zlib.constants.BROTLI_PARAM_QUALITY] = 4
74+
}
75+
5176
// options
5277
var filter = opts.filter || shouldCompress
5378
var threshold = bytes.parse(opts.threshold)
@@ -175,11 +200,11 @@ function compression (options) {
175200

176201
// compression method
177202
var accept = accepts(req)
178-
var method = accept.encoding(['gzip', 'deflate', 'identity'])
203+
var method = accept.encoding(supportedEncodings)
179204

180205
// we really don't prefer deflate
181-
if (method === 'deflate' && accept.encoding(['gzip'])) {
182-
method = accept.encoding(['gzip', 'identity'])
206+
if (method === 'deflate' && accept.encoding(supportedCompressionsNoDeflate)) {
207+
method = accept.encoding(supportedEncodingsNoDeflate)
183208
}
184209

185210
// negotiation failed
@@ -192,7 +217,9 @@ function compression (options) {
192217
debug('%s compression', method)
193218
stream = method === 'gzip'
194219
? zlib.createGzip(opts)
195-
: zlib.createDeflate(opts)
220+
: method === 'br'
221+
? zlib.createBrotliCompress(opts)
222+
: zlib.createDeflate(opts)
196223

197224
// add buffered listeners to stream
198225
addListeners(stream, stream.on, listeners)

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
"bytes": "3.0.0",
1414
"compressible": "~2.0.16",
1515
"debug": "2.6.9",
16+
"object-assign": "^4.1.1",
1617
"on-headers": "~1.0.2",
1718
"safe-buffer": "5.1.2",
1819
"vary": "~1.1.2"

test/compression.js

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,12 @@ var zlib = require('zlib')
99

1010
var compression = require('..')
1111

12+
/**
13+
* @const
14+
* whether current node version has brotli support
15+
*/
16+
var hasBrotliSupport = 'brotli' in process.versions
17+
1218
describe('compression()', function () {
1319
it('should skip HEAD', function (done) {
1420
var server = createServer({ threshold: 0 }, function (req, res) {
@@ -465,6 +471,21 @@ describe('compression()', function () {
465471
})
466472
})
467473

474+
describe('when "Accept-Encoding: br"', function () {
475+
var brotlit = hasBrotliSupport ? it : it.skip
476+
brotlit('should respond with br', function (done) {
477+
var server = createServer({ threshold: 0 }, function (req, res) {
478+
res.setHeader('Content-Type', 'text/plain')
479+
res.end('hello, world')
480+
})
481+
482+
request(server)
483+
.get('/')
484+
.set('Accept-Encoding', 'br')
485+
.expect('Content-Encoding', 'br', done)
486+
})
487+
})
488+
468489
describe('when "Accept-Encoding: gzip, deflate"', function () {
469490
it('should respond with gzip', function (done) {
470491
var server = createServer({ threshold: 0 }, function (req, res) {
@@ -493,6 +514,21 @@ describe('compression()', function () {
493514
})
494515
})
495516

517+
describe('when "Accept-Encoding: deflate, br"', function () {
518+
var brotlit = hasBrotliSupport ? it : it.skip
519+
brotlit('should respond with br', function (done) {
520+
var server = createServer({ threshold: 0 }, function (req, res) {
521+
res.setHeader('Content-Type', 'text/plain')
522+
res.end('hello, world')
523+
})
524+
525+
request(server)
526+
.get('/')
527+
.set('Accept-Encoding', 'deflate, br')
528+
.expect('Content-Encoding', 'br', done)
529+
})
530+
})
531+
496532
describe('when "Cache-Control: no-transform" response header', function () {
497533
it('should not compress response', function (done) {
498534
var server = createServer({ threshold: 0 }, function (req, res) {
@@ -631,6 +667,33 @@ describe('compression()', function () {
631667
.end()
632668
})
633669

670+
var brotlit = hasBrotliSupport ? it : it.skip
671+
brotlit('should flush small chunks for brotli', function (done) {
672+
var chunks = 0
673+
var next
674+
var server = createServer({ threshold: 0 }, function (req, res) {
675+
next = writeAndFlush(res, 2, Buffer.from('..'))
676+
res.setHeader('Content-Type', 'text/plain')
677+
next()
678+
})
679+
680+
function onchunk (chunk) {
681+
assert.ok(chunks++ < 20)
682+
assert.strictEqual(chunk.toString(), '..')
683+
next()
684+
}
685+
686+
request(server)
687+
.get('/')
688+
.set('Accept-Encoding', 'br')
689+
.request()
690+
.on('response', unchunk('br', onchunk, function (err) {
691+
if (err) return done(err)
692+
server.close(done)
693+
}))
694+
.end()
695+
})
696+
634697
it('should flush small chunks for deflate', function (done) {
635698
var chunks = 0
636699
var next
@@ -710,6 +773,9 @@ function unchunk (encoding, onchunk, onend) {
710773
case 'gzip':
711774
stream = res.pipe(zlib.createGunzip())
712775
break
776+
case 'br':
777+
stream = res.pipe(zlib.createBrotliDecompress())
778+
break
713779
}
714780

715781
stream.on('data', onchunk)

0 commit comments

Comments
 (0)