Skip to content

Commit 1045a22

Browse files
jtmthfhaoxin
authored and
haoxin
committedApr 26, 2017
Added serving of brotli versions of files
closes #76
1 parent c7449f2 commit 1045a22

File tree

5 files changed

+100
-4
lines changed

5 files changed

+100
-4
lines changed
 

‎Readme.md

+1
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ $ npm install koa-send
2222
- `hidden` Allow transfer of hidden files. (defaults to `false`)
2323
- [`root`](#root-path) Root directory to restrict file access
2424
- `gzip` Try to serve the gzipped version of a file automatically when `gzip` is supported by a client and if the requested file with `.gz` extension exists. defaults to true.
25+
- `brotli` Try to serve the brotli version of a file automatically when `brotli` is supported by a client and if the requested file with `.br` extension exists. defaults to true.
2526
- `format` If not `false` (defaults to `true`), format the path to serve static file servers and not require a trailing slash for directories, so that you can do both `/directory` and `/directory/`
2627
- [`setHeaders`](#setheaders) Function to set custom headers on response.
2728
- `extensions` Try to match extensions from passed array to search for file when no extension is sufficed in URL. First found is served. (defaults to `false`)

‎index.js

+7-4
Original file line numberDiff line numberDiff line change
@@ -48,15 +48,14 @@ async function send(ctx, path, opts = {}) {
4848
const hidden = opts.hidden || false;
4949
const format = opts.format === false ? false : true;
5050
const extensions = Array.isArray(opts.extensions) ? opts.extensions : false;
51+
const brotli = opts.brotli === false ? false : true;
5152
const gzip = opts.gzip === false ? false : true;
5253
const setHeaders = opts.setHeaders;
5354

5455
if (setHeaders && typeof setHeaders !== 'function') {
5556
throw new TypeError('option setHeaders must be function')
5657
}
5758

58-
const encoding = ctx.acceptsEncodings('gzip', 'deflate', 'identity');
59-
6059
// normalize path
6160
path = decode(path);
6261

@@ -70,8 +69,12 @@ async function send(ctx, path, opts = {}) {
7069
// hidden file support, ignore
7170
if (!hidden && isHidden(root, path)) return;
7271

73-
// serve gzipped file when possible
74-
if (encoding === 'gzip' && gzip && (await fs.exists(path + '.gz'))) {
72+
// serve brotli file when possible otherwise gzipped file when possible
73+
if (ctx.acceptsEncodings('br', 'deflate', 'identity') === 'br' && brotli && (await fs.exists(path + '.br'))) {
74+
path = path + '.br';
75+
ctx.set('Content-Encoding', 'br');
76+
ctx.res.removeHeader('Content-Length');
77+
} else if (ctx.acceptsEncodings('gzip', 'deflate', 'identity') === 'gzip' && gzip && (await fs.exists(path + '.gz'))) {
7578
path = path + '.gz';
7679
ctx.set('Content-Encoding', 'gzip');
7780
ctx.res.removeHeader('Content-Length');

‎package.json

+1
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
"index.js"
1414
],
1515
"devDependencies": {
16+
"iltorb": "^1.2.1",
1617
"istanbul": "0",
1718
"koa": "2",
1819
"mocha": "3",

‎test/fixtures/gzip.json.br

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
��{ "name": "tobi" }

‎test/index.js

+90
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ const send = require('..');
44
const path = require('path');
55
const Koa = require('koa');
66
const assert = require('assert');
7+
const decompress = require('iltorb').decompress;
78

89
describe('send(ctx, file)', function(){
910
describe('with no .root', function(){
@@ -372,6 +373,95 @@ describe('send(ctx, file)', function(){
372373
})
373374
})
374375

376+
describe('or .br version when requested and if possible', function(){
377+
it('should return path', function(done){
378+
const app = new Koa();
379+
380+
app.use(async (ctx) => {
381+
await send(ctx, '/test/fixtures/gzip.json');
382+
});
383+
384+
request(app.listen())
385+
.get('/')
386+
.set('Accept-Encoding', 'deflate, identity')
387+
.expect('Content-Length', '18')
388+
.expect('{ "name": "tobi" }')
389+
.expect(200, done);
390+
})
391+
392+
it('should return .br path (brotli option defaults to true)', function(done){
393+
const app = new Koa();
394+
395+
app.use(async (ctx) => {
396+
await send(ctx, '/test/fixtures/gzip.json');
397+
});
398+
399+
request(app.listen())
400+
.get('/')
401+
.set('Accept-Encoding', 'br, deflate, identity')
402+
.expect('Content-Length', '22')
403+
.expect(200)
404+
.then(({body}) => {
405+
decompress(body, (err, output) => {
406+
assert.strictEqual(err, null);
407+
assert.deepStrictEqual(output.toString(), '{ "name": "tobi" }');
408+
done();
409+
});
410+
});
411+
})
412+
413+
it('should return .br path when brotli option is turned on', function(done){
414+
const app = new Koa();
415+
416+
app.use(async (ctx) => {
417+
await send(ctx, '/test/fixtures/gzip.json', { brotli: true });
418+
});
419+
420+
request(app.listen())
421+
.get('/')
422+
.set('Accept-Encoding', 'br, deflate, identity')
423+
.expect('Content-Length', '22')
424+
.expect(200)
425+
.then(({body}) => {
426+
decompress(body, (err, output) => {
427+
assert.strictEqual(err, null);
428+
assert.deepStrictEqual(output.toString(), '{ "name": "tobi" }');
429+
done();
430+
});
431+
});
432+
})
433+
434+
it('should not return .br path when brotli option is false', function(done){
435+
const app = new Koa();
436+
437+
app.use(async (ctx) => {
438+
await send(ctx, '/test/fixtures/gzip.json', { brotli: false });
439+
});
440+
441+
request(app.listen())
442+
.get('/')
443+
.set('Accept-Encoding', 'br, deflate, identity')
444+
.expect('Content-Length', '18')
445+
.expect('{ "name": "tobi" }')
446+
.expect(200, done);
447+
})
448+
449+
it('should return .gz path when brotli option is turned off', function(done){
450+
const app = new Koa();
451+
452+
app.use(async (ctx) => {
453+
await send(ctx, '/test/fixtures/gzip.json', { brotli: false });
454+
});
455+
456+
request(app.listen())
457+
.get('/')
458+
.set('Accept-Encoding', 'br, gzip, deflate, identity')
459+
.expect('Content-Length', '48')
460+
.expect('{ "name": "tobi" }')
461+
.expect(200, done);
462+
})
463+
})
464+
375465
describe('and max age is specified', function(){
376466
it('should set max-age in seconds', function(done){
377467
const app = new Koa();

0 commit comments

Comments
 (0)
Please sign in to comment.