From 0007b908ea85a971ff9440e4eaa588ddf17225fb Mon Sep 17 00:00:00 2001 From: Zorn Date: Fri, 26 Jun 2020 05:05:03 -0400 Subject: [PATCH] Support passing zlibOptions and brotliOptions (#113) * Support passing zlibOptions and brotliOptions https://github.com/fastify/fastify-compress/issues/106 * Update tests, types, and docs Co-authored-by: Blake Sager --- README.md | 18 +++++++ index.d.ts | 3 ++ index.js | 14 +++--- test/index.test-d.ts | 9 +++- test/test-global-compress.js | 95 ++++++++++++++++++++++++++++++++++++ 5 files changed, 132 insertions(+), 7 deletions(-) diff --git a/README.md b/README.md index 05a0103..48fdb38 100644 --- a/README.md +++ b/README.md @@ -144,6 +144,24 @@ fastify.register( ) ``` +### brotliOptions and zlibOptions + +You can tune compression by setting the `brotliOptions` and `zlibOptions` properties. These properties are passed directly to native node `zlib` methods, so they should match the corresponding [class](https://nodejs.org/api/zlib.html#zlib_class_brotlioptions) [definitions](https://nodejs.org/api/zlib.html#zlib_class_options). + +```javascript + server.register(fastifyCompress, { + brotliOptions: { + params: { + [zlib.constants.BROTLI_PARAM_MODE]: zlib.constants.BROTLI_MODE_TEXT, // useful for APIs that primarily return text + [zlib.constants.BROTLI_PARAM_QUALITY]: 4, // default is 11, max is 11, min is 0 + }, + }, + zlibOptions: { + level: 9, // default is 9, max is 9, min is 0 + } + }); +``` + ## Usage - Decompress request payloads This plugin adds a `preParsing` hook that decompress the request payload according to the `content-encoding` request header. diff --git a/index.d.ts b/index.d.ts index c2dcccf..19e3240 100644 --- a/index.d.ts +++ b/index.d.ts @@ -1,6 +1,7 @@ import { FastifyPlugin, FastifyReply, FastifyRequest, RawServerBase } from 'fastify'; import { Input, InputObject } from 'into-stream'; import { Stream } from 'stream'; +import { BrotliOptions, ZlibOptions } from 'zlib'; declare module "fastify" { interface FastifyReply { @@ -15,6 +16,8 @@ export interface FastifyCompressOptions { threshold?: number customTypes?: RegExp zlib?: NodeModule + brotliOptions?: BrotliOptions + zlibOptions?: ZlibOptions inflateIfDeflated?: boolean onUnsupportedEncoding?: (encoding: string, request: FastifyRequest, reply: FastifyReply) => string | Buffer | Stream encodings?: Array diff --git a/index.js b/index.js index 736b1ea..986b5fb 100644 --- a/index.js +++ b/index.js @@ -110,19 +110,21 @@ function processCompressParams (opts) { global: (typeof opts.global === 'boolean') ? opts.global : true } + params.brotliOptions = opts.brotliOptions + params.zlibOptions = opts.zlibOptions params.onUnsupportedEncoding = opts.onUnsupportedEncoding params.inflateIfDeflated = opts.inflateIfDeflated === true params.threshold = typeof opts.threshold === 'number' ? opts.threshold : 1024 params.compressibleTypes = opts.customTypes instanceof RegExp ? opts.customTypes : /^text\/|\+json$|\+text$|\+xml$|octet-stream$/ params.compressStream = { - br: (opts.zlib || zlib).createBrotliCompress || zlib.createBrotliCompress, - gzip: (opts.zlib || zlib).createGzip || zlib.createGzip, - deflate: (opts.zlib || zlib).createDeflate || zlib.createDeflate + br: () => ((opts.zlib || zlib).createBrotliCompress || zlib.createBrotliCompress)(params.brotliOptions), + gzip: () => ((opts.zlib || zlib).createGzip || zlib.createGzip)(params.zlibOptions), + deflate: () => ((opts.zlib || zlib).createDeflate || zlib.createDeflate)(params.zlibOptions) } params.uncompressStream = { - br: (opts.zlib || zlib).createBrotliDecompress || zlib.createBrotliDecompress, - gzip: (opts.zlib || zlib).createGunzip || zlib.createGunzip, - deflate: (opts.zlib || zlib).createInflate || zlib.createInflate + br: () => ((opts.zlib || zlib).createBrotliDecompress || zlib.createBrotliDecompress)(params.brotliOptions), + gzip: () => ((opts.zlib || zlib).createGunzip || zlib.createGunzip)(params.zlibOptions), + deflate: () => ((opts.zlib || zlib).createInflate || zlib.createInflate)(params.zlibOptions) } const supportedEncodings = ['br', 'gzip', 'deflate', 'identity'] diff --git a/test/index.test-d.ts b/test/index.test-d.ts index 29de717..e8afbce 100644 --- a/test/index.test-d.ts +++ b/test/index.test-d.ts @@ -10,6 +10,13 @@ app.register(fastifyCompress, { global: true, threshold: 10, zlib: zlib, + brotliOptions: { + params: { + [zlib.constants.BROTLI_PARAM_MODE]: zlib.constants.BROTLI_MODE_TEXT, + [zlib.constants.BROTLI_PARAM_QUALITY]: 4 + } + }, + zlibOptions: { level: 1 }, inflateIfDeflated: true, customTypes: /x-protobuf$/, encodings: ['gzip', 'br', 'identity', 'deflate'], @@ -17,7 +24,7 @@ app.register(fastifyCompress, { forceRequestEncoding: 'gzip' }) -const appWithoutGlobal = fastify(); +const appWithoutGlobal = fastify() appWithoutGlobal.register(fastifyCompress, { global: false }) diff --git a/test/test-global-compress.js b/test/test-global-compress.js index af09ef9..5bed6dd 100644 --- a/test/test-global-compress.js +++ b/test/test-global-compress.js @@ -1597,3 +1597,98 @@ test('Should not compress mime types with undefined compressible values', t => { t.strictEqual(res.payload, 'hello') }) }) + +test('Should send data compressed according to brotliOptions', t => { + t.plan(3) + const fastify = Fastify() + const brotliOptions = { + params: { + [zlib.constants.BROTLI_PARAM_MODE]: zlib.constants.BROTLI_MODE_TEXT, + [zlib.constants.BROTLI_PARAM_QUALITY]: 4 + } + } + + fastify.register(compressPlugin, { + global: false, + brotliOptions + }) + + fastify.get('/', (req, reply) => { + reply.type('text/plain').compress(createReadStream('./package.json')) + }) + + fastify.inject({ + url: '/', + method: 'GET', + headers: { + 'accept-encoding': 'br' + } + }, (err, res) => { + t.error(err) + t.strictEqual(res.headers['content-encoding'], 'br') + const file = readFileSync('./package.json', 'utf8') + const payload = zlib.brotliDecompressSync(res.rawPayload, brotliOptions) + t.strictEqual(payload.toString('utf-8'), file) + }) +}) + +test('Should send data deflated according to zlibOptions', t => { + t.plan(3) + const fastify = Fastify() + const zlibOptions = { + level: 1, + dictionary: Buffer.from('fastifycompress') + } + + fastify.register(compressPlugin, { + global: false, + zlibOptions + }) + + fastify.get('/', (req, reply) => { + reply.type('text/plain').compress(createReadStream('./package.json')) + }) + + fastify.inject({ + url: '/', + method: 'GET', + headers: { + 'accept-encoding': 'deflate' + } + }, (err, res) => { + t.error(err) + t.strictEqual(res.headers['content-encoding'], 'deflate') + const fileBuffer = readFileSync('./package.json') + t.same(res.rawPayload, zlib.deflateSync(fileBuffer, zlibOptions)) + }) +}) + +test('Should send data gzipped according to zlibOptions', t => { + t.plan(3) + const fastify = Fastify() + const zlibOptions = { + level: 1 + } + + fastify.register(compressPlugin, { + global: false, + zlibOptions + }) + + 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 fileBuffer = readFileSync('./package.json') + t.same(res.rawPayload, zlib.gzipSync(fileBuffer, zlibOptions)) + }) +})