Skip to content

Commit

Permalink
Support Brotli
Browse files Browse the repository at this point in the history
Closes koajs#77
  • Loading branch information
ryota-ka committed Oct 3, 2020
1 parent 309e3f7 commit 17b94f9
Show file tree
Hide file tree
Showing 3 changed files with 132 additions and 5 deletions.
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,8 @@ app.use(staticCache(path.join(__dirname, 'public'), {
- `options.buffer` (bool) - store the files in memory instead of streaming from the filesystem on each request.
- `options.gzip` (bool) - when request's accept-encoding include gzip, files will compressed by gzip.
- `options.usePrecompiledGzip` (bool) - try use gzip files, loaded from disk, like nginx gzip_static
- `options.brotli` (bool) - when request's accept-encoding include br, files will compressed by brotli.
- `options.usePrecompiledBrotli` (bool) - try use brotli files, loaded from disk
- `options.alias` (obj) - object map of aliases. See below.
- `options.prefix` (str) - the url prefix you wish to add, default to `''`.
- `options.dynamic` (bool) - dynamic load file which not cached on initialization.
Expand Down
34 changes: 29 additions & 5 deletions index.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ var mime = require('mime-types')
var compressible = require('compressible')
var readDir = require('fs-readdir-recursive')
var debug = require('debug')('koa-static-cache')
var util = require('util');

module.exports = function staticCache(dir, options, files) {
if (typeof dir === 'object') {
Expand All @@ -20,6 +21,7 @@ module.exports = function staticCache(dir, options, files) {
files = new FileManager(files || options.files)
dir = dir || options.dir || process.cwd()
dir = path.normalize(dir)
var enableBrotli = !!options.brotli
var enableGzip = !!options.gzip
var filePrefix = path.normalize(options.prefix.replace(/^\//, ''))

Expand Down Expand Up @@ -79,7 +81,7 @@ module.exports = function staticCache(dir, options, files) {

ctx.status = 200

if (enableGzip) ctx.vary('Accept-Encoding')
if (enableBrotli || enableGzip) ctx.vary('Accept-Encoding')

if (!file.buffer) {
var stats = await fs.stat(file.path)
Expand All @@ -102,10 +104,14 @@ module.exports = function staticCache(dir, options, files) {

if (ctx.method === 'HEAD') return

var acceptBrotli = ctx.acceptsEncodings('br') === 'br'
var acceptGzip = ctx.acceptsEncodings('gzip') === 'gzip'

if (file.zipBuffer) {
if (acceptGzip) {
if (acceptBrotli) {
ctx.set('content-encoding', 'br')
ctx.body = file.brBuffer
} else if (acceptGzip) {
ctx.set('content-encoding', 'gzip')
ctx.body = file.zipBuffer
} else {
Expand All @@ -114,13 +120,27 @@ module.exports = function staticCache(dir, options, files) {
return
}

var shouldBrotli = enableBrotli
&& file.length > 1024
&& acceptBrotli
&& compressible(file.type)
var shouldGzip = enableGzip
&& file.length > 1024
&& acceptGzip
&& compressible(file.type)

if (file.buffer) {
if (shouldGzip) {
if (shouldBrotli) {

var brFile = files.get(filename + '.br')
if (options.usePrecompiledBrotli && brFile && brFile.buffer) { // if .br file already read from disk
file.brBuffer = brFile.buffer
} else {
file.brBuffer = await util.promisify(zlib.brotliCompress)(file.buffer)
}
ctx.set('content-encoding', 'br')
ctx.body = file.brBuffer
} else if (shouldGzip) {

var gzFile = files.get(filename + '.gz')
if (options.usePrecompiledGzip && gzFile && gzFile.buffer) { // if .gz file already read from disk
Expand Down Expand Up @@ -148,8 +168,12 @@ module.exports = function staticCache(dir, options, files) {
}

ctx.body = stream
// enable gzip will remove content length
if (shouldGzip) {
// enable brotli/gzip will remove content length
if (shouldBrotli) {
ctx.remove('content-length')
ctx.set('content-encoding', 'br')
ctx.body = stream.pipe(zlib.createBrotliCompress())
} else if (shouldGzip) {
ctx.remove('content-length')
ctx.set('content-encoding', 'gzip')
ctx.body = stream.pipe(zlib.createGzip())
Expand Down
101 changes: 101 additions & 0 deletions test/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,26 @@ app5.use(staticCache({
}))
var server5 = http.createServer(app5.callback())

var app6 = new Koa()
app6.use(staticCache(path.join(__dirname, '..'), {
buffer: true,
brotli: true,
filter(file) {
return !file.includes('node_modules')
}
}))
var server6 = http.createServer(app6.callback())

var app7 = new Koa()
var files7 = {}
app7.use(staticCache(path.join(__dirname, '..'), {
brotli: true,
filter(file) {
return !file.includes('node_modules')
}
}, files7))
var server7 = http.createServer(app7.callback())

describe('Static Cache', function () {

it('should dir priority than options.dir', function (done) {
Expand Down Expand Up @@ -601,4 +621,85 @@ describe('Static Cache', function () {
.expect(404)
.end(done)
})

it('should serve files with brotli buffer', function (done) {
var index = fs.readFileSync('index.js')
zlib.brotliCompress(index, function (err, content) {
request(server6)
.get('/index.js')
.set('Accept-Encoding', 'br')
.responseType('arraybuffer')
.expect(200)
.expect('Cache-Control', 'public, max-age=0')
.expect('Content-Encoding', 'br')
.expect('Content-Type', /javascript/)
.expect('Content-Length', String(content.length))
.expect('Vary', 'Accept-Encoding')
.expect(function (res) {
return res.body.toString('hex') === content.toString('hex')
})
.end(function (err, res) {
if (err)
return done(err)
res.should.have.header('Content-Length')
res.should.have.header('Last-Modified')
res.should.have.header('ETag')

etag = res.headers.etag

done()
})
})
})

it('should not serve files with brotli buffer when accept encoding not include br',
function (done) {
var index = fs.readFileSync('index.js')
request(server6)
.get('/index.js')
.set('Accept-Encoding', '')
.expect(200)
.expect('Cache-Control', 'public, max-age=0')
.expect('Content-Type', /javascript/)
.expect('Content-Length', String(index.length))
.expect('Vary', 'Accept-Encoding')
.expect(index.toString())
.end(function (err, res) {
if (err)
return done(err)
res.should.not.have.header('Content-Encoding')
res.should.have.header('Content-Length')
res.should.have.header('Last-Modified')
res.should.have.header('ETag')
done()
})
})

it('should serve files with brotli stream', function (done) {
var index = fs.readFileSync('index.js')
zlib.brotliCompress(index, function (err, content) {
request(server7)
.get('/index.js')
.set('Accept-Encoding', 'br')
.expect(200)
.expect('Cache-Control', 'public, max-age=0')
.expect('Content-Encoding', 'br')
.expect('Content-Type', /javascript/)
.expect('Vary', 'Accept-Encoding')
.expect(function (res) {
return res.body.toString('hex') === content.toString('hex')
})
.end(function (err, res) {
if (err)
return done(err)
res.should.not.have.header('Content-Length')
res.should.have.header('Last-Modified')
res.should.have.header('ETag')

etag = res.headers.etag

done()
})
})
})
})

0 comments on commit 17b94f9

Please sign in to comment.