koa-compress 5.0.1 → 5.1.1

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,4 @@
1
+ SEE https://github.com/koajs/compress/releases
1
2
 
2
3
  4.0.1 / 2020-04-29
3
4
  ==================
package/README.md CHANGED
@@ -27,6 +27,11 @@ app.use(compress({
27
27
  }))
28
28
  ```
29
29
 
30
+ ## Maintainers
31
+
32
+ - Lead: @jonathanong [@jongleberry](https://twitter.com/jongleberry)
33
+ - Team: @koajs/compress
34
+
30
35
  ## Options
31
36
 
32
37
  ### filter\<Function\>
@@ -54,12 +59,13 @@ Setting `options[encoding] = false` will disable that encoding.
54
59
  #### options<span></span>.br
55
60
 
56
61
  [Brotli compression](https://en.wikipedia.org/wiki/Brotli) is supported in node v11.7.0+, which includes it natively.
62
+ As of v5.1.0, the default quality level is 4 for performance reasons.
57
63
 
58
64
  ### options.defaultEncoding\<String\>
59
65
 
60
66
  An optional string, which specifies what encoders to use for requests without
61
67
  [Accept-Encoding](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Accept-Encoding).
62
- Default `idenity`.
68
+ Default `identity`.
63
69
 
64
70
  The standard dictates to treat such requests as `*` meaning that all compressions are permissible,
65
71
  yet it causes very practical problems when debugging servers with manual tools like `curl`, `wget`, and so on.
package/lib/encodings.js CHANGED
@@ -3,92 +3,102 @@
3
3
  const errors = require('http-errors')
4
4
  const zlib = require('zlib')
5
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
6
+ class Encodings {
7
+ constructor (options = {}) {
8
+ this.wildcardAcceptEncoding = options.wildcardAcceptEncoding || Encodings.wildcardAcceptEncoding
9
+ this.preferredEncodings = options.preferredEncodings || Encodings.preferredEncodings
10
+ this.reDirective = options.reDirective || Encodings.reDirective
14
11
 
15
- this.encodingWeights = new Map()
16
- }
12
+ this.encodingWeights = new Map()
13
+ }
17
14
 
18
- Encodings.encodingMethods = {
19
- gzip: zlib.createGzip,
20
- deflate: zlib.createDeflate,
21
- br: zlib.createBrotliCompress
22
- }
15
+ parseAcceptEncoding (acceptEncoding = '*') {
16
+ const { encodingWeights, reDirective } = this
23
17
 
24
- // how we treat `Accept-Encoding: *`
25
- Encodings.wildcardAcceptEncoding = ['gzip', 'deflate']
26
- // our preferred encodings
27
- Encodings.preferredEncodings = ['br', 'gzip', 'deflate']
28
- Encodings.reDirective = /^\s*(gzip|compress|deflate|br|identity|\*)\s*(?:;\s*q\s*=\s*(\d(?:\.\d)?))?\s*$/
18
+ acceptEncoding.split(',').forEach((directive) => {
19
+ const match = reDirective.exec(directive)
20
+ if (!match) return // not a supported encoding above
29
21
 
30
- Encodings.prototype.parseAcceptEncoding = function (acceptEncoding = '*') {
31
- const { encodingWeights, reDirective } = this
22
+ const encoding = match[1]
32
23
 
33
- acceptEncoding.split(',').forEach((directive) => {
34
- const match = reDirective.exec(directive)
35
- if (!match) return // not a supported encoding above
24
+ // weight must be in [0, 1]
25
+ let weight = match[2] && !isNaN(match[2]) ? parseFloat(match[2], 10) : 1
26
+ weight = Math.max(weight, 0)
27
+ weight = Math.min(weight, 1)
36
28
 
37
- const encoding = match[1]
29
+ if (encoding === '*') {
30
+ // set the weights for the default encodings
31
+ this.wildcardAcceptEncoding.forEach((enc) => {
32
+ if (!encodingWeights.has(enc)) encodingWeights.set(enc, weight)
33
+ })
34
+ return
35
+ }
38
36
 
39
- // weight must be in [0, 1]
40
- let weight = match[2] && !isNaN(match[2]) ? parseFloat(match[2], 10) : 1
41
- weight = Math.max(weight, 0)
42
- weight = Math.min(weight, 1)
37
+ encodingWeights.set(encoding, weight)
38
+ })
39
+ }
43
40
 
44
- if (encoding === '*') {
45
- // set the weights for the default encodings
46
- this.wildcardAcceptEncoding.forEach((enc) => {
47
- if (!encodingWeights.has(enc)) encodingWeights.set(enc, weight)
48
- })
49
- return
41
+ getPreferredContentEncoding () {
42
+ const {
43
+ encodingWeights,
44
+ preferredEncodings
45
+ } = this
46
+
47
+ // get ordered list of accepted encodings
48
+ const acceptedEncodings = Array.from(encodingWeights.keys())
49
+ // sort by weight
50
+ .sort((a, b) => encodingWeights.get(b) - encodingWeights.get(a))
51
+ // filter by supported encodings
52
+ .filter((encoding) => encoding === 'identity' || typeof Encodings.encodingMethods[encoding] === 'function')
53
+
54
+ // group them by weights
55
+ const weightClasses = new Map()
56
+ acceptedEncodings.forEach((encoding) => {
57
+ const weight = encodingWeights.get(encoding)
58
+ if (!weightClasses.has(weight)) weightClasses.set(weight, new Set())
59
+ weightClasses.get(weight).add(encoding)
60
+ })
61
+
62
+ // search by weight, descending
63
+ const weights = Array.from(weightClasses.keys()).sort((a, b) => b - a)
64
+ for (let i = 0; i < weights.length; i++) {
65
+ // encodings at this weight
66
+ const encodings = weightClasses.get(weights[i])
67
+ // return the first encoding in the preferred list
68
+ for (let j = 0; j < preferredEncodings.length; j++) {
69
+ const preferredEncoding = preferredEncodings[j]
70
+ if (encodings.has(preferredEncoding)) return preferredEncoding
71
+ }
50
72
  }
51
73
 
52
- encodingWeights.set(encoding, weight)
53
- })
74
+ // no encoding matches, check to see if the client set identity, q=0
75
+ if (encodingWeights.get('identity') === 0) throw errors(406, 'Please accept br, gzip, deflate, or identity.')
76
+
77
+ // by default, return nothing
78
+ return 'identity'
79
+ }
80
+ }
81
+
82
+ Encodings.encodingMethods = {
83
+ gzip: zlib.createGzip,
84
+ deflate: zlib.createDeflate,
85
+ br: zlib.createBrotliCompress
54
86
  }
55
87
 
56
- Encodings.prototype.getPreferredContentEncoding = function () {
57
- const {
58
- encodingWeights,
59
- preferredEncodings
60
- } = this
61
-
62
- // get ordered list of accepted encodings
63
- const acceptedEncodings = Array.from(encodingWeights.keys())
64
- // sort by weight
65
- .sort((a, b) => encodingWeights.get(b) - encodingWeights.get(a))
66
- // filter by supported encodings
67
- .filter((encoding) => encoding === 'identity' || typeof Encodings.encodingMethods[encoding] === 'function')
68
-
69
- // group them by weights
70
- const weightClasses = new Map()
71
- acceptedEncodings.forEach((encoding) => {
72
- const weight = encodingWeights.get(encoding)
73
- if (!weightClasses.has(weight)) weightClasses.set(weight, new Set())
74
- weightClasses.get(weight).add(encoding)
75
- })
76
-
77
- // search by weight, descending
78
- const weights = Array.from(weightClasses.keys()).sort((a, b) => b - a)
79
- for (let i = 0; i < weights.length; i++) {
80
- // encodings at this weight
81
- const encodings = weightClasses.get(weights[i])
82
- // return the first encoding in the preferred list
83
- for (let j = 0; j < preferredEncodings.length; j++) {
84
- const preferredEncoding = preferredEncodings[j]
85
- if (encodings.has(preferredEncoding)) return preferredEncoding
88
+ Encodings.encodingMethodDefaultOptions = {
89
+ gzip: {},
90
+ deflate: {},
91
+ br: {
92
+ params: {
93
+ [zlib.constants.BROTLI_PARAM_QUALITY]: 4
86
94
  }
87
95
  }
96
+ }
88
97
 
89
- // no encoding matches, check to see if the client set identity, q=0
90
- if (encodingWeights.get('identity') === 0) throw errors(406, 'Please accept br, gzip, deflate, or identity.')
98
+ // how we treat `Accept-Encoding: *`
99
+ Encodings.wildcardAcceptEncoding = ['gzip', 'deflate']
100
+ // our preferred encodings
101
+ Encodings.preferredEncodings = ['br', 'gzip', 'deflate']
102
+ Encodings.reDirective = /^\s*(gzip|compress|deflate|br|identity|\*)\s*(?:;\s*q\s*=\s*(\d(?:\.\d)?))?\s*$/
91
103
 
92
- // by default, return nothing
93
- return 'identity'
94
- }
104
+ module.exports = Encodings
package/lib/index.js CHANGED
@@ -6,7 +6,6 @@
6
6
 
7
7
  const compressible = require('compressible')
8
8
  const isJSON = require('koa-is-json')
9
- const status = require('statuses')
10
9
  const Stream = require('stream')
11
10
  const bytes = require('bytes')
12
11
 
@@ -17,6 +16,11 @@ const Encodings = require('./encodings')
17
16
  */
18
17
  const NO_TRANSFORM_REGEX = /(?:^|,)\s*?no-transform\s*?(?:,|$)/
19
18
 
19
+ /**
20
+ * empty body statues.
21
+ */
22
+ const emptyBodyStatues = new Set([204, 205, 304])
23
+
20
24
  /**
21
25
  * Compress middleware.
22
26
  *
@@ -31,36 +35,46 @@ module.exports = (options = {}) => {
31
35
 
32
36
  // `options.br = false` would remove it as a preferred encoding
33
37
  const preferredEncodings = Encodings.preferredEncodings.filter((encoding) => options[encoding] !== false && options[encoding] !== null)
38
+ const encodingOptions = {}
39
+ preferredEncodings.forEach((encoding) => {
40
+ encodingOptions[encoding] = {
41
+ ...Encodings.encodingMethodDefaultOptions[encoding],
42
+ ...(options[encoding] || {})
43
+ }
44
+ })
45
+
46
+ Object.assign(compressMiddleware, {
47
+ preferredEncodings,
48
+ encodingOptions
49
+ })
34
50
 
35
- return async (ctx, next) => {
51
+ return compressMiddleware
52
+
53
+ async function compressMiddleware (ctx, next) {
36
54
  ctx.vary('Accept-Encoding')
37
55
 
38
56
  await next()
39
57
 
40
- // early exit if there's no content body or the body is already encoded
41
58
  let { body } = ctx
42
- if (!body) return
43
- if (ctx.res.headersSent || !ctx.writable) return
44
- if (ctx.compress === false) return
45
- if (ctx.request.method === 'HEAD') return
46
- if (status.empty[ctx.response.status]) return
47
- if (ctx.response.get('Content-Encoding')) return
48
-
49
- // forced compression or implied
50
- if (!(ctx.compress === true || filter(ctx.response.type))) return
51
-
52
- // don't compress for Cache-Control: no-transform
53
- // https://tools.ietf.org/html/rfc7234#section-5.2.1.6
54
- const cacheControl = ctx.response.get('Cache-Control')
55
- if (cacheControl && NO_TRANSFORM_REGEX.test(cacheControl)) return
56
-
57
- // don't compress if the current response is below the threshold
58
- if (threshold && ctx.response.length < threshold) return
59
+ if (
60
+ // early exit if there's no content body or the body is already encoded
61
+ !body ||
62
+ ctx.res.headersSent || !ctx.writable ||
63
+ ctx.compress === false ||
64
+ ctx.request.method === 'HEAD' ||
65
+ emptyBodyStatues.has(+ctx.response.status) ||
66
+ ctx.response.get('Content-Encoding') ||
67
+ // forced compression or implied
68
+ !(ctx.compress === true || filter(ctx.response.type)) ||
69
+ // don't compress for Cache-Control: no-transform
70
+ // https://tools.ietf.org/html/rfc7234#section-5.2.1.6
71
+ NO_TRANSFORM_REGEX.test(ctx.response.get('Cache-Control')) ||
72
+ // don't compress if the current response is below the threshold
73
+ (threshold && ctx.response.length < threshold)
74
+ ) return
59
75
 
60
76
  // get the preferred content encoding
61
- const encodings = new Encodings({
62
- preferredEncodings
63
- })
77
+ const encodings = new Encodings({ preferredEncodings })
64
78
  encodings.parseAcceptEncoding(ctx.request.headers['accept-encoding'] || defaultEncoding)
65
79
  const encoding = encodings.getPreferredContentEncoding()
66
80
 
@@ -76,12 +90,9 @@ module.exports = (options = {}) => {
76
90
  ctx.res.removeHeader('Content-Length')
77
91
 
78
92
  const compress = Encodings.encodingMethods[encoding]
79
- const stream = ctx.body = compress(options[encoding])
93
+ const stream = ctx.body = compress(encodingOptions[encoding])
80
94
 
81
- if (body instanceof Stream) {
82
- body.pipe(stream)
83
- } else {
84
- stream.end(body)
85
- }
95
+ if (body instanceof Stream) return body.pipe(stream)
96
+ stream.end(body)
86
97
  }
87
98
  }
package/package.json CHANGED
@@ -1,32 +1,38 @@
1
1
  {
2
2
  "name": "koa-compress",
3
3
  "description": "Compress middleware for koa",
4
- "version": "5.0.1",
4
+ "version": "5.1.1",
5
5
  "author": {
6
6
  "name": "Jonathan Ong",
7
7
  "email": "me@jongleberry.com",
8
8
  "url": "http://jongleberry.com",
9
9
  "twitter": "https://twitter.com/jongleberry"
10
10
  },
11
+ "contributors": [
12
+ {
13
+ "name": "Imed Jaberi",
14
+ "email": "imed-jaberi@outlook.com",
15
+ "url": "https://www.3imed-jaberi.com/"
16
+ }
17
+ ],
11
18
  "license": "MIT",
12
19
  "repository": "koajs/compress",
13
20
  "dependencies": {
14
- "bytes": "^3.0.0",
15
- "compressible": "^2.0.0",
16
- "http-errors": "^1.7.3",
17
- "koa-is-json": "^1.0.0",
18
- "statuses": "^2.0.0"
21
+ "bytes": "^3.1.2",
22
+ "compressible": "^2.0.18",
23
+ "http-errors": "^1.8.1",
24
+ "koa-is-json": "^1.0.0"
19
25
  },
20
26
  "devDependencies": {
21
- "eslint": "^6.0.0",
22
- "eslint-config-standard": "^14.1.1",
23
- "eslint-plugin-import": "^2.9.0",
24
- "eslint-plugin-node": "^11.0.0",
25
- "eslint-plugin-promise": "^4.0.0",
26
- "eslint-plugin-standard": "^4.0.1",
27
- "jest": "^25.3.0",
28
- "koa": "^2.0.1",
29
- "supertest": "^4.0.2"
27
+ "eslint": "^7.32.0",
28
+ "eslint-config-standard": "^16.0.3",
29
+ "eslint-plugin-import": "^2.26.0",
30
+ "eslint-plugin-node": "^11.1.0",
31
+ "eslint-plugin-promise": "^5.2.0",
32
+ "eslint-plugin-standard": "^5.0.0",
33
+ "jest": "^27.5.1",
34
+ "koa": "^2.13.4",
35
+ "supertest": "^6.2.4"
30
36
  },
31
37
  "scripts": {
32
38
  "eslint": "eslint --ignore-path .gitignore .",
@@ -38,10 +44,7 @@
38
44
  "HISTORY.md"
39
45
  ],
40
46
  "main": "lib",
41
- "jest": {
42
- "testEnvironment": "node"
43
- },
44
47
  "engines": {
45
- "node": ">= 8.0.0"
48
+ "node": ">= 12"
46
49
  }
47
50
  }