Skip to content

Commit 3b31349

Browse files
committed
feat: Adding support for brolti
1 parent 3fea81d commit 3b31349

File tree

4 files changed

+343
-9
lines changed

4 files changed

+343
-9
lines changed

README.md

Lines changed: 27 additions & 1 deletion
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 is supported only since Node.js versions v11.7.0 and v10.16.0.
1417

1518
## Install
1619

@@ -44,7 +47,8 @@ as compressing will transform the body.
4447

4548
`compression()` accepts these properties in the options object. In addition to
4649
those listed below, [zlib](http://nodejs.org/api/zlib.html) options may be
47-
passed in to the options object.
50+
passed in to the options object or
51+
[brotli](https://nodejs.org/api/zlib.html#zlib_class_brotlioptions) options.
4852

4953
##### chunkSize
5054

@@ -101,6 +105,20 @@ The default value is `zlib.Z_DEFAULT_MEMLEVEL`, or `8`.
101105
See [Node.js documentation](http://nodejs.org/api/zlib.html#zlib_memory_usage_tuning)
102106
regarding the usage.
103107

108+
##### params *(brotli only)* - [key-value object containing indexed Brotli parameters](https://nodejs.org/api/zlib.html#zlib_brotli_constants)
109+
110+
- `zlib.constants.BROTLI_PARAM_MODE`
111+
- `zlib.constants.BROTLI_MODE_GENERIC` (default)
112+
- `zlib.constants.BROTLI_MODE_TEXT`, adjusted for UTF-8 text
113+
- `zlib.constants.BROTLI_MODE_FONT`, adjusted for WOFF 2.0 fonts
114+
- `zlib.constants.BROTLI_PARAM_QUALITY`
115+
- Ranges from `zlib.constants.BROTLI_MIN_QUALITY` to
116+
`zlib.constants.BROTLI_MAX_QUALITY`, with a default of
117+
`4` (which is not node's default but the most optimal).
118+
119+
Note that here the default is set to compression level 4. This is a balanced setting with a very good speed and a very good
120+
compression ratio.
121+
104122
##### strategy
105123

106124
This is used to tune the compression algorithm. This value only affects the
@@ -141,6 +159,14 @@ The default value is `zlib.Z_DEFAULT_WINDOWBITS`, or `15`.
141159
See [Node.js documentation](http://nodejs.org/api/zlib.html#zlib_memory_usage_tuning)
142160
regarding the usage.
143161

162+
##### preferClient
163+
164+
This library by default will prioritize the optimal compression encoding algorithm if the client has
165+
given more than one encoding the same explicit "quality". This is generally what users want and it
166+
follows an established pattern used by other popular web servers. If you would like to opt-out of this
167+
behavior (and therefore be more spec compliant), you can set `preferClient` to "true" and when
168+
two encodings have the same "quality" the first one will be used.
169+
144170
#### .filter
145171

146172
The default `filter` function. This is used to construct a custom filter

index.js

Lines changed: 76 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ var debug = require('debug')('compression')
2222
var onHeaders = require('on-headers')
2323
var vary = require('vary')
2424
var zlib = require('zlib')
25+
var objectAssign = require('object-assign')
2526

2627
/**
2728
* Module exports.
@@ -30,6 +31,17 @@ var zlib = require('zlib')
3031
module.exports = compression
3132
module.exports.filter = shouldCompress
3233

34+
/**
35+
* @const
36+
* whether current node version has brotli support
37+
*/
38+
var hasBrotliSupport = 'createBrotliCompress' in zlib
39+
40+
var preferredEncodings = ['gzip', 'deflate', 'identity']
41+
if (hasBrotliSupport) {
42+
preferredEncodings.unshift('br')
43+
}
44+
3345
/**
3446
* Module variables.
3547
* @private
@@ -48,6 +60,17 @@ var cacheControlNoTransformRegExp = /(?:^|,)\s*?no-transform\s*?(?:,|$)/
4860
function compression (options) {
4961
var opts = options || {}
5062

63+
if (hasBrotliSupport) {
64+
// set the default level to a reasonable value with balanced speed/ratio
65+
if (opts.params === undefined) {
66+
opts.params = {}
67+
}
68+
69+
if (opts.params[zlib.constants.BROTLI_PARAM_QUALITY] === undefined) {
70+
opts.params[zlib.constants.BROTLI_PARAM_QUALITY] = 4
71+
}
72+
}
73+
5174
// options
5275
var filter = opts.filter || shouldCompress
5376
var threshold = bytes.parse(opts.threshold)
@@ -173,14 +196,12 @@ function compression (options) {
173196
return
174197
}
175198

176-
// compression method
177-
var accept = accepts(req)
178-
var method = accept.encoding(['gzip', 'deflate', 'identity'])
199+
// force proper priorization
200+
var headers = objectAssign({}, req.headers, options.prioritizeClient ? null : { 'accept-encoding': prioritize(req.headers['accept-encoding']) })
179201

180-
// we really don't prefer deflate
181-
if (method === 'deflate' && accept.encoding(['gzip'])) {
182-
method = accept.encoding(['gzip', 'identity'])
183-
}
202+
// compression method
203+
var accept = accepts({ headers: headers })
204+
var method = accept.encoding(preferredEncodings)
184205

185206
// negotiation failed
186207
if (!method || method === 'identity') {
@@ -192,7 +213,9 @@ function compression (options) {
192213
debug('%s compression', method)
193214
stream = method === 'gzip'
194215
? zlib.createGzip(opts)
195-
: zlib.createDeflate(opts)
216+
: method === 'br'
217+
? zlib.createBrotliCompress(opts)
218+
: zlib.createDeflate(opts)
196219

197220
// add buffered listeners to stream
198221
addListeners(stream, stream.on, listeners)
@@ -286,3 +309,48 @@ function toBuffer (chunk, encoding) {
286309
? Buffer.from(chunk, encoding)
287310
: chunk
288311
}
312+
313+
/**
314+
* Most browsers send "br" (brotli) as the last value in
315+
* in the 'Accept-Encoding' header which causes it to be
316+
* deprioritized according to the spec.
317+
*
318+
* This is typically not what end users actually want so here
319+
* we force the "br" (brotli) value to first in the list so that
320+
* it will get properly prioritized and used.
321+
*
322+
* It's worth noting that although this is not "spec compliant",
323+
* we belive it follows a well-established convention.
324+
*
325+
* @private
326+
*/
327+
function prioritize (str) {
328+
return str && str.toLowerCase()
329+
.split(',')
330+
.sort(sortEncodings)
331+
.join(',')
332+
}
333+
334+
/**
335+
* Sort compression encodings in order of our preference:
336+
* br > gzip > deflate
337+
*
338+
* @private
339+
*/
340+
function sortEncodings (a, b) {
341+
if (a.indexOf('br') >= 0) {
342+
return -1
343+
}
344+
if (a.indexOf('gzip') >= 0) {
345+
return b.indexOf('br') >= 0 ? 1 : -1
346+
}
347+
// we need these inverse rules to fix a stable sort bug
348+
// found in node 10.x
349+
if (b.indexOf('br') >= 0) {
350+
return 1
351+
}
352+
if (b.indexOf('gzip') >= 0) {
353+
return 1
354+
}
355+
return 0
356+
}

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.17",
1515
"debug": "2.6.9",
16+
"object-assign": "4.1.1",
1617
"on-headers": "~1.0.2",
1718
"safe-buffer": "5.2.0",
1819
"vary": "~1.1.2"

0 commit comments

Comments
 (0)