express-static-gzip
Advanced tools
Comparing version
10
index.js
let serveStatic = require('serve-static'); | ||
let parseOptions = require('./util/options').parseOptions; | ||
let sanitizeOptions = require('./util/options').sanitizeOptions; | ||
let findEncoding = require('./util/encoding-selection').findEncoding; | ||
@@ -13,3 +13,3 @@ let mime = serveStatic.mime; | ||
* @param {string} rootFolder: folder to staticly serve files from | ||
* @param {{enableBrotli?:boolean, customCompressions?:[{encodingName:string,fileExtension:string}], index?: boolean}} options: options to change module behaviour | ||
* @param {{enableBrotli?:boolean, customCompressions?:{encodingName:string,fileExtension:string}[], orderPreference: string[], index?: boolean}} options: options to change module behaviour | ||
* @returns express middleware function | ||
@@ -19,4 +19,4 @@ */ | ||
// strip away unnecessary options | ||
let opts = parseOptions(options); | ||
let opts = sanitizeOptions(options); | ||
//create a express.static middleware to handle serving files | ||
@@ -48,3 +48,3 @@ let defaultStatic = serveStatic(rootFolder, options); | ||
//use the first matching compression to serve a compresed file | ||
var compression = findEncoding(acceptEncoding, matchedFile.compressions); | ||
var compression = findEncoding(acceptEncoding, matchedFile.compressions, opts.orderPreference); | ||
if (compression) { | ||
@@ -51,0 +51,0 @@ convertToCompressedRequest(req, res, compression); |
{ | ||
"name": "express-static-gzip", | ||
"version": "1.0.0", | ||
"version": "1.1.0", | ||
"description": "simple wrapper on top of express.static, that allows serving pre-gziped files", | ||
@@ -5,0 +5,0 @@ "main": "index.js", |
@@ -64,2 +64,3 @@ # express-static-gzip | ||
If not set to false, any request to '/' or 'somepath/' will be answered with the file '/index.html' or 'somepath/index.html' in an accepted compression | ||
* **`indexFromEmptyFile`** (**deprecated**, see `index` option) | ||
@@ -71,3 +72,5 @@ | ||
* **`orderPreference`**: string[] | ||
This options allows overwriting the client's requested encoding preference (see [MDN](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Accept-Encoding)) with a server side preference. Any encoding listed in `orderPreference` will be used first (if supported by the client) before falling back to the client's supported encodings. The order of entries in `orderPreference` is taken into account. | ||
@@ -84,2 +87,3 @@ # Behavior warning | ||
Because this middleware was developed for a static production server use case, to maximize performance, it is designed to look up and cache the compressed files corresponding to uncompressed file names on startup. This means that it will not be aware of compressed files being added or removed later on. | ||
@@ -86,0 +90,0 @@ # Example |
@@ -59,3 +59,3 @@ const expect = require('chai').expect; | ||
it('should not serve brotli', function () { | ||
it('should not serve brotli if not enabled', function () { | ||
setupServer(); | ||
@@ -137,2 +137,28 @@ | ||
it('should use server\'s prefered encoding', function () { | ||
setupServer({ | ||
customCompressions: [{ encodingName: 'deflate', fileExtension: 'zz' }], | ||
enableBrotli: true, | ||
orderPreference: ['br'] | ||
}); | ||
return requestFile('/index.html', { 'accept-encoding': 'gzip, deflate, br' }).then(resp => { | ||
expect(resp.statusCode).to.equal(200); | ||
expect(resp.body).to.equal('index.html.br'); | ||
}); | ||
}); | ||
it('should use client\'s prefered encoding, when server\'s not available', function () { | ||
setupServer({ | ||
customCompressions: [{ encodingName: 'deflate', fileExtension: 'zz' }], | ||
enableBrotli: true, | ||
orderPreference: ['br'] | ||
}); | ||
return requestFile('/index.html', { 'accept-encoding': 'gzip, deflate' }).then(resp => { | ||
expect(resp.statusCode).to.equal(200); | ||
expect(resp.body).to.equal('index.html.gz'); | ||
}); | ||
}); | ||
/** | ||
@@ -139,0 +165,0 @@ * |
@@ -6,3 +6,4 @@ const expect = require('chai').expect; | ||
const GZIP = { encodingName: "gzip", fileExtension: 'gz' }; | ||
const BROTLI = { encodingName: "brotli", fileExtension: 'br' }; | ||
const BROTLI = { encodingName: "br", fileExtension: 'br' }; | ||
const DEFLATE = { encodingName: "deflate", fileExtension: 'deflate' }; | ||
@@ -25,3 +26,3 @@ it('should handle empty accepted-encoding', function () { | ||
it('should select by order', function () { | ||
const result = findEncoding('gzip, brotli', [GZIP, BROTLI]); | ||
const result = findEncoding('gzip, br', [GZIP, BROTLI]); | ||
expect(result).to.be.deep.equal(GZIP); | ||
@@ -31,3 +32,3 @@ }); | ||
it('should select by different order', function () { | ||
const result = findEncoding('brotli, gzip', [GZIP, BROTLI]); | ||
const result = findEncoding('br, gzip', [GZIP, BROTLI]); | ||
expect(result).to.be.deep.equal(BROTLI); | ||
@@ -37,3 +38,3 @@ }); | ||
it('should select by quality', function () { | ||
const result = findEncoding('brotli;q=0.5, gzip;q=1.0', [GZIP, BROTLI]); | ||
const result = findEncoding('br;q=0.5, gzip;q=1.0', [GZIP, BROTLI]); | ||
expect(result).to.be.deep.equal(GZIP); | ||
@@ -43,3 +44,3 @@ }); | ||
it('should handle strange formating', function () { | ||
const result = findEncoding('brotli ; q=0.5 , gzip ; q=1.0', [GZIP, BROTLI]); | ||
const result = findEncoding('br ; q=0.5 , gzip ; q=1.0', [GZIP, BROTLI]); | ||
expect(result).to.be.deep.equal(GZIP); | ||
@@ -49,5 +50,25 @@ }); | ||
it('should handle wildcard', function () { | ||
const result = findEncoding('brotli ; q=0.5 , * ; q=1.0', [GZIP]); | ||
const result = findEncoding('br ; q=0.5 , * ; q=1.0', [GZIP]); | ||
expect(result).to.be.deep.equal(GZIP); | ||
}); | ||
it('should favour server preference over client order', function () { | ||
const result = findEncoding('gzip, deflate, br', [GZIP, BROTLI, DEFLATE], ['br']); | ||
expect(result).to.be.deep.equal(BROTLI); | ||
}); | ||
it('should work with multiple server preferences', function () { | ||
const result = findEncoding('deflate, gzip', [GZIP, BROTLI, DEFLATE], ['br', 'gzip']); | ||
expect(result).to.be.deep.equal(GZIP); | ||
}); | ||
it('should use client order, when server preference not available', function () { | ||
const result = findEncoding('deflate, gzip', [GZIP, BROTLI, DEFLATE], ['br']); | ||
expect(result).to.be.deep.equal(DEFLATE); | ||
}); | ||
it('should use server preference with quality', function () { | ||
const result = findEncoding('gzip; q=0.6, deflate; q=1, br;q=0.5', [GZIP, BROTLI, DEFLATE], ['br']); | ||
expect(result).to.be.deep.equal(BROTLI); | ||
}); | ||
}); |
const expect = require('chai').expect; | ||
const { parseOptions } = require('../util/options'); | ||
const { sanitizeOptions } = require('../util/options'); | ||
describe('option', function () { | ||
it('should handle no options provided', function () { | ||
const res = parseOptions(); | ||
const res = sanitizeOptions(); | ||
expect(res).to.deep.equal({ index: true }); | ||
@@ -11,6 +11,6 @@ }); | ||
it('should parse index options', function () { | ||
let res = parseOptions({ index: true }); | ||
let res = sanitizeOptions({ index: true }); | ||
expect(res).to.deep.equal({ index: true }); | ||
res = parseOptions({ index: false }); | ||
res = sanitizeOptions({ index: false }); | ||
expect(res).to.deep.equal({ index: false }); | ||
@@ -20,6 +20,6 @@ }); | ||
it('should parse indexFromEmptyFile options', function () { | ||
let res = parseOptions({ indexFromEmptyFile: true }); | ||
let res = sanitizeOptions({ indexFromEmptyFile: true }); | ||
expect(res).to.deep.equal({ index: true }); | ||
res = parseOptions({ indexFromEmptyFile: false }); | ||
res = sanitizeOptions({ indexFromEmptyFile: false }); | ||
expect(res).to.deep.equal({ index: false }); | ||
@@ -29,6 +29,6 @@ }); | ||
it('should parse enableBrotli option', function () { | ||
let res = parseOptions({ enableBrotli: true }); | ||
let res = sanitizeOptions({ enableBrotli: true }); | ||
expect(res).to.deep.equal({ enableBrotli: true, index: true }); | ||
res = parseOptions({ enableBrotli: 'true' }); | ||
res = sanitizeOptions({ enableBrotli: 'true' }); | ||
expect(res).to.deep.equal({ enableBrotli: true, index: true }); | ||
@@ -39,3 +39,3 @@ }); | ||
const compression = { encodingName: 'brotli', fileExtension: 'br' }; | ||
const res = parseOptions({ customCompressions: [compression] }); | ||
const res = sanitizeOptions({ customCompressions: [compression] }); | ||
expect(res).to.deep.equal({ customCompressions: [compression], index: true }); | ||
@@ -45,3 +45,3 @@ }); | ||
it('should parse empty customCompressions', function () { | ||
const res = parseOptions({ customCompressions: [] }); | ||
const res = sanitizeOptions({ customCompressions: [] }); | ||
expect(res).to.deep.equal({ customCompressions: [], index: true }); | ||
@@ -51,3 +51,3 @@ }); | ||
it('should ignore bad customCompressions', function () { | ||
const res = parseOptions({ customCompressions: true }); | ||
const res = sanitizeOptions({ customCompressions: true }); | ||
expect(res).to.deep.equal({ index: true }); | ||
@@ -57,5 +57,10 @@ }); | ||
it('should strip additional options', function () { | ||
const res = parseOptions({ index: true, test: 'value' }); | ||
const res = sanitizeOptions({ index: true, test: 'value' }); | ||
expect(res).to.deep.equal({ index: true }); | ||
}); | ||
it('should parse orderPreference', function () { | ||
const res = sanitizeOptions({ orderPreference: ['br'] }); | ||
expect(res).to.deep.equal({ index: true, orderPreference: ['br'] }); | ||
}); | ||
}); |
@@ -7,6 +7,8 @@ // see https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Accept-Encoding | ||
* @param {{encodingName: string, fileExtension: string}[]} availableCompressions | ||
* @param {{string[]} preference | ||
*/ | ||
function findEncoding(acceptEncoding, availableCompressions) { | ||
function findEncoding(acceptEncoding, availableCompressions, preference) { | ||
if (acceptEncoding) { | ||
let sortedEncodingList = parseEncoding(acceptEncoding); | ||
sortedEncodingList = takePreferenceIntoAccount(sortedEncodingList, preference); | ||
return findFirstMatchingCompression(sortedEncodingList, availableCompressions); | ||
@@ -21,3 +23,3 @@ } | ||
for (let i = 0; i < availableCompressions.length; i++) { | ||
if (encoding.name === '*' || encoding.name === availableCompressions[i].encodingName) { | ||
if (encoding === '*' || encoding === availableCompressions[i].encodingName) { | ||
return availableCompressions[i]; | ||
@@ -32,2 +34,25 @@ } | ||
* | ||
* @param {string[]} sortedEncodingList | ||
* @param {string[]} preferences | ||
*/ | ||
function takePreferenceIntoAccount(sortedEncodingList, preferences) { | ||
if (!preferences || preferences.length === 0) { | ||
return sortedEncodingList; | ||
} | ||
for (let i = preferences.length; i >= 0; i--) { | ||
let pref = preferences[i]; | ||
let matchIdx = sortedEncodingList.indexOf(pref); | ||
if (matchIdx >= 0) { | ||
sortedEncodingList.splice(matchIdx, 1); | ||
sortedEncodingList.splice(0, 0, pref); | ||
} | ||
} | ||
return sortedEncodingList; | ||
} | ||
/** | ||
* | ||
* @param {string} acceptedEncoding | ||
@@ -38,3 +63,4 @@ */ | ||
.map(encoding => parseQuality(encoding)) | ||
.sort((encodingA, encodingB) => encodingB.q - encodingA.q); | ||
.sort((encodingA, encodingB) => encodingB.q - encodingA.q) | ||
.map(encoding => encoding.name); | ||
} | ||
@@ -41,0 +67,0 @@ |
@@ -7,3 +7,3 @@ | ||
*/ | ||
function parseOptions(userOptions) { | ||
function sanitizeOptions(userOptions) { | ||
userOptions = userOptions || {}; | ||
@@ -28,2 +28,6 @@ | ||
if (typeof (userOptions.orderPreference) === "object" ) { | ||
options.orderPreference = userOptions.orderPreference; | ||
} | ||
return options; | ||
@@ -43,3 +47,3 @@ } | ||
module.exports = { | ||
parseOptions: parseOptions | ||
sanitizeOptions: sanitizeOptions | ||
}; |
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
Found 1 instance in 1 package
29955
14.95%553
14.02%106
3.92%0
-100%