Skip to content

Commit

Permalink
Merge 69dc5b9 into 3fea81d
Browse files Browse the repository at this point in the history
  • Loading branch information
nicksrandall committed Sep 15, 2020
2 parents 3fea81d + 69dc5b9 commit bb02c57
Show file tree
Hide file tree
Showing 4 changed files with 318 additions and 9 deletions.
20 changes: 19 additions & 1 deletion README.md
Expand Up @@ -11,6 +11,9 @@ The following compression codings are supported:

- deflate
- gzip
- br (brotli)

**Note** Brotli is supported only since Node.js versions v11.7.0 and v10.16.0.

## Install

Expand Down Expand Up @@ -44,7 +47,8 @@ as compressing will transform the body.

`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.
passed in to the options object or
[brotli](https://nodejs.org/api/zlib.html#zlib_class_brotlioptions) options.

##### chunkSize

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

##### params *(brotli only)* - [key-value object containing indexed Brotli parameters](https://nodejs.org/api/zlib.html#zlib_brotli_constants)

- `zlib.constants.BROTLI_PARAM_MODE`
- `zlib.constants.BROTLI_MODE_GENERIC` (default)
- `zlib.constants.BROTLI_MODE_TEXT`, adjusted for UTF-8 text
- `zlib.constants.BROTLI_MODE_FONT`, adjusted for WOFF 2.0 fonts
- `zlib.constants.BROTLI_PARAM_QUALITY`
- Ranges from `zlib.constants.BROTLI_MIN_QUALITY` to
`zlib.constants.BROTLI_MAX_QUALITY`, with a default of
`4` (which is not node's default but the most optimal).

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
compression ratio.

##### strategy

This is used to tune the compression algorithm. This value only affects the
Expand Down
79 changes: 71 additions & 8 deletions index.js
Expand Up @@ -4,6 +4,7 @@
* Copyright(c) 2011 TJ Holowaychuk
* Copyright(c) 2014 Jonathan Ong
* Copyright(c) 2014-2015 Douglas Christopher Wilson
* Copyright(c) 2020 Nick Rndall
* MIT Licensed
*/

Expand All @@ -22,6 +23,7 @@ var debug = require('debug')('compression')
var onHeaders = require('on-headers')
var vary = require('vary')
var zlib = require('zlib')
var objectAssign = require('object-assign')

/**
* Module exports.
Expand All @@ -30,6 +32,12 @@ var zlib = require('zlib')
module.exports = compression
module.exports.filter = shouldCompress

/**
* @const
* whether current node version has brotli support
*/
var hasBrotliSupport = 'createBrotliCompress' in zlib

/**
* Module variables.
* @private
Expand All @@ -48,6 +56,17 @@ var cacheControlNoTransformRegExp = /(?:^|,)\s*?no-transform\s*?(?:,|$)/
function compression (options) {
var opts = options || {}

if (hasBrotliSupport) {
// set the default level to a reasonable value with balanced speed/ratio
if (opts.params === undefined) {
opts.params = {}
}

if (opts.params[zlib.constants.BROTLI_PARAM_QUALITY] === undefined) {
opts.params[zlib.constants.BROTLI_PARAM_QUALITY] = 4
}
}

// options
var filter = opts.filter || shouldCompress
var threshold = bytes.parse(opts.threshold)
Expand Down Expand Up @@ -173,14 +192,12 @@ function compression (options) {
return
}

// compression method
var accept = accepts(req)
var method = accept.encoding(['gzip', 'deflate', 'identity'])
// force proper priorization
var headers = objectAssign({}, req.headers, { 'accept-encoding': prioritize(req.headers['accept-encoding']) })

// we really don't prefer deflate
if (method === 'deflate' && accept.encoding(['gzip'])) {
method = accept.encoding(['gzip', 'identity'])
}
// compression method
var accept = accepts(objectAssign({}, res, { headers: headers }))
var method = accept.encoding(['br', 'gzip', 'deflate', 'identity'])

// negotiation failed
if (!method || method === 'identity') {
Expand All @@ -192,7 +209,9 @@ function compression (options) {
debug('%s compression', method)
stream = method === 'gzip'
? zlib.createGzip(opts)
: zlib.createDeflate(opts)
: method === 'br'
? zlib.createBrotliCompress(opts)
: zlib.createDeflate(opts)

// add buffered listeners to stream
addListeners(stream, stream.on, listeners)
Expand Down Expand Up @@ -286,3 +305,47 @@ function toBuffer (chunk, encoding) {
? Buffer.from(chunk, encoding)
: chunk
}

/**
* Most browsers send "br" (brolti) as the last value in
* in the 'Accept-Encoding' header which causes it to be
* depriorited according to the spec.
*
* This is typically not what end users actually want so here
* we force the "br" (brotli) value to first in the list so that
* it will get properly prioritized and used.
*
* It's worth noting that although this is not "spec compliant",
* we belive it follows a well-established convention.
*
* @private
*/
function prioritize (str) {
return str && str.split(',')
.sort(sortEncodings)
.join(',')
}

/**
* Sort compression encodings in order of our preference:
* br > gzip > deflate
*
* @private
*/
function sortEncodings (a, b) {
if (a.indexOf('br') >= 0) {
return -1
}
if (a.indexOf('gzip') >= 0) {
return b.indexOf('br') >= 0 ? 1 : -1
}
// we need these inverse rules to fix a stable sort bug
// found in node 10.x
if (b.indexOf('br') >= 0) {
return 1
}
if (b.indexOf('gzip') >= 0) {
return 1
}
return 0
}
1 change: 1 addition & 0 deletions package.json
Expand Up @@ -13,6 +13,7 @@
"bytes": "3.0.0",
"compressible": "~2.0.17",
"debug": "2.6.9",
"object-assign": "4.1.1",
"on-headers": "~1.0.2",
"safe-buffer": "5.2.0",
"vary": "~1.1.2"
Expand Down

0 comments on commit bb02c57

Please sign in to comment.