koa-compress 2.0.0 → 4.0.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,9 +1,39 @@
1
1
 
2
+ 4.0.1 / 2020-04-29
3
+ ==================
4
+
5
+ * fix issue with state being preserved between requests
6
+
7
+ 4.0.0 / 2020-04-27
8
+ ==================
9
+
10
+ * drop support for versions of node below 10
11
+ * add brotli support for versions of node that support it
12
+ * changed compression options; `options` is no longer passed to each compression function. Use `options.gzip = {}`, `options.br = {}`, etc.
13
+ * added compression disabling option: set `options.br = false`
14
+
15
+ 3.1.0 / 2020-04-15
16
+ ==================
17
+
18
+ * support no-transform @Pawda
19
+
20
+ 3.0.0 / 2018-04-14
21
+ ==================
22
+
23
+ * republish 2.1.0 as it switches to async functions
24
+
25
+ 2.1.0 / 2018-04-13 (unpublished)
26
+ ==================
27
+
28
+ * do not compress if the response is not writable
29
+ * switch testing frameworks to jest
30
+ * implement linting
31
+
2
32
  1.0.8 / 2014-09-14
3
33
  ==================
4
34
 
5
35
  * bump compressible
6
-
36
+
7
37
  1.0.7 / 2014-05-13
8
38
  ==================
9
39
 
package/README.md CHANGED
@@ -1,41 +1,64 @@
1
- # Koa Compress [![Build Status](https://travis-ci.org/koajs/compress.png)](https://travis-ci.org/koajs/compress)
1
+ # Koa Compress
2
+
3
+ [![Node.js CI](https://github.com/koajs/compress/workflows/Node.js%20CI/badge.svg?branch=master)](https://github.com/koajs/compress/actions?query=workflow%3A%22Node.js+CI%22+branch%3Amaster)
4
+ [![codecov](https://codecov.io/gh/koajs/compress/branch/master/graph/badge.svg)](https://codecov.io/gh/koajs/compress)
2
5
 
3
6
  Compress middleware for Koa
4
7
 
5
8
  ## Example
6
9
 
7
10
  ```js
8
- var compress = require('koa-compress')
9
- var Koa = require('koa')
11
+ const compress = require('koa-compress')
12
+ const Koa = require('koa')
10
13
 
11
- var app = new Koa()
14
+ const app = new Koa()
12
15
  app.use(compress({
13
- filter: function (content_type) {
16
+ filter (content_type) {
14
17
  return /text/i.test(content_type)
15
18
  },
16
19
  threshold: 2048,
17
- flush: require('zlib').Z_SYNC_FLUSH
20
+ gzip: {
21
+ flush: require('zlib').Z_SYNC_FLUSH
22
+ },
23
+ deflate: {
24
+ flush: require('zlib').Z_SYNC_FLUSH,
25
+ },
26
+ br: false // disable brotli
18
27
  }))
19
28
  ```
20
29
 
21
30
  ## Options
22
31
 
23
- The options are passed to `zlib`: http://nodejs.org/api/zlib.html#zlib_options
32
+ ### filter\<Function\>
24
33
 
25
- ### filter
34
+ ```ts
35
+ function (mimeType: string): Boolean {
36
+
37
+ }
38
+ ```
26
39
 
27
40
  An optional function that checks the response content type to decide whether to compress.
28
- By default, it uses [compressible](https://github.com/expressjs/compressible).
41
+ By default, it uses [compressible](https://github.com/jshttp/compressible).
29
42
 
30
- ### threshold
43
+ ### options.threshold\<String|Number\>
31
44
 
32
45
  Minimum response size in bytes to compress.
33
46
  Default `1024` bytes or `1kb`.
34
47
 
48
+ ### options[encoding]\<Object\>
49
+
50
+ The current encodings are, in order of preference: `br`, `gzip`, `deflate`.
51
+ Setting `options[encoding] = {}` will pass those options to the encoding function.
52
+ Setting `options[encoding] = false` will disable that encoding.
53
+
54
+ ### options.br
55
+
56
+ [Brotli compression](https://en.wikipedia.org/wiki/Brotli) is supported in node v11.7.0+, which includes it natively.
57
+
35
58
  ## Manually turning compression on and off
36
59
 
37
- You can always enable compression by setting `this.compress = true`.
38
- You can always disable compression by setting `this.compress = false`.
60
+ You can always enable compression by setting `ctx.compress = true`.
61
+ You can always disable compression by setting `ctx.compress = false`.
39
62
  This bypasses the filter check.
40
63
 
41
64
  ```js
@@ -0,0 +1,94 @@
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
+ // 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*$/
29
+
30
+ Encodings.prototype.parseAcceptEncoding = function (acceptEncoding = '*') {
31
+ const { encodingWeights, reDirective } = this
32
+
33
+ acceptEncoding.split(',').forEach((directive) => {
34
+ const match = reDirective.exec(directive)
35
+ if (!match) return // not a supported encoding above
36
+
37
+ const encoding = match[1]
38
+
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)
43
+
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
50
+ }
51
+
52
+ encodingWeights.set(encoding, weight)
53
+ })
54
+ }
55
+
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
86
+ }
87
+ }
88
+
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.')
91
+
92
+ // by default, return nothing
93
+ return 'identity'
94
+ }
package/lib/index.js ADDED
@@ -0,0 +1,87 @@
1
+ 'use strict'
2
+
3
+ /**
4
+ * Module dependencies.
5
+ */
6
+
7
+ const compressible = require('compressible')
8
+ const isJSON = require('koa-is-json')
9
+ const status = require('statuses')
10
+ const Stream = require('stream')
11
+ const bytes = require('bytes')
12
+
13
+ const Encodings = require('./encodings')
14
+
15
+ /**
16
+ * Regex to match no-transform directive in a cache-control header
17
+ */
18
+ const NO_TRANSFORM_REGEX = /(?:^|,)\s*?no-transform\s*?(?:,|$)/
19
+
20
+ /**
21
+ * Compress middleware.
22
+ *
23
+ * @param {Object} [options]
24
+ * @return {Function}
25
+ * @api public
26
+ */
27
+
28
+ module.exports = (options = {}) => {
29
+ let { filter = compressible, threshold = 1024 } = options
30
+ if (typeof threshold === 'string') threshold = bytes(threshold)
31
+
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
+
35
+ return async (ctx, next) => {
36
+ ctx.vary('Accept-Encoding')
37
+
38
+ await next()
39
+
40
+ // early exit if there's no content body or the body is already encoded
41
+ 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
+
60
+ // get the preferred content encoding
61
+ const encodings = new Encodings({
62
+ preferredEncodings
63
+ })
64
+ encodings.parseAcceptEncoding(ctx.request.headers['accept-encoding'] || undefined)
65
+ const encoding = encodings.getPreferredContentEncoding()
66
+
67
+ // identity === no compression
68
+ if (encoding === 'identity') return
69
+
70
+ /** begin compression logic **/
71
+
72
+ // json
73
+ if (isJSON(body)) body = ctx.body = JSON.stringify(body)
74
+
75
+ ctx.set('Content-Encoding', encoding)
76
+ ctx.res.removeHeader('Content-Length')
77
+
78
+ const compress = Encodings.encodingMethods[encoding]
79
+ const stream = ctx.body = compress(options[encoding])
80
+
81
+ if (body instanceof Stream) {
82
+ body.pipe(stream)
83
+ } else {
84
+ stream.end(body)
85
+ }
86
+ }
87
+ }
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "koa-compress",
3
3
  "description": "Compress middleware for koa",
4
- "version": "2.0.0",
4
+ "version": "4.0.1",
5
5
  "author": {
6
6
  "name": "Jonathan Ong",
7
7
  "email": "me@jongleberry.com",
@@ -11,26 +11,37 @@
11
11
  "license": "MIT",
12
12
  "repository": "koajs/compress",
13
13
  "dependencies": {
14
- "bytes": "^2.3.0",
14
+ "bytes": "^3.0.0",
15
15
  "compressible": "^2.0.0",
16
+ "http-errors": "^1.7.3",
16
17
  "koa-is-json": "^1.0.0",
17
- "statuses": "^1.0.0"
18
+ "statuses": "^2.0.0"
18
19
  },
19
20
  "devDependencies": {
20
- "istanbul": "^0.4.2",
21
- "koa": "^2.0.0-alpha.3",
22
- "mocha": "^2.4.1",
23
- "should": "^3.0.0",
24
- "supertest": "^1.0.0"
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"
25
30
  },
26
31
  "scripts": {
27
- "test": "NODE_ENV=test mocha --require should --reporter spec",
28
- "test-cov": "NODE_ENV=test node ./node_modules/.bin/istanbul cover ./node_modules/.bin/_mocha -- --require should",
29
- "test-travis": "NODE_ENV=test node ./node_modules/.bin/istanbul cover ./node_modules/.bin/_mocha --report lcovonly -- --require should"
32
+ "eslint": "eslint --ignore-path .gitignore .",
33
+ "test": "jest"
30
34
  },
31
35
  "files": [
32
- "index.js",
36
+ "lib",
33
37
  "LICENSE",
34
38
  "HISTORY.md"
35
- ]
39
+ ],
40
+ "main": "lib",
41
+ "jest": {
42
+ "testEnvironment": "node"
43
+ },
44
+ "engines": {
45
+ "node": ">= 8.0.0"
46
+ }
36
47
  }
package/index.js DELETED
@@ -1,79 +0,0 @@
1
- 'use strict';
2
-
3
- /**
4
- * Module dependencies.
5
- */
6
-
7
- var compressible = require('compressible')
8
- var isJSON = require('koa-is-json')
9
- var status = require('statuses')
10
- var Stream = require('stream')
11
- var bytes = require('bytes')
12
- var zlib = require('zlib')
13
-
14
- /**
15
- * Encoding methods supported.
16
- */
17
-
18
- var encodingMethods = {
19
- gzip: zlib.createGzip,
20
- deflate: zlib.createDeflate
21
- }
22
-
23
- /**
24
- * Compress middleware.
25
- *
26
- * @param {Object} [options]
27
- * @return {Function}
28
- * @api public
29
- */
30
-
31
- module.exports = (options) => {
32
- options = options || {}
33
-
34
- var filter = options.filter || compressible
35
-
36
- var threshold = !options.threshold ? 1024
37
- : typeof options.threshold === 'number' ? options.threshold
38
- : typeof options.threshold === 'string' ? bytes(options.threshold)
39
- : 1024
40
-
41
- return function compress(ctx, next) {
42
- ctx.vary('Accept-Encoding')
43
-
44
- return next().then(() => {
45
- var body = ctx.body
46
- if (!body) return
47
- if (ctx.compress === false) return
48
- if (ctx.request.method === 'HEAD') return
49
- if (status.empty[ctx.response.status]) return
50
- if (ctx.response.get('Content-Encoding')) return
51
-
52
- // forced compression or implied
53
- if (!(ctx.compress === true || filter(ctx.response.type))) return
54
-
55
- // identity
56
- var encoding = ctx.acceptsEncodings('gzip', 'deflate', 'identity')
57
- if (!encoding) ctx.throw(406, 'supported encodings: gzip, deflate, identity')
58
- if (encoding === 'identity') return
59
-
60
- // json
61
- if (isJSON(body)) body = ctx.body = JSON.stringify(body)
62
-
63
- // threshold
64
- if (threshold && ctx.response.length < threshold) return
65
-
66
- ctx.set('Content-Encoding', encoding)
67
- ctx.res.removeHeader('Content-Length')
68
-
69
- var stream =
70
- ctx.body = encodingMethods[encoding](options)
71
-
72
- if (body instanceof Stream) {
73
- body.pipe(stream)
74
- } else {
75
- stream.end(body)
76
- }
77
- });
78
- };
79
- }