@@ -22,6 +22,7 @@ var debug = require('debug')('compression')
22
22
var onHeaders = require ( 'on-headers' )
23
23
var vary = require ( 'vary' )
24
24
var zlib = require ( 'zlib' )
25
+ var objectAssign = require ( 'object-assign' )
25
26
26
27
/**
27
28
* Module exports.
@@ -30,6 +31,12 @@ var zlib = require('zlib')
30
31
module . exports = compression
31
32
module . exports . filter = shouldCompress
32
33
34
+ /**
35
+ * @const
36
+ * whether current node version has brotli support
37
+ */
38
+ var hasBrotliSupport = 'createBrotliCompress' in zlib
39
+
33
40
/**
34
41
* Module variables.
35
42
* @private
@@ -48,6 +55,17 @@ var cacheControlNoTransformRegExp = /(?:^|,)\s*?no-transform\s*?(?:,|$)/
48
55
function compression ( options ) {
49
56
var opts = options || { }
50
57
58
+ if ( hasBrotliSupport ) {
59
+ // set the default level to a reasonable value with balanced speed/ratio
60
+ if ( opts . params === undefined ) {
61
+ opts . params = { }
62
+ }
63
+
64
+ if ( opts . params [ zlib . constants . BROTLI_PARAM_QUALITY ] === undefined ) {
65
+ opts . params [ zlib . constants . BROTLI_PARAM_QUALITY ] = 4
66
+ }
67
+ }
68
+
51
69
// options
52
70
var filter = opts . filter || shouldCompress
53
71
var threshold = bytes . parse ( opts . threshold )
@@ -173,14 +191,12 @@ function compression (options) {
173
191
return
174
192
}
175
193
176
- // compression method
177
- var accept = accepts ( req )
178
- var method = accept . encoding ( [ 'gzip' , 'deflate' , 'identity' ] )
194
+ // force proper priorization
195
+ var headers = objectAssign ( { } , req . headers , { 'accept-encoding' : prioritize ( req . headers [ 'accept-encoding' ] ) } )
179
196
180
- // we really don't prefer deflate
181
- if ( method === 'deflate' && accept . encoding ( [ 'gzip' ] ) ) {
182
- method = accept . encoding ( [ 'gzip' , 'identity' ] )
183
- }
197
+ // compression method
198
+ var accept = accepts ( objectAssign ( { } , res , { headers : headers } ) )
199
+ var method = accept . encoding ( [ 'br' , 'gzip' , 'deflate' , 'identity' ] )
184
200
185
201
// negotiation failed
186
202
if ( ! method || method === 'identity' ) {
@@ -192,7 +208,9 @@ function compression (options) {
192
208
debug ( '%s compression' , method )
193
209
stream = method === 'gzip'
194
210
? zlib . createGzip ( opts )
195
- : zlib . createDeflate ( opts )
211
+ : method === 'br'
212
+ ? zlib . createBrotliCompress ( opts )
213
+ : zlib . createDeflate ( opts )
196
214
197
215
// add buffered listeners to stream
198
216
addListeners ( stream , stream . on , listeners )
@@ -286,3 +304,47 @@ function toBuffer (chunk, encoding) {
286
304
? Buffer . from ( chunk , encoding )
287
305
: chunk
288
306
}
307
+
308
+ /**
309
+ * Most browsers send "br" (brolti) as the last value in
310
+ * in the 'Accept-Encoding' header which causes it to be
311
+ * depriorited according to the spec.
312
+ *
313
+ * This is typically not what end users actually want so here
314
+ * we force the "br" (brotli) value to first in the list so that
315
+ * it will get properly prioritized and used.
316
+ *
317
+ * It's worth noting that although this is not "spec compliant",
318
+ * we belive it follows a well-established convention.
319
+ *
320
+ * @private
321
+ */
322
+ function prioritize ( str ) {
323
+ return str && str . split ( ',' )
324
+ . sort ( sortEncodings )
325
+ . join ( ',' )
326
+ }
327
+
328
+ /**
329
+ * Sort compression encodings in order of our preference:
330
+ * br > gzip > deflate
331
+ *
332
+ * @private
333
+ */
334
+ function sortEncodings ( a , b ) {
335
+ if ( a . indexOf ( 'br' ) >= 0 ) {
336
+ return - 1
337
+ }
338
+ if ( a . indexOf ( 'gzip' ) >= 0 ) {
339
+ return b . indexOf ( 'br' ) >= 0 ? 1 : - 1
340
+ }
341
+ // we need these inverse rules to fix a stable sort bug
342
+ // found in node 10.x
343
+ if ( b . indexOf ( 'br' ) >= 0 ) {
344
+ return 1
345
+ }
346
+ if ( b . indexOf ( 'gzip' ) >= 0 ) {
347
+ return 1
348
+ }
349
+ return 0
350
+ }
0 commit comments