Skip to content

Commit 32ecd8f

Browse files
author
Mikko Tiihonen
committed
Allow configuring how the set of preferred encodings is sorted.
Options: client: use client quality levels and client order for equal quality levels clientThenServer: use client quality levels and server order for equal quality levels server: use server order The 'client' preferred order is the default to match with previous implementation.
1 parent 751c381 commit 32ecd8f

File tree

5 files changed

+174
-15
lines changed

5 files changed

+174
-15
lines changed

HISTORY.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,8 @@
1+
unreleased
2+
==========
3+
4+
* Add configurable preferred sorting order for `Accept-Encoding`
5+
16
0.6.1 / 2016-05-02
27
==================
38

README.md

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -168,19 +168,29 @@ You can check a working example at `examples/encoding.js`.
168168

169169
Returns the most preferred encoding from the client.
170170

171-
##### encoding(availableEncodings)
171+
##### encoding(availableEncodings, options)
172172

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

175175
##### encodings()
176176

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

179-
##### encodings(availableEncodings)
179+
##### encodings(availableEncodings, options)
180180

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

184+
The options has one available option. The default value if not specified is
185+
```js
186+
{ sortPreference: 'client' }
187+
```
188+
189+
#### Sort options
190+
`client`: sorts first by client quality level and then by client given order
191+
`clientThenServer`: sorts first by client quality level and then by server given order
192+
`server`: sorts by server given order
193+
184194
## See Also
185195

186196
The [accepts](https://npmjs.org/package/accepts#readme) module builds on

index.js

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -47,14 +47,14 @@ Negotiator.prototype.charsets = function charsets(available) {
4747
return preferredCharsets(this.request.headers['accept-charset'], available);
4848
};
4949

50-
Negotiator.prototype.encoding = function encoding(available) {
51-
var set = this.encodings(available);
50+
Negotiator.prototype.encoding = function encoding(available, options) {
51+
var set = this.encodings(available, options);
5252
return set && set[0];
5353
};
5454

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

6060
Negotiator.prototype.language = function language(available) {

lib/encoding.js

Lines changed: 33 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,11 @@ module.exports.preferredEncodings = preferredEncodings;
2222
*/
2323

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

2631
/**
2732
* Parse the Accept-Encoding header.
@@ -50,7 +55,7 @@ function parseAcceptEncoding(accept) {
5055
*/
5156
accepts[j++] = {
5257
encoding: 'identity',
53-
q: minQuality,
58+
q: minQuality * 0.1,
5459
i: i
5560
};
5661
}
@@ -74,8 +79,8 @@ function parseEncoding(str, i) {
7479
var q = 1;
7580
if (match[2]) {
7681
var params = match[2].split(';');
77-
for (var i = 0; i < params.length; i ++) {
78-
var p = params[i].trim().split('=');
82+
for (var j = 0; j < params.length; j ++) {
83+
var p = params[j].trim().split('=');
7984
if (p[0] === 'q') {
8085
q = parseFloat(p[1]);
8186
break;
@@ -135,23 +140,25 @@ function specify(encoding, spec, index) {
135140
* @public
136141
*/
137142

138-
function preferredEncodings(accept, provided) {
143+
function preferredEncodings(accept, provided, options) {
139144
var accepts = parseAcceptEncoding(accept || '');
140145

141146
if (!provided) {
142147
// sorted list of all encodings
143148
return accepts
144149
.filter(isQuality)
145-
.sort(compareSpecs)
150+
.sort(compareSpecsPreferClient)
146151
.map(getFullEncoding);
147152
}
148153

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

158+
var comparator = options && comparators[options.sortPreference] || compareSpecsPreferClient
159+
153160
// sorted list of accepted encodings
154-
return priorities.filter(isQuality).sort(compareSpecs).map(function getEncoding(priority) {
161+
return priorities.filter(isQuality).sort(comparator).map(function getEncoding(priority) {
155162
return provided[priorities.indexOf(priority)];
156163
});
157164
}
@@ -161,8 +168,26 @@ function preferredEncodings(accept, provided) {
161168
* @private
162169
*/
163170

164-
function compareSpecs(a, b) {
165-
return (b.q - a.q) || (b.s - a.s) || (a.o - b.o) || (a.i - b.i) || 0;
171+
function compareSpecsPreferClient(a, b) {
172+
return (b.q - a.q) || (b.s - a.s) || (a.o - b.o) || 0;
173+
}
174+
175+
/**
176+
* Compare two specs.
177+
* @private
178+
*/
179+
180+
function compareSpecsPreferClientThenServer(a, b) {
181+
return (b.q - a.q) || (a.i - b.i) || 0;
182+
}
183+
184+
/**
185+
* Compare two specs, ignoring client quality levels.
186+
* @private
187+
*/
188+
189+
function compareSpecsPreferServer(a, b) {
190+
return (a.i - b.i) || 0;
166191
}
167192

168193
/**

test/encoding.js

Lines changed: 120 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,10 @@
22
var assert = require('assert')
33
var Negotiator = require('..')
44

5+
var clientPreference = { sortPreference: 'client'}
6+
var serverPreference = { sortPreference: 'server'}
7+
var clientThenServerPreference = { sortPreference: 'clientThenServer'}
8+
59
describe('negotiator.encoding()', function () {
610
whenAcceptEncoding(undefined, function () {
711
it('should return identity', function () {
@@ -112,6 +116,12 @@ describe('negotiator.encoding(array)', function () {
112116
assert.strictEqual(this.negotiator.encoding(['gzip']), 'gzip')
113117
assert.strictEqual(this.negotiator.encoding(['compress', 'gzip']), 'gzip')
114118
})
119+
120+
it('clientThenServerPreference: should return server-preferred encoding', function () {
121+
assert.strictEqual(this.negotiator.encoding(['identity'], clientThenServerPreference), 'identity')
122+
assert.strictEqual(this.negotiator.encoding(['gzip'], clientThenServerPreference), 'gzip')
123+
assert.strictEqual(this.negotiator.encoding(['compress', 'gzip'], clientThenServerPreference), 'compress')
124+
})
115125
})
116126

117127
whenAcceptEncoding('*, gzip;q=0', function () {
@@ -206,6 +216,23 @@ describe('negotiator.encoding(array)', function () {
206216
assert.strictEqual(this.negotiator.encoding(['compress', 'identity']), 'identity')
207217
})
208218
})
219+
220+
whenAcceptEncoding('gzip;q=0.9, sdhc, br;q=0.9', function () {
221+
it('should return best server-preferred encoding of equal client-preferred encodings', function () {
222+
assert.strictEqual(this.negotiator.encoding(['compress', 'br', 'gzip']), 'gzip')
223+
assert.strictEqual(this.negotiator.encoding(['compress', 'gzip', 'br']), 'gzip')
224+
})
225+
226+
it('should return best server-preferred encoding of equal client-preferred encodings', function () {
227+
assert.strictEqual(this.negotiator.encoding(['compress', 'br', 'gzip'], clientThenServerPreference), 'br')
228+
assert.strictEqual(this.negotiator.encoding(['compress', 'gzip', 'br'], clientThenServerPreference), 'gzip')
229+
})
230+
231+
it('should return best server-preferred encoding of equal client-preferred encodings', function () {
232+
assert.strictEqual(this.negotiator.encoding(['compress', 'br', 'gzip'], serverPreference), 'br')
233+
assert.strictEqual(this.negotiator.encoding(['compress', 'gzip', 'br'], serverPreference), 'gzip')
234+
})
235+
})
209236
})
210237

211238
describe('negotiator.encodings()', function () {
@@ -316,7 +343,13 @@ describe('negotiator.encodings(array)', function () {
316343
it('should prefer gzip', function () {
317344
assert.deepEqual(this.negotiator.encodings(['identity']), ['identity'])
318345
assert.deepEqual(this.negotiator.encodings(['gzip']), ['gzip'])
319-
assert.deepEqual(this.negotiator.encodings(['compress', 'gzip']), ['gzip', 'compress'])
346+
assert.deepEqual(this.negotiator.encodings(['compress', 'deflate', 'gzip']), ['gzip', 'compress', 'deflate'])
347+
})
348+
349+
it('clientThenServerPreference: should return server preferred encodings', function () {
350+
assert.deepEqual(this.negotiator.encodings(['identity'], clientThenServerPreference), ['identity'])
351+
assert.deepEqual(this.negotiator.encodings(['gzip'], clientThenServerPreference), ['gzip'])
352+
assert.deepEqual(this.negotiator.encodings(['compress', 'deflate', 'gzip'], clientThenServerPreference), ['compress', 'deflate', 'gzip'])
320353
})
321354
})
322355

@@ -403,6 +436,11 @@ describe('negotiator.encodings(array)', function () {
403436
assert.deepEqual(this.negotiator.encodings(['deflate']), ['deflate'])
404437
assert.deepEqual(this.negotiator.encodings(['deflate', 'gzip']), ['deflate', 'gzip'])
405438
})
439+
440+
it('serverPreference: should ignore client quality levels', function () {
441+
assert.deepEqual(this.negotiator.encodings(['deflate', 'gzip'], serverPreference), ['deflate', 'gzip'])
442+
assert.deepEqual(this.negotiator.encodings(['gzip', 'deflate'], serverPreference), ['gzip', 'deflate'])
443+
})
406444
})
407445

408446
whenAcceptEncoding('gzip;q=0.8, identity;q=0.5, *;q=0.3', function () {
@@ -411,6 +449,87 @@ describe('negotiator.encodings(array)', function () {
411449
assert.deepEqual(this.negotiator.encodings(['identity', 'gzip', 'compress']), ['gzip', 'identity', 'compress'])
412450
})
413451
})
452+
453+
whenAcceptEncoding('not;q=0, med1;q=0.9, med2;q=0.9, high, *;q=0.8', function () {
454+
it('clientPreference', function () {
455+
assert.deepEqual(this.negotiator.encodings(['high', 'not', 'med2', 'other', 'med1'], clientPreference), ['high', 'med1', 'med2', 'other'])
456+
assert.deepEqual(this.negotiator.encodings(['high', 'not', 'med1', 'other', 'med2'], clientPreference), ['high', 'med1', 'med2', 'other'])
457+
})
458+
459+
it('clientThenServerPreference', function () {
460+
assert.deepEqual(this.negotiator.encodings(['high', 'not', 'med2', 'other', 'med1'], clientThenServerPreference), ['high', 'med2', 'med1', 'other'])
461+
assert.deepEqual(this.negotiator.encodings(['high', 'not', 'med1', 'other', 'med2'], clientThenServerPreference), ['high', 'med1', 'med2', 'other'])
462+
})
463+
464+
it('serverPreference', function () {
465+
assert.deepEqual(this.negotiator.encodings(['high', 'not', 'med2', 'other', 'med1'], serverPreference), ['high', 'med2', 'other', 'med1'])
466+
assert.deepEqual(this.negotiator.encodings(['high', 'not', 'med1', 'other', 'med2'], serverPreference), ['high', 'med1', 'other', 'med2'])
467+
})
468+
})
469+
470+
whenAcceptEncoding('not;q=0, med1;q=0.9, med2;q=0.9, high, identity;q=0.9', function () {
471+
it('clientPreference', function () {
472+
assert.deepEqual(this.negotiator.encodings(['high', 'not', 'med2', 'other', 'med1', 'identity'], clientPreference), ['high', 'med1', 'med2', 'identity'])
473+
assert.deepEqual(this.negotiator.encodings(['high', 'not', 'med1', 'other', 'med2', 'identity'], clientPreference), ['high', 'med1', 'med2', 'identity'])
474+
})
475+
476+
it('clientThenServerPreference', function () {
477+
assert.deepEqual(this.negotiator.encodings(['high', 'not', 'med2', 'other', 'med1', 'identity'], clientThenServerPreference), ['high', 'med2', 'med1', 'identity'])
478+
assert.deepEqual(this.negotiator.encodings(['high', 'not', 'med1', 'other', 'med2', 'identity'], clientThenServerPreference), ['high', 'med1', 'med2', 'identity'])
479+
})
480+
481+
it('serverPreference', function () {
482+
assert.deepEqual(this.negotiator.encodings(['high', 'not', 'med2', 'other', 'med1', 'identity'], serverPreference), ['high', 'med2', 'med1', 'identity'])
483+
assert.deepEqual(this.negotiator.encodings(['high', 'not', 'med1', 'other', 'med2', 'identity'], serverPreference), ['high', 'med1', 'med2', 'identity'])
484+
})
485+
})
486+
487+
whenAcceptEncoding('c;q=0.9, b;q=0.89, a;q=0.9, d;q=0.91, e;q=0.9', function () {
488+
it('clientPreference', function () {
489+
assert.deepEqual(this.negotiator.encodings(['b','c','d','e'], clientPreference), ['d', 'c', 'e', 'b']);
490+
assert.deepEqual(this.negotiator.encodings(['c','d','e','b'], clientPreference), ['d', 'c', 'e', 'b']);
491+
assert.deepEqual(this.negotiator.encodings(['d','e','b','c'], clientPreference), ['d', 'c', 'e', 'b']);
492+
assert.deepEqual(this.negotiator.encodings(['e','b','c','d'], clientPreference), ['d', 'c', 'e', 'b']);
493+
494+
assert.deepEqual(this.negotiator.encodings(['a','c','e'], clientPreference), ['c', 'a', 'e']);
495+
assert.deepEqual(this.negotiator.encodings(['c','e','a'], clientPreference), ['c', 'a', 'e']);
496+
assert.deepEqual(this.negotiator.encodings(['e','a','c'], clientPreference), ['c', 'a', 'e']);
497+
498+
assert.deepEqual(this.negotiator.encodings(['identity','e','f'], clientPreference), ['e','identity']);
499+
assert.deepEqual(this.negotiator.encodings(['f','identity','e'], clientPreference), ['e','identity']);
500+
assert.deepEqual(this.negotiator.encodings(['e','f','identity'], clientPreference), ['e','identity']);
501+
})
502+
503+
it('clientThenServerPreference', function () {
504+
assert.deepEqual(this.negotiator.encodings(['b','c','d','e'], clientThenServerPreference), ['d', 'c', 'e', 'b']);
505+
assert.deepEqual(this.negotiator.encodings(['c','d','e','b'], clientThenServerPreference), ['d', 'c', 'e', 'b']);
506+
assert.deepEqual(this.negotiator.encodings(['d','e','b','c'], clientThenServerPreference), ['d', 'e', 'c', 'b']);
507+
assert.deepEqual(this.negotiator.encodings(['e','b','c','d'], clientThenServerPreference), ['d', 'e', 'c', 'b']);
508+
509+
assert.deepEqual(this.negotiator.encodings(['a','c','e'], clientThenServerPreference), ['a', 'c', 'e']);
510+
assert.deepEqual(this.negotiator.encodings(['c','e','a'], clientThenServerPreference), ['c', 'e', 'a']);
511+
assert.deepEqual(this.negotiator.encodings(['e','a','c'], clientThenServerPreference), ['e', 'a', 'c']);
512+
513+
assert.deepEqual(this.negotiator.encodings(['identity','e','f'], clientThenServerPreference), ['e','identity']);
514+
assert.deepEqual(this.negotiator.encodings(['f','identity','e'], clientThenServerPreference), ['e','identity']);
515+
assert.deepEqual(this.negotiator.encodings(['e','f','identity'], clientThenServerPreference), ['e','identity']);
516+
})
517+
518+
it('serverPreference', function () {
519+
assert.deepEqual(this.negotiator.encodings(['b','c','d','e'], serverPreference), ['b','c','d','e']);
520+
assert.deepEqual(this.negotiator.encodings(['c','d','e','b'], serverPreference), ['c','d','e','b']);
521+
assert.deepEqual(this.negotiator.encodings(['d','e','b','c'], serverPreference), ['d','e','b','c']);
522+
assert.deepEqual(this.negotiator.encodings(['e','b','c','d'], serverPreference), ['e','b','c','d']);
523+
524+
assert.deepEqual(this.negotiator.encodings(['a','c','e'], serverPreference), ['a', 'c', 'e']);
525+
assert.deepEqual(this.negotiator.encodings(['c','e','a'], serverPreference), ['c', 'e', 'a']);
526+
assert.deepEqual(this.negotiator.encodings(['e','a','c'], serverPreference), ['e', 'a', 'c']);
527+
528+
assert.deepEqual(this.negotiator.encodings(['identity','e','f'], serverPreference), ['identity', 'e']);
529+
assert.deepEqual(this.negotiator.encodings(['f','identity','e'], serverPreference), ['identity', 'e']);
530+
assert.deepEqual(this.negotiator.encodings(['e','f','identity'], serverPreference), ['e','identity']);
531+
})
532+
})
414533
})
415534

416535
function createRequest(headers) {

0 commit comments

Comments
 (0)