From b166462e53122d1d7932634e44d77cd9e7d9cf95 Mon Sep 17 00:00:00 2001 From: Bart Riepe Date: Wed, 18 Jun 2025 13:41:35 +0900 Subject: [PATCH 1/3] feat: allow enabling and disabling compression and decompression separately --- README.md | 11 +++ index.js | 8 +- test/global-compress.test.js | 149 +++++++++++++++++++++++++++++++++++ types/index.d.ts | 2 + 4 files changed, 168 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 7953282..aec5175 100644 --- a/README.md +++ b/README.md @@ -56,6 +56,17 @@ await fastify.register( { global: false } ) ``` + +If only compression, or only decompression is desired, you can use the `globalCompression` or `globalDecompression` config flags to turn either off specifically (because the global hook is enabled by default). + +```js +await fastify.register( + import('@fastify/compress'), + // only decompress compressed incoming requests + { globalCompression: false } +) +``` + Fastify encapsulation can be used to set global compression but run it only in a subset of routes by wrapping them inside a plugin. > ℹ️ Note: If using `@fastify/compress` plugin together with `@fastify/static` plugin, `@fastify/compress` must be registered (with *global hook*) **before** registering `@fastify/static`. diff --git a/index.js b/index.js index 0aabf9b..c9bb1e6 100644 --- a/index.js +++ b/index.js @@ -124,7 +124,9 @@ function processCompressParams (opts) { } const params = { - global: (typeof opts.global === 'boolean') ? opts.global : true + global: (typeof opts.globalCompression === 'boolean') + ? opts.globalCompression + : (typeof opts.global === 'boolean') ? opts.global : true } params.removeContentLengthHeader = typeof opts.removeContentLengthHeader === 'boolean' ? opts.removeContentLengthHeader : true @@ -173,7 +175,9 @@ function processDecompressParams (opts) { const customZlib = opts.zlib || zlib const params = { - global: (typeof opts.global === 'boolean') ? opts.global : true, + global: (typeof opts.globalDecompression === 'boolean') + ? opts.globalDecompression + : (typeof opts.global === 'boolean') ? opts.global : true, onUnsupportedRequestEncoding: opts.onUnsupportedRequestEncoding, onInvalidRequestPayload: opts.onInvalidRequestPayload, decompressStream: { diff --git a/test/global-compress.test.js b/test/global-compress.test.js index 4c142bc..de68c58 100644 --- a/test/global-compress.test.js +++ b/test/global-compress.test.js @@ -3298,3 +3298,152 @@ for (const contentType of notByDefaultSupportedContentTypes) { t.assert.equal(response.rawPayload.toString('utf-8'), file) }) } + +test('It should support separate globalCompression and globalDecompression settings', async (t) => { + t.plan(3) + + // Test case: disable compression but enable decompression globally + const fastify = Fastify() + await fastify.register(compressPlugin, { + globalCompression: false, + globalDecompression: true + }) + + const testData = { message: 'test data for decompression only' } + + fastify.get('/', (request, reply) => { + reply.send(testData) + }) + + // Test that compression is disabled + const getResponse = await fastify.inject({ + url: '/', + method: 'GET', + headers: { + 'accept-encoding': 'gzip, deflate, br' + } + }) + + t.assert.equal(getResponse.statusCode, 200) + // No compression should happen since globalCompression is false + t.assert.equal(getResponse.headers['content-encoding'], undefined) + t.assert.deepEqual(getResponse.json(), testData) +}) + +test('It should enable decompression when globalDecompression is true', async (t) => { + t.plan(2) + + // Test case: enable decompression globally + const fastify = Fastify() + await fastify.register(compressPlugin, { + globalCompression: false, + globalDecompression: true + }) + + // Create a compressed request payload to test decompression + const compressedPayload = zlib.gzipSync(JSON.stringify({ input: 'compressed data' })) + + fastify.post('/', (request, reply) => { + // This should be decompressed automatically + reply.send({ received: request.body }) + }) + + // Test that decompression is enabled + const postResponse = await fastify.inject({ + url: '/', + method: 'POST', + headers: { + 'content-encoding': 'gzip', + 'content-type': 'application/json' + }, + payload: compressedPayload + }) + + t.assert.equal(postResponse.statusCode, 200) + // Decompression should work since globalDecompression is true + t.assert.deepEqual(postResponse.json(), { received: { input: 'compressed data' } }) +}) + +test('It should fall back to global option when specific options are not provided', async (t) => { + t.plan(2) + + // Test that global: false affects both compression and decompression + const fastify = Fastify() + await fastify.register(compressPlugin, { + global: false + }) + + const testData = { message: 'test data' } + + fastify.get('/', (request, reply) => { + reply.send(testData) + }) + + const response = await fastify.inject({ + url: '/', + method: 'GET', + headers: { + 'accept-encoding': 'gzip, deflate, br' + } + }) + + t.assert.equal(response.statusCode, 200) + // No compression should happen since global is false + t.assert.equal(response.headers['content-encoding'], undefined) +}) + +test('It should demonstrate globalCompression overrides global setting', async (t) => { + t.plan(2) + + // Test that globalCompression: true overrides global: false + const fastify = Fastify() + await fastify.register(compressPlugin, { + global: false, + globalCompression: true, + threshold: 0 + }) + + fastify.get('/', (request, reply) => { + reply.send({ message: 'globalCompression overrides global' }) + }) + + const response = await fastify.inject({ + url: '/', + method: 'GET', + headers: { 'accept-encoding': 'gzip' } + }) + + t.assert.equal(response.statusCode, 200) + t.assert.equal(response.headers['content-encoding'], 'gzip') // Compression enabled despite global: false +}) + +test('It should demonstrate globalDecompression controls decompression independently', async (t) => { + t.plan(2) + + // Test globalDecompression: false disables decompression even with global: true + const fastify = Fastify() + await fastify.register(compressPlugin, { + global: true, + globalDecompression: false + }) + + const compressedPayload = zlib.gzipSync(JSON.stringify({ test: 'decompression' })) + + fastify.post('/', (request, reply) => { + reply.send({ received: request.body }) + }) + + const response = await fastify.inject({ + url: '/', + method: 'POST', + headers: { + 'content-encoding': 'gzip', + 'content-type': 'application/json' + }, + payload: compressedPayload + }) + + // Should get 400 error since decompression is disabled and compressed data can't be parsed + t.assert.equal(response.statusCode, 400) + t.assert.ok(response.body.includes('Content-Length') || response.body.includes('Bad Request')) +}) diff --git a/types/index.d.ts b/types/index.d.ts index 63dd1d0..8e7f54b 100644 --- a/types/index.d.ts +++ b/types/index.d.ts @@ -77,6 +77,8 @@ declare namespace fastifyCompress { encodings?: EncodingToken[]; forceRequestEncoding?: EncodingToken; global?: boolean; + globalDecompression?: boolean; + globalCompression?: boolean; inflateIfDeflated?: boolean; onInvalidRequestPayload?: (encoding: string, request: FastifyRequest, error: Error) => Error | undefined | null; onUnsupportedEncoding?: (encoding: string, request: FastifyRequest, reply: FastifyReply) => string | Buffer | Stream; From dbf146f3ff454ed6d501998ca3df6c8ed7f72fb6 Mon Sep 17 00:00:00 2001 From: Bart Riepe Date: Thu, 19 Jun 2025 08:43:32 +0900 Subject: [PATCH 2/3] fix: apply suggestion for more readable English Co-authored-by: Frazer Smith Signed-off-by: Bart Riepe --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index aec5175..49f6c52 100644 --- a/README.md +++ b/README.md @@ -57,7 +57,7 @@ await fastify.register( ) ``` -If only compression, or only decompression is desired, you can use the `globalCompression` or `globalDecompression` config flags to turn either off specifically (because the global hook is enabled by default). +If only compression or decompression is required, set the `globalCompression` or `globalDecompression` config flags to `false` respectively (both are `true` by default). ```js await fastify.register( From 78b6e8b72e1b2f0d3ef2d7dc8bec2745e6134764 Mon Sep 17 00:00:00 2001 From: Manuel Spigolon Date: Sun, 9 Nov 2025 09:23:39 +0100 Subject: [PATCH 3/3] Apply suggestion from @Copilot Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> Signed-off-by: Manuel Spigolon --- README.md | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 49f6c52..f500887 100644 --- a/README.md +++ b/README.md @@ -62,9 +62,15 @@ If only compression or decompression is required, set the `globalCompression` or ```js await fastify.register( import('@fastify/compress'), - // only decompress compressed incoming requests + // Disable compression but keep decompression enabled (default behavior for globalDecompression is true) { globalCompression: false } ) + +// Disable decompression but keep compression enabled +await fastify.register( + import('@fastify/compress'), + { globalDecompression: false } +) ``` Fastify encapsulation can be used to set global compression but run it only in a subset of routes by wrapping them inside a plugin.