koa-compress 5.1.0 → 5.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/HISTORY.md CHANGED
@@ -1,3 +1,35 @@
1
+ SEE https://github.com/koajs/compress/releases
2
+
3
+ 5.2.0 / 2026-02-10
4
+ ==================
5
+
6
+ * add functional and local options
7
+ * updated dependencies
8
+ * copied the project setup from Koa
9
+ * add zstd
10
+ * use `negotiator` for content negotiation
11
+ * add TypeScript typings and tests
12
+
13
+ 5.1.1 / 2023-04-08
14
+ ==================
15
+
16
+ * minor refactoring + updated dependencies
17
+
18
+ 5.1.0 / 2021-08-17
19
+ ==================
20
+
21
+ * change brotli defaults (compression level to 4)
22
+ * updated dependencies
23
+
24
+ 5.0.1 / 2020-07-06
25
+ ==================
26
+
27
+ * updated to accomodate Node changes: zlib constants were moved to a different namespace
28
+
29
+ 5.0.0 / 2020-07-05
30
+ ==================
31
+
32
+ * add `defaultEncoding`
1
33
 
2
34
  4.0.1 / 2020-04-29
3
35
  ==================
package/README.md CHANGED
@@ -8,23 +8,28 @@ Compress middleware for Koa
8
8
  ## Example
9
9
 
10
10
  ```js
11
- const compress = require('koa-compress')
12
- const Koa = require('koa')
13
-
14
- const app = new Koa()
15
- app.use(compress({
16
- filter (content_type) {
17
- return /text/i.test(content_type)
18
- },
19
- threshold: 2048,
20
- gzip: {
21
- flush: require('zlib').constants.Z_SYNC_FLUSH
22
- },
23
- deflate: {
24
- flush: require('zlib').constants.Z_SYNC_FLUSH,
25
- },
26
- br: false // disable brotli
27
- }))
11
+ const compress = require("koa-compress");
12
+ const Koa = require("koa");
13
+
14
+ const app = new Koa();
15
+ app.use(
16
+ compress({
17
+ filter(content_type) {
18
+ return /text/i.test(content_type);
19
+ },
20
+ threshold: 2048,
21
+ gzip: {
22
+ flush: require("zlib").constants.Z_SYNC_FLUSH,
23
+ },
24
+ deflate: {
25
+ flush: require("zlib").constants.Z_SYNC_FLUSH,
26
+ },
27
+ zstd: {
28
+ flush: require("zlib").constants.Z_SYNC_FLUSH,
29
+ },
30
+ br: false, // disable brotli
31
+ }),
32
+ );
28
33
  ```
29
34
 
30
35
  ## Maintainers
@@ -45,22 +50,28 @@ function (mimeType: string): Boolean {
45
50
  An optional function that checks the response content type to decide whether to compress.
46
51
  By default, it uses [compressible](https://github.com/jshttp/compressible).
47
52
 
48
- ### options.threshold\<String|Number\>
53
+ ### options.threshold\<String|Number|Function\>
49
54
 
50
- Minimum response size in bytes to compress.
55
+ Minimum response size in bytes to compress or a function that returns such response (see below).
51
56
  Default `1024` bytes or `1kb`.
52
57
 
53
- ### options[encoding]\<Object\>
58
+ ### options[encoding]\<Object|Function\>
54
59
 
55
- The current encodings are, in order of preference: `br`, `gzip`, `deflate`.
60
+ The current encodings are, in order of preference: `br`, `zstd`, `gzip`, `deflate`.
56
61
  Setting `options[encoding] = {}` will pass those options to the encoding function.
57
62
  Setting `options[encoding] = false` will disable that encoding.
58
63
 
59
- #### options<span></span>.br
64
+ It can be a function that returns options (see below).
65
+
66
+ #### options.br
60
67
 
61
68
  [Brotli compression](https://en.wikipedia.org/wiki/Brotli) is supported in node v11.7.0+, which includes it natively.
62
69
  As of v5.1.0, the default quality level is 4 for performance reasons.
63
70
 
71
+ #### options.zstd
72
+
73
+ [Zstandard compression](https://en.wikipedia.org/wiki/Zstandard) is natively supported in the Node.js API. Minimum supported version: `v22.15.0` (LTS); also available on the Current line starting from `v23.8.0+`. This middleware performs runtime feature detection for `zlib.createZstdCompress`; if present, Zstd is enabled, otherwise it is skipped—no version configuration required.
74
+
64
75
  ### options.defaultEncoding\<String\>
65
76
 
66
77
  An optional string, which specifies what encoders to use for requests without
@@ -79,7 +90,37 @@ This bypasses the filter check.
79
90
 
80
91
  ```js
81
92
  app.use((ctx, next) => {
82
- ctx.compress = true
83
- ctx.body = fs.createReadStream(file)
84
- })
93
+ ctx.compress = true;
94
+ ctx.body = fs.createReadStream(file);
95
+ });
96
+ ```
97
+
98
+ `ctx.compress` can be an object similar to `options` above, whose properties (`threshold` and encoding options)
99
+ override the global `options` for this response and bypass the filter check.
100
+
101
+ ## Functional properties
102
+
103
+ Certain properties (`threshold` and encoding options) can be specified as functions. Such functions will be called
104
+ for every response with three arguments:
105
+
106
+ - `type` &mdash; the same as `ctx.response.type` (provided for convenience)
107
+ - `size` &mdash; the same as `ctx.response.length` (provided for convenience)
108
+ - `ctx` &mdash; the whole context object, if you want to do something unique
109
+
110
+ It should return a valid value for that property. It is possible to return a function of the same shape,
111
+ which will be used to calculate the actual property.
112
+
113
+ Example:
114
+
115
+ ```js
116
+ app.use((ctx, next) => {
117
+ // ...
118
+ ctx.compress = (type, size, ctx) => ({
119
+ br: size && size >= 65536,
120
+ gzip: size && size < 65536,
121
+ });
122
+ ctx.body = payload;
123
+ });
85
124
  ```
125
+
126
+ Read all about `ctx` in https://koajs.com/#context and `ctx.response` in https://koajs.com/#response
package/lib/index.d.ts ADDED
@@ -0,0 +1,137 @@
1
+ import type { BrotliOptions, ZlibOptions, ZstdOptions } from "node:zlib";
2
+ import type Koa = require("koa");
3
+
4
+ /**
5
+ * Function to calculate a threshold value dynamically from a MIME type,
6
+ * an existing size and the current context.
7
+ */
8
+ export type ThresholdFunction = (
9
+ /** MIME type of the response */
10
+ type: string,
11
+ /** Size of the response in bytes */
12
+ size: number,
13
+ /** Context of the request */
14
+ ctx: Koa.Context,
15
+ ) => number | string | ThresholdFunction;
16
+
17
+ /**
18
+ * Function to calculate compression parameters for `deflate` and `gzip` from a MIME type,
19
+ * an exisiting size and the current context.
20
+ */
21
+ export type ZlibOptionsFunction = (
22
+ /** MIME type of the response */
23
+ type: string,
24
+ /** Size of the response in bytes */
25
+ size: number,
26
+ /** Context of the request */
27
+ ctx: Koa.Context,
28
+ ) => boolean | null | ZlibOptions | ZlibOptionsFunction;
29
+
30
+ /**
31
+ * Function to calculate compression parameters for `brotli` from a MIME type,
32
+ * an exisiting size and the current context.
33
+ */
34
+ export type BrotliOptionsFunction = (
35
+ /** MIME type of the response */
36
+ type: string,
37
+ /** Size of the response in bytes */
38
+ size: number,
39
+ /** Context of the request */
40
+ ctx: Koa.Context,
41
+ ) => boolean | null | BrotliOptions | BrotliOptionsFunction;
42
+
43
+ /**
44
+ * Function to calculate compression parameters for `zstd` from a MIME type,
45
+ * an exisiting size and the current context.
46
+ */
47
+ export type ZstdOptionsFunction = (
48
+ /** MIME type of the response */
49
+ type: string,
50
+ /** Size of the response in bytes */
51
+ size: number,
52
+ /** Context of the request */
53
+ ctx: Koa.Context,
54
+ ) => boolean | null | ZstdOptions | ZstdOptionsFunction;
55
+
56
+ /**
57
+ * Compression options that govern how `koa/compress` handles responses.
58
+ */
59
+ export type CompressOptions = {
60
+ /**
61
+ * Function to determine if compression should be applied.
62
+ * Default: `compressible()`.
63
+ * @param type MIME type of the response
64
+ * @returns `true` if compression should be applied, `false` otherwise
65
+ */
66
+ filter?: (type: string) => boolean;
67
+ /**
68
+ * Lower limit to apply compression to content. If it is a number, it is size in bytes,
69
+ * if it is a string, it is a human-readable size accepted by `bytes()`, e.g., `"1mb"`,
70
+ * or a `ThresholdFunction` that can calculate that value. Default: `1024`.
71
+ */
72
+ threshold?: number | string | ThresholdFunction;
73
+ /**
74
+ * Default value for `Accept-Encoding` header, if it is not supplied by the client.
75
+ * Default: `"identity"`.
76
+ */
77
+ defaultEncoding?: string;
78
+ /**
79
+ * What `Accept-Encoding` value should be assumed if it is set to `"*"`. Default: `"gzip"`.
80
+ */
81
+ wildcardAcceptEncoding?: string;
82
+ /**
83
+ * An array of compression types, which should be used when we have multiple choices
84
+ * with the same weight. An item with a lower index has higher priority.
85
+ * Default: `['zstd', 'br', 'gzip', 'deflate']`.
86
+ */
87
+ encodingPreference?: string[];
88
+ /**
89
+ * Options to use when compressing with `deflate()`.
90
+ * If it is `false` or `null` this compression should be disabled.
91
+ * It can be a function used to calculate such values.
92
+ * Default: `{}`.
93
+ */
94
+ deflate?: boolean | null | ZlibOptions | ZlibOptionsFunction;
95
+ /**
96
+ * Options to use when compressing with `gzip()`.
97
+ * If it is `false` or `null` this compression should be disabled.
98
+ * It can be a function used to calculate such values.
99
+ * Default: `{}`.
100
+ */
101
+ gzip?: boolean | null | ZlibOptions | ZlibOptionsFunction;
102
+ /**
103
+ * Options to use when compressing with `br()`.
104
+ * If it is `false` or `null` this compression should be disabled.
105
+ * It can be a function used to calculate such values.
106
+ * Default: `{[zlib.constants.BROTLI_PARAM_QUALITY]: 4}`.
107
+ */
108
+ br?: boolean | null | BrotliOptions | BrotliOptionsFunction;
109
+ /**
110
+ * Options to use when compressing with `zstd()`.
111
+ * If it is `false` or `null` this compression should be disabled.
112
+ * It can be a function used to calculate such values.
113
+ * Default: `{}`.
114
+ */
115
+ zstd?: boolean | null | ZstdOptions | ZstdOptionsFunction;
116
+ };
117
+
118
+ /**
119
+ * The main function that constructs a middleware for compressing responses.
120
+ * @param options sets the default way to handle compression.
121
+ * @returns a middleware function that compresses responses.
122
+ */
123
+ declare function compress(options: CompressOptions = {}): Koa.Middleware;
124
+
125
+ declare module "koa" {
126
+ interface DefaultContext {
127
+ /**
128
+ * Context property used to handle individual responses.
129
+ * If it is set to `false`, the compression is disabled.
130
+ * If it is an object, it is mixed with the default options overriding
131
+ * its properties for this request.
132
+ */
133
+ compress?: boolean | CompressOptions;
134
+ }
135
+ }
136
+
137
+ export = compress;
package/lib/index.js CHANGED
@@ -1,3 +1,5 @@
1
+ // @ts-self-types="./index.d.ts"
2
+
1
3
  'use strict'
2
4
 
3
5
  /**
@@ -6,17 +8,45 @@
6
8
 
7
9
  const compressible = require('compressible')
8
10
  const isJSON = require('koa-is-json')
9
- const status = require('statuses')
10
11
  const Stream = require('stream')
12
+ const zlib = require('zlib')
11
13
  const bytes = require('bytes')
14
+ const Negotiator = require('negotiator')
15
+
16
+ const encodingMethods = {
17
+ zstd: zlib.createZstdCompress,
18
+ br: zlib.createBrotliCompress,
19
+ gzip: zlib.createGzip,
20
+ deflate: zlib.createDeflate
21
+ }
12
22
 
13
- const Encodings = require('./encodings')
23
+ const encodingDefaultPreference = ['zstd', 'br', 'gzip', 'deflate', 'identity']
24
+
25
+ const preferredEncodings = encodingDefaultPreference.filter(
26
+ (encoding) => encodingMethods[encoding] || encoding === 'identity'
27
+ )
28
+
29
+ const encodingMethodDefaultOptions = {
30
+ gzip: {},
31
+ deflate: {},
32
+ br: {
33
+ params: {
34
+ [zlib.constants.BROTLI_PARAM_QUALITY]: 4
35
+ }
36
+ },
37
+ zstd: {}
38
+ }
14
39
 
15
40
  /**
16
- * Regex to match no-transform directive in a cache-control header
17
- */
41
+ * Regex to match no-transform directive in a cache-control header
42
+ */
18
43
  const NO_TRANSFORM_REGEX = /(?:^|,)\s*?no-transform\s*?(?:,|$)/
19
44
 
45
+ /**
46
+ * empty body statues.
47
+ */
48
+ const emptyBodyStatues = new Set([204, 205, 304])
49
+
20
50
  /**
21
51
  * Compress middleware.
22
52
  *
@@ -26,18 +56,31 @@ const NO_TRANSFORM_REGEX = /(?:^|,)\s*?no-transform\s*?(?:,|$)/
26
56
  */
27
57
 
28
58
  module.exports = (options = {}) => {
29
- let { filter = compressible, threshold = 1024, defaultEncoding = 'identity' } = options
59
+ let {
60
+ filter = compressible,
61
+ threshold = 1024,
62
+ defaultEncoding,
63
+ wildcardAcceptEncoding,
64
+ encodingPreference = encodingDefaultPreference
65
+ } = options
30
66
  if (typeof threshold === 'string') threshold = bytes(threshold)
67
+ if (!wildcardAcceptEncoding || wildcardAcceptEncoding === '*') {
68
+ wildcardAcceptEncoding = 'gzip'
69
+ }
70
+ defaultEncoding =
71
+ defaultEncoding === '*'
72
+ ? wildcardAcceptEncoding
73
+ : !defaultEncoding
74
+ ? 'identity'
75
+ : defaultEncoding
31
76
 
32
- // `options.br = false` would remove it as a preferred encoding
33
- const preferredEncodings = Encodings.preferredEncodings.filter((encoding) => options[encoding] !== false && options[encoding] !== null)
34
77
  const encodingOptions = {}
35
- preferredEncodings.forEach((encoding) => {
78
+ for (const encoding of preferredEncodings) {
36
79
  encodingOptions[encoding] = {
37
- ...Encodings.encodingMethodDefaultOptions[encoding],
38
- ...(options[encoding] || {})
80
+ ...encodingMethodDefaultOptions[encoding],
81
+ ...options[encoding]
39
82
  }
40
- })
83
+ }
41
84
 
42
85
  Object.assign(compressMiddleware, {
43
86
  preferredEncodings,
@@ -51,32 +94,67 @@ module.exports = (options = {}) => {
51
94
 
52
95
  await next()
53
96
 
54
- // early exit if there's no content body or the body is already encoded
55
97
  let { body } = ctx
56
- if (!body) return
57
- if (ctx.res.headersSent || !ctx.writable) return
58
- if (ctx.compress === false) return
59
- if (ctx.request.method === 'HEAD') return
60
- if (status.empty[ctx.response.status]) return
61
- if (ctx.response.get('Content-Encoding')) return
62
-
63
- // forced compression or implied
64
- if (!(ctx.compress === true || filter(ctx.response.type))) return
98
+ const { type, length: size } = ctx.response
99
+ if (
100
+ // early exit if there's no content body or the body is already encoded
101
+ !body ||
102
+ ctx.res.headersSent ||
103
+ !ctx.writable ||
104
+ ctx.compress === false ||
105
+ ctx.request.method === 'HEAD' ||
106
+ emptyBodyStatues.has(+ctx.response.status) ||
107
+ ctx.response.get('Content-Encoding') ||
108
+ // forced compression or implied
109
+ !(ctx.compress === true || filter(type)) ||
110
+ // don't compress for Cache-Control: no-transform
111
+ // https://tools.ietf.org/html/rfc7234#section-5.2.1.6
112
+ NO_TRANSFORM_REGEX.test(ctx.response.get('Cache-Control'))
113
+ ) {
114
+ return
115
+ }
65
116
 
66
- // don't compress for Cache-Control: no-transform
67
- // https://tools.ietf.org/html/rfc7234#section-5.2.1.6
68
- const cacheControl = ctx.response.get('Cache-Control')
69
- if (cacheControl && NO_TRANSFORM_REGEX.test(cacheControl)) return
117
+ // calculate "local" compression options
118
+ const responseOptions = { ...options, ...ctx.compress }
119
+ let { threshold = 1024 } = responseOptions
120
+ while (typeof threshold === 'function') {
121
+ threshold = threshold(type, size, ctx)
122
+ }
123
+ if (typeof threshold === 'string') threshold = bytes(threshold)
70
124
 
71
125
  // don't compress if the current response is below the threshold
72
- if (threshold && ctx.response.length < threshold) return
126
+ if (threshold && size < threshold) return
73
127
 
74
128
  // get the preferred content encoding
75
- const encodings = new Encodings({
76
- preferredEncodings
129
+ for (const encoding of compressMiddleware.preferredEncodings) {
130
+ // calculate compressor options, if any
131
+ if (!(encoding in responseOptions)) continue
132
+ let compressor = responseOptions[encoding]
133
+ while (typeof compressor === 'function') {
134
+ compressor = compressor(type, size, ctx)
135
+ }
136
+ responseOptions[encoding] = compressor
137
+ }
138
+
139
+ const finalEncodings = compressMiddleware.preferredEncodings.filter(
140
+ (encoding) =>
141
+ responseOptions[encoding] !== false &&
142
+ responseOptions[encoding] !== null
143
+ )
144
+
145
+ const acceptEncoding = ctx.request.headers['accept-encoding']
146
+ let request = ctx.request
147
+ if (acceptEncoding === '*') {
148
+ request = { headers: { 'accept-encoding': wildcardAcceptEncoding } }
149
+ } else if (!acceptEncoding) {
150
+ request = { headers: { 'accept-encoding': defaultEncoding } }
151
+ }
152
+
153
+ const negotiator = new Negotiator(request)
154
+ // if no encodings are supported, negotiator returns 'identity'
155
+ const encoding = negotiator.encoding(finalEncodings, {
156
+ preferred: encodingPreference
77
157
  })
78
- encodings.parseAcceptEncoding(ctx.request.headers['accept-encoding'] || defaultEncoding)
79
- const encoding = encodings.getPreferredContentEncoding()
80
158
 
81
159
  // identity === no compression
82
160
  if (encoding === 'identity') return
@@ -89,13 +167,10 @@ module.exports = (options = {}) => {
89
167
  ctx.set('Content-Encoding', encoding)
90
168
  ctx.res.removeHeader('Content-Length')
91
169
 
92
- const compress = Encodings.encodingMethods[encoding]
93
- const stream = ctx.body = compress(encodingOptions[encoding])
170
+ const compress = encodingMethods[encoding]
171
+ const stream = (ctx.body = compress(responseOptions[encoding]))
94
172
 
95
- if (body instanceof Stream) {
96
- body.pipe(stream)
97
- } else {
98
- stream.end(body)
99
- }
173
+ if (body instanceof Stream) return body.pipe(stream)
174
+ stream.end(body)
100
175
  }
101
176
  }
package/package.json CHANGED
@@ -1,36 +1,54 @@
1
1
  {
2
2
  "name": "koa-compress",
3
3
  "description": "Compress middleware for koa",
4
- "version": "5.1.0",
4
+ "version": "5.2.0",
5
+ "type": "commonjs",
5
6
  "author": {
6
7
  "name": "Jonathan Ong",
7
8
  "email": "me@jongleberry.com",
8
9
  "url": "http://jongleberry.com",
9
10
  "twitter": "https://twitter.com/jongleberry"
10
11
  },
12
+ "contributors": [
13
+ {
14
+ "name": "Imed Jaberi",
15
+ "email": "imed-jaberi@outlook.com",
16
+ "url": "https://www.3imed-jaberi.com/"
17
+ },
18
+ {
19
+ "name": "Eugene Lazutkin",
20
+ "email": "eugene.lazutkin@gmail.com",
21
+ "url": "https://www.lazutkin.com/"
22
+ }
23
+ ],
11
24
  "license": "MIT",
12
25
  "repository": "koajs/compress",
13
26
  "dependencies": {
14
- "bytes": "^3.0.0",
15
- "compressible": "^2.0.0",
16
- "http-errors": "^1.8.0",
27
+ "bytes": "^3.1.2",
28
+ "compressible": "^2.0.18",
29
+ "http-errors": "^2.0.1",
17
30
  "koa-is-json": "^1.0.0",
18
- "statuses": "^2.0.1"
31
+ "negotiator": "^1.0.0"
19
32
  },
20
33
  "devDependencies": {
21
- "eslint": "^7.32.0",
22
- "eslint-config-standard": "^16.0.3",
23
- "eslint-plugin-import": "^2.24.0",
24
- "eslint-plugin-node": "^11.0.0",
25
- "eslint-plugin-promise": "^5.1.0",
26
- "eslint-plugin-standard": "^5.0.0",
27
- "jest": "^27.0.6",
28
- "koa": "^2.13.1",
29
- "supertest": "^6.1.6"
34
+ "@types/koa": "^3.0.1",
35
+ "@types/node": "^25.2.2",
36
+ "c8": "^10.1.3",
37
+ "jest": "^30.2.0",
38
+ "koa": "^3.1.1",
39
+ "snazzy": "^9.0.0",
40
+ "standard": "^17.1.2",
41
+ "supertest": "^7.2.2",
42
+ "typescript": "^5.9.3"
30
43
  },
31
44
  "scripts": {
32
- "eslint": "eslint --ignore-path .gitignore .",
33
- "test": "jest"
45
+ "test": "jest",
46
+ "test:coverage": "c8 --reporter=lcov --reporter=text-summary jest --coverage",
47
+ "lint": "standard",
48
+ "lint:fix": "standard --fix",
49
+ "lint:pretty": "standard | snazzy",
50
+ "ts-check": "tsc --noEmit",
51
+ "authors": "git log --format='%aN <%aE>' | sort -u > AUTHORS"
34
52
  },
35
53
  "files": [
36
54
  "lib",
@@ -38,10 +56,21 @@
38
56
  "HISTORY.md"
39
57
  ],
40
58
  "main": "lib",
41
- "jest": {
42
- "testEnvironment": "node"
43
- },
59
+ "types": "lib/index.d.ts",
44
60
  "engines": {
45
- "node": ">= 8.0.0"
61
+ "node": ">= 12"
62
+ },
63
+ "standard": {
64
+ "globals": [
65
+ "afterAll",
66
+ "beforeAll",
67
+ "afterEach",
68
+ "jasmine",
69
+ "describe",
70
+ "test",
71
+ "jest",
72
+ "expect",
73
+ "it"
74
+ ]
46
75
  }
47
76
  }
package/lib/encodings.js DELETED
@@ -1,102 +0,0 @@
1
-
2
- // https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Accept-Encoding
3
- const errors = require('http-errors')
4
- const zlib = require('zlib')
5
-
6
- module.exports = Encodings
7
-
8
- // TODO: convert this to a Class when we drop node@10 support
9
- // because node@10 does not support class fields.
10
- function Encodings (options = {}) {
11
- this.wildcardAcceptEncoding = options.wildcardAcceptEncoding || Encodings.wildcardAcceptEncoding
12
- this.preferredEncodings = options.preferredEncodings || Encodings.preferredEncodings
13
- this.reDirective = options.reDirective || Encodings.reDirective
14
-
15
- this.encodingWeights = new Map()
16
- }
17
-
18
- Encodings.encodingMethods = {
19
- gzip: zlib.createGzip,
20
- deflate: zlib.createDeflate,
21
- br: zlib.createBrotliCompress
22
- }
23
-
24
- Encodings.encodingMethodDefaultOptions = {
25
- gzip: {},
26
- deflate: {},
27
- br: {
28
- [zlib.constants.BROTLI_PARAM_QUALITY]: 4
29
- }
30
- }
31
-
32
- // how we treat `Accept-Encoding: *`
33
- Encodings.wildcardAcceptEncoding = ['gzip', 'deflate']
34
- // our preferred encodings
35
- Encodings.preferredEncodings = ['br', 'gzip', 'deflate']
36
- Encodings.reDirective = /^\s*(gzip|compress|deflate|br|identity|\*)\s*(?:;\s*q\s*=\s*(\d(?:\.\d)?))?\s*$/
37
-
38
- Encodings.prototype.parseAcceptEncoding = function (acceptEncoding = '*') {
39
- const { encodingWeights, reDirective } = this
40
-
41
- acceptEncoding.split(',').forEach((directive) => {
42
- const match = reDirective.exec(directive)
43
- if (!match) return // not a supported encoding above
44
-
45
- const encoding = match[1]
46
-
47
- // weight must be in [0, 1]
48
- let weight = match[2] && !isNaN(match[2]) ? parseFloat(match[2], 10) : 1
49
- weight = Math.max(weight, 0)
50
- weight = Math.min(weight, 1)
51
-
52
- if (encoding === '*') {
53
- // set the weights for the default encodings
54
- this.wildcardAcceptEncoding.forEach((enc) => {
55
- if (!encodingWeights.has(enc)) encodingWeights.set(enc, weight)
56
- })
57
- return
58
- }
59
-
60
- encodingWeights.set(encoding, weight)
61
- })
62
- }
63
-
64
- Encodings.prototype.getPreferredContentEncoding = function () {
65
- const {
66
- encodingWeights,
67
- preferredEncodings
68
- } = this
69
-
70
- // get ordered list of accepted encodings
71
- const acceptedEncodings = Array.from(encodingWeights.keys())
72
- // sort by weight
73
- .sort((a, b) => encodingWeights.get(b) - encodingWeights.get(a))
74
- // filter by supported encodings
75
- .filter((encoding) => encoding === 'identity' || typeof Encodings.encodingMethods[encoding] === 'function')
76
-
77
- // group them by weights
78
- const weightClasses = new Map()
79
- acceptedEncodings.forEach((encoding) => {
80
- const weight = encodingWeights.get(encoding)
81
- if (!weightClasses.has(weight)) weightClasses.set(weight, new Set())
82
- weightClasses.get(weight).add(encoding)
83
- })
84
-
85
- // search by weight, descending
86
- const weights = Array.from(weightClasses.keys()).sort((a, b) => b - a)
87
- for (let i = 0; i < weights.length; i++) {
88
- // encodings at this weight
89
- const encodings = weightClasses.get(weights[i])
90
- // return the first encoding in the preferred list
91
- for (let j = 0; j < preferredEncodings.length; j++) {
92
- const preferredEncoding = preferredEncodings[j]
93
- if (encodings.has(preferredEncoding)) return preferredEncoding
94
- }
95
- }
96
-
97
- // no encoding matches, check to see if the client set identity, q=0
98
- if (encodingWeights.get('identity') === 0) throw errors(406, 'Please accept br, gzip, deflate, or identity.')
99
-
100
- // by default, return nothing
101
- return 'identity'
102
- }