@@ -32,7 +32,7 @@ function decodePathname(pathname) {
32
32
33
33
34
34
// Check to see if we should try to compress a file with gzip.
35
- function shouldCompress ( req ) {
35
+ function shouldCompressGzip ( req ) {
36
36
const headers = req . headers ;
37
37
38
38
return headers && headers [ 'accept-encoding' ] &&
@@ -42,6 +42,16 @@ function shouldCompress(req) {
42
42
;
43
43
}
44
44
45
+ function shouldCompressBrotli ( req ) {
46
+ const headers = req . headers ;
47
+
48
+ return headers && headers [ 'accept-encoding' ] &&
49
+ headers [ 'accept-encoding' ]
50
+ . split ( ',' )
51
+ . some ( el => [ '*' , 'br' ] . indexOf ( el . trim ( ) ) !== - 1 )
52
+ ;
53
+ }
54
+
45
55
function hasGzipId12 ( gzipped , cb ) {
46
56
const stream = fs . createReadStream ( gzipped , { start : 0 , end : 1 } ) ;
47
57
let buffer = Buffer ( '' ) ;
@@ -166,7 +176,8 @@ module.exports = function createMiddleware(_dir, _options) {
166
176
const parsed = url . parse ( req . url ) ;
167
177
let pathname = null ;
168
178
let file = null ;
169
- let gzipped = null ;
179
+ let gzippedFile = null ;
180
+ let brotliFile = null ;
170
181
171
182
// Strip any null bytes from the url
172
183
// This was at one point necessary because of an old bug in url.parse
@@ -198,7 +209,9 @@ module.exports = function createMiddleware(_dir, _options) {
198
209
path . relative ( path . join ( '/' , baseDir ) , pathname )
199
210
)
200
211
) ;
201
- gzipped = `${ file } .gz` ;
212
+ // determine compressed forms if they were to exist
213
+ gzippedFile = `${ file } .gz` ;
214
+ brotliFile = `${ file } .br` ;
202
215
203
216
if ( serverHeader !== false ) {
204
217
// Set common headers.
@@ -229,7 +242,7 @@ module.exports = function createMiddleware(_dir, _options) {
229
242
230
243
function serve ( stat ) {
231
244
// Do a MIME lookup, fall back to octet-stream and handle gzip
232
- // special case.
245
+ // and brotli special case.
233
246
const defaultType = opts . contentType || 'application/octet-stream' ;
234
247
let contentType = mime . lookup ( file , defaultType ) ;
235
248
let charSet ;
@@ -238,19 +251,21 @@ module.exports = function createMiddleware(_dir, _options) {
238
251
const etag = generateEtag ( stat , weakEtags ) ;
239
252
let cacheControl = cache ;
240
253
let stream = null ;
241
-
242
254
if ( contentType ) {
243
255
charSet = mime . charsets . lookup ( contentType , 'utf-8' ) ;
244
256
if ( charSet ) {
245
257
contentType += `; charset=${ charSet } ` ;
246
258
}
247
259
}
248
260
249
- if ( file === gzipped ) { // is .gz picked up
261
+ if ( file === gzippedFile ) { // is .gz picked up
250
262
res . setHeader ( 'Content-Encoding' , 'gzip' ) ;
251
-
252
263
// strip gz ending and lookup mime type
253
264
contentType = mime . lookup ( path . basename ( file , '.gz' ) , defaultType ) ;
265
+ } else if ( file === brotliFile ) { // is .br picked up
266
+ res . setHeader ( 'Content-Encoding' , 'br' ) ;
267
+ // strip br ending and lookup mime type
268
+ contentType = mime . lookup ( path . basename ( file , '.br' ) , defaultType ) ;
254
269
}
255
270
256
271
if ( typeof cacheControl === 'function' ) {
@@ -401,13 +416,13 @@ module.exports = function createMiddleware(_dir, _options) {
401
416
} ) ;
402
417
}
403
418
404
- // Look for a gzipped file if this is turned on
405
- if ( opts . gzip && shouldCompress ( req ) ) {
406
- fs . stat ( gzipped , ( err , stat ) => {
419
+ // serve gzip file if exists and is valid
420
+ function tryServeWithGzip ( ) {
421
+ fs . stat ( gzippedFile , ( err , stat ) => {
407
422
if ( ! err && stat . isFile ( ) ) {
408
- hasGzipId12 ( gzipped , ( gzipErr , isGzip ) => {
423
+ hasGzipId12 ( gzippedFile , ( gzipErr , isGzip ) => {
409
424
if ( ! gzipErr && isGzip ) {
410
- file = gzipped ;
425
+ file = gzippedFile ;
411
426
serve ( stat ) ;
412
427
} else {
413
428
statFile ( ) ;
@@ -417,6 +432,29 @@ module.exports = function createMiddleware(_dir, _options) {
417
432
statFile ( ) ;
418
433
}
419
434
} ) ;
435
+ }
436
+
437
+ // serve brotli file if exists, otherwise try gzip
438
+ function tryServeWithBrotli ( shouldTryGzip ) {
439
+ fs . stat ( brotliFile , ( err , stat ) => {
440
+ if ( ! err && stat . isFile ( ) ) {
441
+ file = brotliFile ;
442
+ serve ( stat ) ;
443
+ } else if ( shouldTryGzip ) {
444
+ tryServeWithGzip ( ) ;
445
+ } else {
446
+ statFile ( ) ;
447
+ }
448
+ } ) ;
449
+ }
450
+
451
+ const shouldTryBrotli = opts . brotli && shouldCompressBrotli ( req ) ;
452
+ const shouldTryGzip = opts . gzip && shouldCompressGzip ( req ) ;
453
+ // always try brotli first, next try gzip, finally serve without compression
454
+ if ( shouldTryBrotli ) {
455
+ tryServeWithBrotli ( shouldTryGzip ) ;
456
+ } else if ( shouldTryGzip ) {
457
+ tryServeWithGzip ( ) ;
420
458
} else {
421
459
statFile ( ) ;
422
460
}
0 commit comments