Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Change resolved Accept-Encoding to honour server side order preferece #49

Closed
wants to merge 1 commit into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions HISTORY.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,8 @@
unreleased
==========

* Add configurable preferred sorting order for `Accept-Encoding`

0.6.1 / 2016-05-02
==================

Expand Down
14 changes: 12 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -168,19 +168,29 @@ You can check a working example at `examples/encoding.js`.

Returns the most preferred encoding from the client.

##### encoding(availableEncodings)
##### encoding(availableEncodings, options)

Returns the most preferred encoding from a list of available encodings.

##### encodings()

Returns an array of preferred encodings ordered by the client preference.

##### encodings(availableEncodings)
##### encodings(availableEncodings, options)

Returns an array of preferred encodings ordered by priority from a list of
available encodings.

The options has one available option. The default value if not specified is
```js
{ sortPreference: 'client' }
```

#### Sort options
`client`: sorts first by client quality level and then by client given order
`clientThenServer`: sorts first by client quality level and then by server given order
`server`: sorts by server given order

## See Also

The [accepts](https://npmjs.org/package/accepts#readme) module builds on
Expand Down
8 changes: 4 additions & 4 deletions index.js
Original file line number Diff line number Diff line change
Expand Up @@ -47,14 +47,14 @@ Negotiator.prototype.charsets = function charsets(available) {
return preferredCharsets(this.request.headers['accept-charset'], available);
};

Negotiator.prototype.encoding = function encoding(available) {
var set = this.encodings(available);
Negotiator.prototype.encoding = function encoding(available, options) {
var set = this.encodings(available, options);
return set && set[0];
};

Negotiator.prototype.encodings = function encodings(available) {
Negotiator.prototype.encodings = function encodings(available, options) {
var preferredEncodings = loadModule('encoding').preferredEncodings;
return preferredEncodings(this.request.headers['accept-encoding'], available);
return preferredEncodings(this.request.headers['accept-encoding'], available, options);
};

Negotiator.prototype.language = function language(available) {
Expand Down
39 changes: 32 additions & 7 deletions lib/encoding.js
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,11 @@ module.exports.preferredEncodings = preferredEncodings;
*/

var simpleEncodingRegExp = /^\s*([^\s;]+)\s*(?:;(.*))?$/;
var comparators = {
client: compareSpecsPreferClient,
clientThenServer: compareSpecsPreferClientThenServer,
server: compareSpecsPreferServer
};

/**
* Parse the Accept-Encoding header.
Expand Down Expand Up @@ -50,7 +55,7 @@ function parseAcceptEncoding(accept) {
*/
accepts[j++] = {
encoding: 'identity',
q: minQuality,
q: minQuality * 0.1,
i: i
};
}
Expand All @@ -74,8 +79,8 @@ function parseEncoding(str, i) {
var q = 1;
if (match[2]) {
var params = match[2].split(';');
for (var i = 0; i < params.length; i ++) {
var p = params[i].trim().split('=');
for (var j = 0; j < params.length; j ++) {
var p = params[j].trim().split('=');
if (p[0] === 'q') {
q = parseFloat(p[1]);
break;
Expand Down Expand Up @@ -135,23 +140,25 @@ function specify(encoding, spec, index) {
* @public
*/

function preferredEncodings(accept, provided) {
function preferredEncodings(accept, provided, options) {
var accepts = parseAcceptEncoding(accept || '');

if (!provided) {
// sorted list of all encodings
return accepts
.filter(isQuality)
.sort(compareSpecs)
.sort(compareSpecsPreferClient)
.map(getFullEncoding);
}

var priorities = provided.map(function getPriority(type, index) {
return getEncodingPriority(type, accepts, index);
});

var comparator = options && comparators[options.sortPreference] || compareSpecsPreferClient

// sorted list of accepted encodings
return priorities.filter(isQuality).sort(compareSpecs).map(function getEncoding(priority) {
return priorities.filter(isQuality).sort(comparator).map(function getEncoding(priority) {
return provided[priorities.indexOf(priority)];
});
}
Expand All @@ -161,10 +168,28 @@ function preferredEncodings(accept, provided) {
* @private
*/

function compareSpecs(a, b) {
function compareSpecsPreferClient(a, b) {
return (b.q - a.q) || (b.s - a.s) || (a.o - b.o) || (a.i - b.i) || 0;
}

/**
* Compare two specs.
* @private
*/

function compareSpecsPreferClientThenServer(a, b) {
return (b.q - a.q) || (a.i - b.i) || 0;
}

/**
* Compare two specs, ignoring client quality levels.
* @private
*/

function compareSpecsPreferServer(a, b) {
return (a.i - b.i) || 0;
}

/**
* Get full encoding string.
* @private
Expand Down
121 changes: 120 additions & 1 deletion test/encoding.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,10 @@
var assert = require('assert')
var Negotiator = require('..')

var clientPreference = { sortPreference: 'client'}
var serverPreference = { sortPreference: 'server'}
var clientThenServerPreference = { sortPreference: 'clientThenServer'}

describe('negotiator.encoding()', function () {
whenAcceptEncoding(undefined, function () {
it('should return identity', function () {
Expand Down Expand Up @@ -112,6 +116,12 @@ describe('negotiator.encoding(array)', function () {
assert.strictEqual(this.negotiator.encoding(['gzip']), 'gzip')
assert.strictEqual(this.negotiator.encoding(['compress', 'gzip']), 'gzip')
})

it('clientThenServerPreference: should return server-preferred encoding', function () {
assert.strictEqual(this.negotiator.encoding(['identity'], clientThenServerPreference), 'identity')
assert.strictEqual(this.negotiator.encoding(['gzip'], clientThenServerPreference), 'gzip')
assert.strictEqual(this.negotiator.encoding(['compress', 'gzip'], clientThenServerPreference), 'compress')
})
})

whenAcceptEncoding('*, gzip;q=0', function () {
Expand Down Expand Up @@ -206,6 +216,23 @@ describe('negotiator.encoding(array)', function () {
assert.strictEqual(this.negotiator.encoding(['compress', 'identity']), 'identity')
})
})

whenAcceptEncoding('gzip;q=0.9, sdhc, br;q=0.9', function () {
it('should return best server-preferred encoding of equal client-preferred encodings', function () {
assert.strictEqual(this.negotiator.encoding(['compress', 'br', 'gzip']), 'gzip')
assert.strictEqual(this.negotiator.encoding(['compress', 'gzip', 'br']), 'gzip')
})

it('should return best server-preferred encoding of equal client-preferred encodings', function () {
assert.strictEqual(this.negotiator.encoding(['compress', 'br', 'gzip'], clientThenServerPreference), 'br')
assert.strictEqual(this.negotiator.encoding(['compress', 'gzip', 'br'], clientThenServerPreference), 'gzip')
})

it('should return best server-preferred encoding of equal client-preferred encodings', function () {
assert.strictEqual(this.negotiator.encoding(['compress', 'br', 'gzip'], serverPreference), 'br')
assert.strictEqual(this.negotiator.encoding(['compress', 'gzip', 'br'], serverPreference), 'gzip')
})
})
})

describe('negotiator.encodings()', function () {
Expand Down Expand Up @@ -316,7 +343,13 @@ describe('negotiator.encodings(array)', function () {
it('should prefer gzip', function () {
assert.deepEqual(this.negotiator.encodings(['identity']), ['identity'])
assert.deepEqual(this.negotiator.encodings(['gzip']), ['gzip'])
assert.deepEqual(this.negotiator.encodings(['compress', 'gzip']), ['gzip', 'compress'])
assert.deepEqual(this.negotiator.encodings(['compress', 'deflate', 'gzip']), ['gzip', 'compress', 'deflate'])
})

it('clientThenServerPreference: should return server preferred encodings', function () {
assert.deepEqual(this.negotiator.encodings(['identity'], clientThenServerPreference), ['identity'])
assert.deepEqual(this.negotiator.encodings(['gzip'], clientThenServerPreference), ['gzip'])
assert.deepEqual(this.negotiator.encodings(['compress', 'deflate', 'gzip'], clientThenServerPreference), ['compress', 'deflate', 'gzip'])
})
})

Expand Down Expand Up @@ -403,6 +436,11 @@ describe('negotiator.encodings(array)', function () {
assert.deepEqual(this.negotiator.encodings(['deflate']), ['deflate'])
assert.deepEqual(this.negotiator.encodings(['deflate', 'gzip']), ['deflate', 'gzip'])
})

it('serverPreference: should ignore client quality levels', function () {
assert.deepEqual(this.negotiator.encodings(['deflate', 'gzip'], serverPreference), ['deflate', 'gzip'])
assert.deepEqual(this.negotiator.encodings(['gzip', 'deflate'], serverPreference), ['gzip', 'deflate'])
})
})

whenAcceptEncoding('gzip;q=0.8, identity;q=0.5, *;q=0.3', function () {
Expand All @@ -411,6 +449,87 @@ describe('negotiator.encodings(array)', function () {
assert.deepEqual(this.negotiator.encodings(['identity', 'gzip', 'compress']), ['gzip', 'identity', 'compress'])
})
})

whenAcceptEncoding('not;q=0, med1;q=0.9, med2;q=0.9, high, *;q=0.8', function () {
it('clientPreference', function () {
assert.deepEqual(this.negotiator.encodings(['high', 'not', 'med2', 'other', 'med1'], clientPreference), ['high', 'med1', 'med2', 'other'])
assert.deepEqual(this.negotiator.encodings(['high', 'not', 'med1', 'other', 'med2'], clientPreference), ['high', 'med1', 'med2', 'other'])
})

it('clientThenServerPreference', function () {
assert.deepEqual(this.negotiator.encodings(['high', 'not', 'med2', 'other', 'med1'], clientThenServerPreference), ['high', 'med2', 'med1', 'other'])
assert.deepEqual(this.negotiator.encodings(['high', 'not', 'med1', 'other', 'med2'], clientThenServerPreference), ['high', 'med1', 'med2', 'other'])
})

it('serverPreference', function () {
assert.deepEqual(this.negotiator.encodings(['high', 'not', 'med2', 'other', 'med1'], serverPreference), ['high', 'med2', 'other', 'med1'])
assert.deepEqual(this.negotiator.encodings(['high', 'not', 'med1', 'other', 'med2'], serverPreference), ['high', 'med1', 'other', 'med2'])
})
})

whenAcceptEncoding('not;q=0, med1;q=0.9, med2;q=0.9, high, identity;q=0.9', function () {
it('clientPreference', function () {
assert.deepEqual(this.negotiator.encodings(['high', 'not', 'med2', 'other', 'med1', 'identity'], clientPreference), ['high', 'med1', 'med2', 'identity'])
assert.deepEqual(this.negotiator.encodings(['high', 'not', 'med1', 'other', 'med2', 'identity'], clientPreference), ['high', 'med1', 'med2', 'identity'])
})

it('clientThenServerPreference', function () {
assert.deepEqual(this.negotiator.encodings(['high', 'not', 'med2', 'other', 'med1', 'identity'], clientThenServerPreference), ['high', 'med2', 'med1', 'identity'])
assert.deepEqual(this.negotiator.encodings(['high', 'not', 'med1', 'other', 'med2', 'identity'], clientThenServerPreference), ['high', 'med1', 'med2', 'identity'])
})

it('serverPreference', function () {
assert.deepEqual(this.negotiator.encodings(['high', 'not', 'med2', 'other', 'med1', 'identity'], serverPreference), ['high', 'med2', 'med1', 'identity'])
assert.deepEqual(this.negotiator.encodings(['high', 'not', 'med1', 'other', 'med2', 'identity'], serverPreference), ['high', 'med1', 'med2', 'identity'])
})
})

whenAcceptEncoding('c;q=0.9, b;q=0.89, a;q=0.9, d;q=0.91, e;q=0.9', function () {
it('clientPreference', function () {
assert.deepEqual(this.negotiator.encodings(['b','c','d','e'], clientPreference), ['d', 'c', 'e', 'b']);
assert.deepEqual(this.negotiator.encodings(['c','d','e','b'], clientPreference), ['d', 'c', 'e', 'b']);
assert.deepEqual(this.negotiator.encodings(['d','e','b','c'], clientPreference), ['d', 'c', 'e', 'b']);
assert.deepEqual(this.negotiator.encodings(['e','b','c','d'], clientPreference), ['d', 'c', 'e', 'b']);

assert.deepEqual(this.negotiator.encodings(['a','c','e'], clientPreference), ['c', 'a', 'e']);
assert.deepEqual(this.negotiator.encodings(['c','e','a'], clientPreference), ['c', 'a', 'e']);
assert.deepEqual(this.negotiator.encodings(['e','a','c'], clientPreference), ['c', 'a', 'e']);

assert.deepEqual(this.negotiator.encodings(['identity','e','f'], clientPreference), ['e','identity']);
assert.deepEqual(this.negotiator.encodings(['f','identity','e'], clientPreference), ['e','identity']);
assert.deepEqual(this.negotiator.encodings(['e','f','identity'], clientPreference), ['e','identity']);
})

it('clientThenServerPreference', function () {
assert.deepEqual(this.negotiator.encodings(['b','c','d','e'], clientThenServerPreference), ['d', 'c', 'e', 'b']);
assert.deepEqual(this.negotiator.encodings(['c','d','e','b'], clientThenServerPreference), ['d', 'c', 'e', 'b']);
assert.deepEqual(this.negotiator.encodings(['d','e','b','c'], clientThenServerPreference), ['d', 'e', 'c', 'b']);
assert.deepEqual(this.negotiator.encodings(['e','b','c','d'], clientThenServerPreference), ['d', 'e', 'c', 'b']);

assert.deepEqual(this.negotiator.encodings(['a','c','e'], clientThenServerPreference), ['a', 'c', 'e']);
assert.deepEqual(this.negotiator.encodings(['c','e','a'], clientThenServerPreference), ['c', 'e', 'a']);
assert.deepEqual(this.negotiator.encodings(['e','a','c'], clientThenServerPreference), ['e', 'a', 'c']);

assert.deepEqual(this.negotiator.encodings(['identity','e','f'], clientThenServerPreference), ['e','identity']);
assert.deepEqual(this.negotiator.encodings(['f','identity','e'], clientThenServerPreference), ['e','identity']);
assert.deepEqual(this.negotiator.encodings(['e','f','identity'], clientThenServerPreference), ['e','identity']);
})

it('serverPreference', function () {
assert.deepEqual(this.negotiator.encodings(['b','c','d','e'], serverPreference), ['b','c','d','e']);
assert.deepEqual(this.negotiator.encodings(['c','d','e','b'], serverPreference), ['c','d','e','b']);
assert.deepEqual(this.negotiator.encodings(['d','e','b','c'], serverPreference), ['d','e','b','c']);
assert.deepEqual(this.negotiator.encodings(['e','b','c','d'], serverPreference), ['e','b','c','d']);

assert.deepEqual(this.negotiator.encodings(['a','c','e'], serverPreference), ['a', 'c', 'e']);
assert.deepEqual(this.negotiator.encodings(['c','e','a'], serverPreference), ['c', 'e', 'a']);
assert.deepEqual(this.negotiator.encodings(['e','a','c'], serverPreference), ['e', 'a', 'c']);

assert.deepEqual(this.negotiator.encodings(['identity','e','f'], serverPreference), ['identity', 'e']);
assert.deepEqual(this.negotiator.encodings(['f','identity','e'], serverPreference), ['identity', 'e']);
assert.deepEqual(this.negotiator.encodings(['e','f','identity'], serverPreference), ['e','identity']);
})
})
})

function createRequest(headers) {
Expand Down