fastify-compress
Advanced tools
Comparing version 0.11.0 to 1.0.0
import { Plugin } from 'fastify' | ||
import { Server, IncomingMessage, ServerResponse } from 'http' | ||
type EncodingToken = 'br' | 'deflate' | 'gzip' | 'identity' | ||
declare const fastifyCompress: Plugin< | ||
@@ -15,2 +17,3 @@ Server, | ||
inflateIfDeflated?: boolean | ||
encodings?: Array<EncodingToken> | ||
} | ||
@@ -17,0 +20,0 @@ > |
57
index.js
@@ -40,8 +40,22 @@ 'use strict' | ||
compressStream.br = opts.brotli.compressStream | ||
supportedEncodings.push('br') | ||
supportedEncodings.unshift('br') | ||
} else if (zlib.createBrotliCompress) { | ||
compressStream.br = zlib.createBrotliCompress | ||
supportedEncodings.push('br') | ||
supportedEncodings.unshift('br') | ||
} | ||
if (opts.encodings && opts.encodings.length < 1) { | ||
next(new Error('The `encodings` option array must have at least 1 item.')) | ||
} | ||
const encodings = Array.isArray(opts.encodings) | ||
? supportedEncodings | ||
.filter(encoding => opts.encodings.includes(encoding)) | ||
.sort((a, b) => opts.encodings.indexOf(a) - supportedEncodings.indexOf(b)) | ||
: supportedEncodings | ||
if (encodings.length < 1) { | ||
next(new Error('None of the passed `encodings` were supported — compression not possible.')) | ||
} | ||
next() | ||
@@ -60,6 +74,6 @@ | ||
(this.request.headers['x-no-compression'] !== undefined) || | ||
// don't compress if not one of the indiated compressible types | ||
// don't compress if not one of the indicated compressible types | ||
(shouldCompress(this.getHeader('Content-Type') || 'application/json', compressibleTypes) === false) || | ||
// don't compress on missing or identity `accept-encoding` header | ||
((encoding = getEncodingHeader(supportedEncodings, this.request)) === undefined || encoding === 'identity') | ||
((encoding = getEncodingHeader(encodings, this.request)) == null || encoding === 'identity') | ||
@@ -76,8 +90,2 @@ if (noCompress) { | ||
if (encoding === null) { | ||
closeStream(payload) | ||
this.code(406).send(new Error('Unsupported encoding')) | ||
return | ||
} | ||
if (typeof payload.pipe !== 'function') { | ||
@@ -118,3 +126,3 @@ if (!Buffer.isBuffer(payload) && typeof payload !== 'string') { | ||
// don't compress on missing or identity `accept-encoding` header | ||
((encoding = getEncodingHeader(supportedEncodings, req)) === undefined || encoding === 'identity') | ||
((encoding = getEncodingHeader(encodings, req)) == null || encoding === 'identity') | ||
@@ -131,9 +139,2 @@ if (noCompress) { | ||
if (encoding === null) { | ||
closeStream(payload) | ||
reply.code(406) | ||
next(new Error('Unsupported encoding')) | ||
return | ||
} | ||
if (typeof payload.pipe !== 'function') { | ||
@@ -160,17 +161,13 @@ if (Buffer.byteLength(payload) < threshold) { | ||
function closeStream (payload) { | ||
if (typeof payload.close === 'function') { | ||
payload.close() | ||
} else if (typeof payload.destroy === 'function') { | ||
payload.destroy() | ||
} else if (typeof payload.abort === 'function') { | ||
payload.abort() | ||
function getEncodingHeader (encodings, request) { | ||
let header = request.headers['accept-encoding'] | ||
if (header != null) { | ||
header = header.toLowerCase() | ||
.replace('*', 'gzip') // consider the no-preference token as gzip for downstream compat | ||
return encodingNegotiator.negotiate(header, encodings) | ||
} else { | ||
return undefined | ||
} | ||
} | ||
function getEncodingHeader (supportedEncodings, request) { | ||
const header = request.headers['accept-encoding'] | ||
return encodingNegotiator.negotiate(header, supportedEncodings) | ||
} | ||
function shouldCompress (type, compressibleTypes) { | ||
@@ -177,0 +174,0 @@ if (compressibleTypes.test(type)) return true |
{ | ||
"name": "fastify-compress", | ||
"version": "0.11.0", | ||
"version": "1.0.0", | ||
"description": "Fastify compression utils", | ||
@@ -15,7 +15,7 @@ "main": "index.js", | ||
"mime-db": "^1.33.0", | ||
"minipass": "2.3.5", | ||
"minipass": "^2.9.0", | ||
"peek-stream": "^1.1.0", | ||
"pump": "^3.0.0", | ||
"pumpify": "^2.0.0", | ||
"string-to-stream": "^2.0.0", | ||
"string-to-stream": "^3.0.0", | ||
"unzipper": "^0.10.1" | ||
@@ -48,3 +48,3 @@ }, | ||
"gzip", | ||
"brodli" | ||
"brotli" | ||
], | ||
@@ -51,0 +51,0 @@ "author": "Tomas Della Vedova - @delvedor (http://delved.org)", |
@@ -7,27 +7,27 @@ # fastify-compress | ||
Adds compression utils to the Fastify `reply` object. | ||
Support `gzip`, `deflate` and `brotli`. | ||
Adds compression utils to [the Fastify `reply` object](https://www.fastify.io/docs/master/Reply/). | ||
Supports `gzip`, `deflate`, and `brotli`. | ||
## Install | ||
``` | ||
npm i fastify-compress --save | ||
npm i fastify-compress | ||
``` | ||
## Usage | ||
This plugins adds two functionalities to Fastify, a compress utility and a global compression hook. | ||
This plugin adds two functionalities to Fastify: a compress utility and a global compression hook. | ||
Currently the following headers are supported: | ||
- `'deflate'` | ||
- `'gzip'` | ||
- `'br'` | ||
- `'*'` | ||
Currently, the following encoding tokens are supported, using the first acceptable token in this order: | ||
If the `'accept-encoding'` header specifies no preferred encoding with an asterisk `*` the payload will be compressed with `gzip`. | ||
1. `br` | ||
2. `gzip` | ||
3. `deflate` | ||
4. `*` (no preference — `fastify-compress` will use `gzip`) | ||
5. `identity` (no compression) | ||
If an unsupported encoding is received, it will automatically return a `406` error, if the `'accept-encoding'` header is missing, it will not compress the payload. | ||
If the `accept-encoding` header is missing or contains no supported encodings, it will not compress the payload. | ||
It automatically defines if a payload should be compressed or not based on its `Content-Type`, if no content type is present, it will assume is `application/json`. | ||
The plugin automatically decides if a payload should be compressed based on its `content-type`; if no content type is present, it will assume `application/json`. | ||
### Global hook | ||
The global compression hook is enabled by default if you want to disable it, pass the option `{ global: false }`. | ||
The global compression hook is enabled by default. To disable it, pass the option `{ global: false }`: | ||
```javascript | ||
@@ -39,6 +39,6 @@ fastify.register( | ||
``` | ||
Remember that thanks to the Fastify encapsulation model, you can set a global compression, but running it only in a subset of routes is you wrap them inside a plugin. | ||
Remember that thanks to the Fastify encapsulation model, you can set a global compression, but run it only in a subset of routes if you wrap them inside a plugin. | ||
### `reply.compress` | ||
This plugin add a `compress` function to `reply` that accepts a stream or a string and compress it based on the `'accept-encoding'` header. If a js object is passed in, will be stringified as json. | ||
This plugin adds a `compress` method to `reply` that accepts a stream or a string, and compresses it based on the `accept-encoding` header. If a JS object is passed in, it will be stringified to JSON. | ||
@@ -64,3 +64,3 @@ ```javascript | ||
### Threshold | ||
You can set a custom threshold below which it will not be made compression, default to `1024`. | ||
The minimum byte size for a response to be compressed. Defaults to `1024`. | ||
```javascript | ||
@@ -73,3 +73,3 @@ fastify.register( | ||
### customTypes | ||
[mime-db](https://github.com/jshttp/mime-db) is used to determine if a `Content-Type` should be compressed. You can compress additional content types via regular expression. | ||
[mime-db](https://github.com/jshttp/mime-db) is used to determine if a `content-type` should be compressed. You can compress additional content types via regular expression. | ||
```javascript | ||
@@ -82,5 +82,5 @@ fastify.register( | ||
### Brotli | ||
Brotli compression is enabled by default if your Node.js(>= v11.7.0) supports it natively. | ||
Brotli compression is enabled by default if your Node.js supports it natively (≥ v11.7.0). | ||
For the Node.js versions that not support brotli natively, it's not enabled by default, if you need it we recommend to install [`iltorb`](https://www.npmjs.com/package/iltorb) and pass it as option. | ||
For Node.js versions that don’t natively support Brotli, it's not enabled by default. If you need it, we recommend installing [`iltorb`](https://www.npmjs.com/package/iltorb) and passing it to the `brotli` option: | ||
@@ -95,6 +95,6 @@ ```javascript | ||
### Disable compression by header | ||
You can selectively disable the response compression by using the `x-no-compression` header in the request. | ||
You can selectively disable response compression by using the `x-no-compression` header in the request. | ||
### Inflate pre-compressed bodies for clients that do not support compression | ||
Optional feature to inflate pre-compressed data if the client doesn't include one of the supported compression types in its `Accept-Encoding` header. | ||
Optional feature to inflate pre-compressed data if the client doesn't include one of the supported compression types in its `accept-encoding` header. | ||
```javascript | ||
@@ -112,4 +112,16 @@ fastify.register( | ||
### Customize encoding priority | ||
By default, `fastify-compress` prioritizes compression as described [at the beginning of §Usage](#usage). You can change that by passing an array of compression tokens to the `encodings` option: | ||
```javascript | ||
fastify.register( | ||
require('fastify-compress'), | ||
// Only support gzip and deflate, and prefer deflate to gzip | ||
{ encodings: ['deflate', 'gzip'] } | ||
) | ||
``` | ||
## Note | ||
Please have in mind that in large scale scenarios, you should use a proxy like Nginx to handle response-compression. | ||
Please note that in large-scale scenarios, you should use a proxy like Nginx to handle response compression. | ||
@@ -116,0 +128,0 @@ ## Acknowledgements |
153
test/test.js
@@ -92,2 +92,26 @@ 'use strict' | ||
test('should send a gzipped data if header case varied', t => { | ||
t.plan(3) | ||
const fastify = Fastify() | ||
fastify.register(compressPlugin, { global: false }) | ||
fastify.get('/', (req, reply) => { | ||
reply.type('text/plain').compress(createReadStream('./package.json')) | ||
}) | ||
fastify.inject({ | ||
url: '/', | ||
method: 'GET', | ||
headers: { | ||
'accept-encoding': 'GZiP' | ||
} | ||
}, (err, res) => { | ||
t.error(err) | ||
t.strictEqual(res.headers['content-encoding'], 'gzip') | ||
const file = readFileSync('./package.json', 'utf8') | ||
const payload = zlib.gunzipSync(res.rawPayload) | ||
t.strictEqual(payload.toString('utf-8'), file) | ||
}) | ||
}) | ||
test('should send a gzipped data with custom zlib', t => { | ||
@@ -262,3 +286,3 @@ t.plan(4) | ||
headers: { | ||
'accept-encoding': 'hello,br' | ||
'accept-encoding': 'hello,br,gzip' | ||
} | ||
@@ -274,3 +298,3 @@ }, (err, res) => { | ||
test('Unsupported encoding', t => { | ||
test('should send uncompressed if unsupported encoding', t => { | ||
t.plan(3) | ||
@@ -292,13 +316,9 @@ const fastify = Fastify() | ||
t.error(err) | ||
const payload = JSON.parse(res.payload) | ||
t.strictEqual(res.statusCode, 406) | ||
t.deepEqual({ | ||
error: 'Not Acceptable', | ||
message: 'Unsupported encoding', | ||
statusCode: 406 | ||
}, payload) | ||
t.strictEqual(res.statusCode, 200) | ||
const file = readFileSync('./package.json', 'utf8') | ||
t.strictEqual(res.payload, file) | ||
}) | ||
}) | ||
test('Unsupported encoding with quality value', t => { | ||
test('should send uncompressed if unsupported encoding with quality value', t => { | ||
t.plan(3) | ||
@@ -320,9 +340,5 @@ const fastify = Fastify() | ||
t.error(err) | ||
const payload = JSON.parse(res.payload) | ||
t.strictEqual(res.statusCode, 406) | ||
t.deepEqual({ | ||
error: 'Not Acceptable', | ||
message: 'Unsupported encoding', | ||
statusCode: 406 | ||
}, payload) | ||
t.strictEqual(res.statusCode, 200) | ||
const file = readFileSync('./package.json', 'utf8') | ||
t.strictEqual(res.payload, file) | ||
}) | ||
@@ -437,3 +453,3 @@ }) | ||
test('Should send 406 error on invalid accept encoding', t => { | ||
test('Should send uncompressed on invalid accept encoding', t => { | ||
t.plan(3) | ||
@@ -456,9 +472,4 @@ const fastify = Fastify() | ||
t.error(err) | ||
const payload = JSON.parse(res.payload) | ||
t.strictEqual(res.statusCode, 406) | ||
t.deepEqual({ | ||
error: 'Not Acceptable', | ||
message: 'Unsupported encoding', | ||
statusCode: 406 | ||
}, payload) | ||
t.strictEqual(res.statusCode, 200) | ||
t.strictEqual(res.payload, 'something') | ||
}) | ||
@@ -513,2 +524,25 @@ }) | ||
test('Should compress buffer (gzip) if header case varied', t => { | ||
t.plan(2) | ||
const fastify = Fastify() | ||
fastify.register(compressPlugin, { global: false, threshold: 0 }) | ||
const buf = Buffer.from('hello world') | ||
fastify.get('/', (req, reply) => { | ||
reply.compress(buf) | ||
}) | ||
fastify.inject({ | ||
url: '/', | ||
method: 'GET', | ||
headers: { | ||
'accept-encoding': 'GzIp' | ||
} | ||
}, (err, res) => { | ||
t.error(err) | ||
const payload = zlib.gunzipSync(res.rawPayload) | ||
t.strictEqual(payload.toString('utf-8'), buf.toString()) | ||
}) | ||
}) | ||
test('Should compress buffer (deflate)', t => { | ||
@@ -608,2 +642,25 @@ t.plan(2) | ||
test('Should compress buffer (gzip) with varied header case - global', t => { | ||
t.plan(2) | ||
const fastify = Fastify() | ||
fastify.register(compressPlugin, { threshold: 0 }) | ||
const buf = Buffer.from('hello world') | ||
fastify.get('/', (req, reply) => { | ||
reply.send(buf) | ||
}) | ||
fastify.inject({ | ||
url: '/', | ||
method: 'GET', | ||
headers: { | ||
'accept-encoding': 'gZIP' | ||
} | ||
}, (err, res) => { | ||
t.error(err) | ||
const payload = zlib.gunzipSync(res.rawPayload) | ||
t.strictEqual(payload.toString('utf-8'), buf.toString()) | ||
}) | ||
}) | ||
test('Should compress buffer (deflate) - global', t => { | ||
@@ -1398,1 +1455,47 @@ t.plan(2) | ||
}) | ||
test('Should only use `encodings` if passed', t => { | ||
t.plan(3) | ||
const fastify = Fastify() | ||
fastify.register(compressPlugin, { encodings: ['deflate'] }) | ||
fastify.get('/', (req, reply) => { | ||
reply.send(createReadStream('./package.json')) | ||
}) | ||
fastify.inject({ | ||
url: '/', | ||
method: 'GET', | ||
headers: { | ||
'accept-encoding': 'br,gzip,deflate' | ||
} | ||
}, (err, res) => { | ||
t.error(err) | ||
t.strictEqual(res.headers['content-encoding'], 'deflate') | ||
t.strictEqual(res.statusCode, 200) | ||
}) | ||
}) | ||
test('Should error if `encodings` array is empty', t => { | ||
t.plan(1) | ||
const fastify = Fastify() | ||
fastify.register(compressPlugin, { encodings: [] }) | ||
fastify.ready(err => { | ||
t.ok(err instanceof Error) | ||
}) | ||
}) | ||
test('Should error if no entries in `encodings` are supported', t => { | ||
t.plan(1) | ||
const fastify = Fastify() | ||
fastify.register(compressPlugin, { | ||
encodings: ['(not-a-real-encoding)'] | ||
}) | ||
fastify.ready(err => { | ||
t.ok(err instanceof Error) | ||
}) | ||
}) |
@@ -15,3 +15,4 @@ import * as fastify from 'fastify' | ||
inflateIfDeflated: true, | ||
customTypes: /x-protobuf$/ | ||
customTypes: /x-protobuf$/, | ||
encodings: ['gzip', 'br', 'identity', 'deflate'] | ||
}) |
No v1
QualityPackage is not semver >=1. This means it is not stable and does not support ^ ranges.
Found 1 instance in 1 package
52967
1537
0
128
+ Addedminipass@2.9.0(transitive)
+ Addedstring-to-stream@3.0.1(transitive)
- Removedminipass@2.3.5(transitive)
- Removedstring-to-stream@2.0.0(transitive)
Updatedminipass@^2.9.0
Updatedstring-to-stream@^3.0.0