@@ -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,17 @@ 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
+
40
+ var preferredEncodings = [ 'gzip' , 'deflate' , 'identity' ]
41
+ if ( hasBrotliSupport ) {
42
+ preferredEncodings . unshift ( 'br' )
43
+ }
44
+
33
45
/**
34
46
* Module variables.
35
47
* @private
@@ -48,6 +60,17 @@ var cacheControlNoTransformRegExp = /(?:^|,)\s*?no-transform\s*?(?:,|$)/
48
60
function compression ( options ) {
49
61
var opts = options || { }
50
62
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
+
51
74
// options
52
75
var filter = opts . filter || shouldCompress
53
76
var threshold = bytes . parse ( opts . threshold )
@@ -173,14 +196,12 @@ function compression (options) {
173
196
return
174
197
}
175
198
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' ] ) } )
179
201
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 )
184
205
185
206
// negotiation failed
186
207
if ( ! method || method === 'identity' ) {
@@ -192,7 +213,9 @@ function compression (options) {
192
213
debug ( '%s compression' , method )
193
214
stream = method === 'gzip'
194
215
? zlib . createGzip ( opts )
195
- : zlib . createDeflate ( opts )
216
+ : method === 'br'
217
+ ? zlib . createBrotliCompress ( opts )
218
+ : zlib . createDeflate ( opts )
196
219
197
220
// add buffered listeners to stream
198
221
addListeners ( stream , stream . on , listeners )
@@ -286,3 +309,48 @@ function toBuffer (chunk, encoding) {
286
309
? Buffer . from ( chunk , encoding )
287
310
: chunk
288
311
}
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
+ }
0 commit comments