UNPKG

@fastify/compress

Version:
1,691 lines (1,407 loc) 107 kB
'use strict' const { describe, test } = require('node:test') const { createReadStream, readFile, readFileSync } = require('node:fs') const { Readable, Writable, PassThrough } = require('node:stream') const zlib = require('node:zlib') const AdmZip = require('adm-zip') const JSONStream = require('jsonstream') const Fastify = require('fastify') const compressPlugin = require('../index') const { once } = require('node:events') const { ReadableStream: WebReadableStream, Response } = globalThis describe('When `global` is not set, it is `true` by default :', async () => { test('it should compress Buffer data using brotli when `Accept-Encoding` request header is `br`', async (t) => { t.plan(1) const fastify = Fastify() await fastify.register(compressPlugin, { threshold: 0 }) const buf = Buffer.from('hello world') fastify.get('/', (_request, reply) => { reply.send(buf) }) const response = await fastify.inject({ url: '/', method: 'GET', headers: { 'accept-encoding': 'br' } }) const payload = zlib.brotliDecompressSync(response.rawPayload) t.assert.equal(payload.toString('utf-8'), buf.toString()) }) test('it should compress Buffer data using zstd when `Accept-Encoding` request header is `zstd`', async (t) => { if (typeof zlib.createZstdCompress !== 'function') { t.skip('zstd not supported in this Node.js version') return } t.plan(1) const fastify = Fastify() await fastify.register(compressPlugin, { threshold: 0 }) const buf = Buffer.from('hello world') fastify.get('/', (_request, reply) => { reply.send(buf) }) const response = await fastify.inject({ url: '/', method: 'GET', headers: { 'accept-encoding': 'zstd' } }) const payload = zlib.zstdDecompressSync(response.rawPayload) t.assert.equal(payload.toString('utf-8'), buf.toString()) }) test('it should compress Buffer data using deflate when `Accept-Encoding` request header is `deflate`', async (t) => { t.plan(1) const fastify = Fastify() await fastify.register(compressPlugin, { threshold: 0 }) const buf = Buffer.from('hello world') fastify.get('/', (_request, reply) => { reply.send(buf) }) const response = await fastify.inject({ url: '/', method: 'GET', headers: { 'accept-encoding': 'deflate' } }) const payload = zlib.inflateSync(response.rawPayload) t.assert.equal(payload.toString('utf-8'), buf.toString()) }) test('it should compress Buffer data using gzip when `Accept-Encoding` request header is `gzip`', async (t) => { t.plan(1) const fastify = Fastify() await fastify.register(compressPlugin, { threshold: 0 }) const buf = Buffer.from('hello world') fastify.get('/', (_request, reply) => { reply.send(buf) }) const response = await fastify.inject({ url: '/', method: 'GET', headers: { 'accept-encoding': 'gzip' } }) const payload = zlib.gunzipSync(response.rawPayload) t.assert.equal(payload.toString('utf-8'), buf.toString()) }) test('it should compress JSON data using brotli when `Accept-Encoding` request header is `br`', async (t) => { t.plan(1) const fastify = Fastify() await fastify.register(compressPlugin, { threshold: 0 }) const json = { hello: 'world' } fastify.get('/', (_request, reply) => { reply.send(json) }) const response = await fastify.inject({ url: '/', method: 'GET', headers: { 'accept-encoding': 'br' } }) const payload = zlib.brotliDecompressSync(response.rawPayload) t.assert.equal(payload.toString('utf-8'), JSON.stringify(json)) }) test('it should compress JSON data using zstd when `Accept-Encoding` request header is `zstd`', async (t) => { if (typeof zlib.createZstdCompress !== 'function') { t.skip('zstd not supported in this Node.js version') return } t.plan(1) const fastify = Fastify() await fastify.register(compressPlugin, { threshold: 0 }) const json = { hello: 'world' } fastify.get('/', (_request, reply) => { reply.send(json) }) const response = await fastify.inject({ url: '/', method: 'GET', headers: { 'accept-encoding': 'zstd' } }) const payload = zlib.zstdDecompressSync(response.rawPayload) t.assert.equal(payload.toString('utf-8'), JSON.stringify(json)) }) test('it should compress JSON data using deflate when `Accept-Encoding` request header is `deflate`', async (t) => { t.plan(1) const fastify = Fastify() await fastify.register(compressPlugin, { threshold: 0 }) const json = { hello: 'world' } fastify.get('/', (_request, reply) => { reply.send(json) }) const response = await fastify.inject({ url: '/', method: 'GET', headers: { 'accept-encoding': 'deflate' } }) const payload = zlib.inflateSync(response.rawPayload) t.assert.equal(payload.toString('utf-8'), JSON.stringify(json)) }) test('it should compress JSON data using gzip when `Accept-Encoding` request header is `gzip`', async (t) => { t.plan(1) const fastify = Fastify() await fastify.register(compressPlugin, { threshold: 0 }) const json = { hello: 'world' } fastify.get('/', (_request, reply) => { reply.send(json) }) const response = await fastify.inject({ url: '/', method: 'GET', headers: { 'accept-encoding': 'gzip' } }) const payload = zlib.gunzipSync(response.rawPayload) t.assert.equal(payload.toString('utf-8'), JSON.stringify(json)) }) test('it should compress string data using brotli when `Accept-Encoding` request header is `br', async (t) => { t.plan(1) const fastify = Fastify() await fastify.register(compressPlugin, { threshold: 0 }) fastify.get('/', (_request, reply) => { reply .type('text/plain') .send('hello') }) const response = await fastify.inject({ url: '/', method: 'GET', headers: { 'accept-encoding': 'br' } }) const payload = zlib.brotliDecompressSync(response.rawPayload) t.assert.equal(payload.toString('utf-8'), 'hello') }) test('it should compress string data using deflate when `Accept-Encoding` request header is `deflate', async (t) => { t.plan(1) const fastify = Fastify() await fastify.register(compressPlugin, { threshold: 0 }) fastify.get('/', (_request, reply) => { reply .type('text/plain') .compress('hello') }) const response = await fastify.inject({ url: '/', method: 'GET', headers: { 'accept-encoding': 'deflate' } }) const payload = zlib.inflateSync(response.rawPayload) t.assert.equal(payload.toString('utf-8'), 'hello') }) test('it should compress string data using gzip when `Accept-Encoding` request header is `gzip', async (t) => { t.plan(1) const fastify = Fastify() await fastify.register(compressPlugin, { threshold: 0 }) fastify.get('/', (_request, reply) => { reply .type('text/plain') .compress('hello') }) const response = await fastify.inject({ url: '/', method: 'GET', headers: { 'accept-encoding': 'gzip' } }) const payload = zlib.gunzipSync(response.rawPayload) t.assert.equal(payload.toString('utf-8'), 'hello') }) test('it should compress a Fetch API Response body', async (t) => { t.plan(1) const fastify = Fastify() await fastify.register(compressPlugin, { threshold: 0 }) const body = 'hello from fetch response' fastify.get('/fetch-resp', (_request, reply) => { const resp = new Response(body, { headers: { 'content-type': 'text/plain' } }) reply.send(resp) }) const response = await fastify.inject({ url: '/fetch-resp', method: 'GET', headers: { 'accept-encoding': 'gzip' } }) const payload = zlib.gunzipSync(response.rawPayload) t.assert.equal(payload.toString('utf-8'), body) }) test('it should compress a Web ReadableStream body', async (t) => { t.plan(1) const fastify = Fastify() await fastify.register(compressPlugin, { threshold: 0 }) const body = 'hello from web stream' fastify.get('/web-stream', (_request, reply) => { const stream = new WebReadableStream({ start (controller) { controller.enqueue(Buffer.from(body)) controller.close() } }) reply.header('content-type', 'text/plain') reply.send(stream) }) const response = await fastify.inject({ url: '/web-stream', method: 'GET', headers: { 'accept-encoding': 'gzip' } }) const payload = zlib.gunzipSync(response.rawPayload) t.assert.equal(payload.toString('utf-8'), body) }) }) describe('It should send compressed Stream data when `global` is `true` :', async () => { test('using brotli when `Accept-Encoding` request header is `br`', async (t) => { t.plan(3) const fastify = Fastify() await fastify.register(compressPlugin, { global: true }) fastify.get('/', (_request, reply) => { reply .type('text/plain') .compress(createReadStream('./package.json')) }) const response = await fastify.inject({ url: '/', method: 'GET', headers: { 'accept-encoding': 'br' } }) const file = readFileSync('./package.json', 'utf8') const payload = zlib.brotliDecompressSync(response.rawPayload) t.assert.equal(response.headers.vary, 'accept-encoding') t.assert.equal(response.headers['content-encoding'], 'br') t.assert.equal(payload.toString('utf-8'), file) }) test('using deflate when `Accept-Encoding` request header is `deflate`', async (t) => { t.plan(4) const fastify = Fastify() await fastify.register(compressPlugin, { global: true }) fastify.get('/', (_request, reply) => { reply .type('text/plain') .compress(createReadStream('./package.json')) }) const response = await fastify.inject({ url: '/', method: 'GET', headers: { 'accept-encoding': 'deflate' } }) const file = readFileSync('./package.json', 'utf8') const payload = zlib.inflateSync(response.rawPayload) t.assert.equal(response.headers.vary, 'accept-encoding') t.assert.equal(response.headers['content-encoding'], 'deflate') t.assert.ok(!response.headers['content-length'], 'no content length') t.assert.equal(payload.toString('utf-8'), file) }) test('using gzip when `Accept-Encoding` request header is `gzip`', async (t) => { t.plan(3) const fastify = Fastify() await fastify.register(compressPlugin, { global: true }) fastify.get('/', (_request, reply) => { reply .type('text/plain') .compress(createReadStream('./package.json')) }) const response = await fastify.inject({ url: '/', method: 'GET', headers: { 'accept-encoding': 'gzip' } }) const file = readFileSync('./package.json', 'utf8') const payload = zlib.gunzipSync(response.rawPayload) t.assert.equal(response.headers.vary, 'accept-encoding') t.assert.equal(response.headers['content-encoding'], 'gzip') t.assert.equal(payload.toString('utf-8'), file) }) }) describe('It should send compressed Buffer data when `global` is `true` :', async () => { test('using brotli when `Accept-Encoding` request header is `br`', async (t) => { t.plan(1) const fastify = Fastify() await fastify.register(compressPlugin, { global: true, threshold: 0 }) const buf = Buffer.from('hello world') fastify.get('/', (_request, reply) => { reply.compress(buf) }) const response = await fastify.inject({ url: '/', method: 'GET', headers: { 'accept-encoding': 'br' } }) const payload = zlib.brotliDecompressSync(response.rawPayload) t.assert.equal(payload.toString('utf-8'), buf.toString()) }) test('using deflate when `Accept-Encoding` request header is `deflate`', async (t) => { t.plan(1) const fastify = Fastify() await fastify.register(compressPlugin, { global: true, threshold: 0 }) const buf = Buffer.from('hello world') fastify.get('/', (_request, reply) => { reply.compress(buf) }) const response = await fastify.inject({ url: '/', method: 'GET', headers: { 'accept-encoding': 'deflate' } }) const payload = zlib.inflateSync(response.rawPayload) t.assert.equal(payload.toString('utf-8'), buf.toString()) }) test('using gzip when `Accept-Encoding` request header is `gzip`', async (t) => { t.plan(1) const fastify = Fastify() await fastify.register(compressPlugin, { global: true, threshold: 0 }) const buf = Buffer.from('hello world') fastify.get('/', (_request, reply) => { reply.compress(buf) }) const response = await fastify.inject({ url: '/', method: 'GET', headers: { 'accept-encoding': 'gzip' } }) const payload = zlib.gunzipSync(response.rawPayload) t.assert.equal(payload.toString('utf-8'), buf.toString()) }) }) describe('It should send compressed JSON data when `global` is `true` :', async () => { test('using brotli when `Accept-Encoding` request header is `br`', async (t) => { t.plan(1) const fastify = Fastify() await fastify.register(compressPlugin, { global: true, threshold: 0 }) const json = { hello: 'world' } fastify.get('/', (_request, reply) => { reply.compress(json) }) const response = await fastify.inject({ url: '/', method: 'GET', headers: { 'accept-encoding': 'br' } }) const payload = zlib.brotliDecompressSync(response.rawPayload) t.assert.equal(payload.toString('utf-8'), JSON.stringify(json)) }) test('using deflate when `Accept-Encoding` request header is `deflate`', async (t) => { t.plan(1) const fastify = Fastify() await fastify.register(compressPlugin, { global: true, threshold: 0 }) const json = { hello: 'world' } fastify.get('/', (_request, reply) => { reply.compress(json) }) const response = await fastify.inject({ url: '/', method: 'GET', headers: { 'accept-encoding': 'deflate' } }) const payload = zlib.inflateSync(response.rawPayload) t.assert.equal(payload.toString('utf-8'), JSON.stringify(json)) }) test('using gzip when `Accept-Encoding` request header is `gzip`', async (t) => { t.plan(1) const fastify = Fastify() await fastify.register(compressPlugin, { global: true, threshold: 0 }) const json = { hello: 'world' } fastify.get('/', (_request, reply) => { reply.compress(json) }) const response = await fastify.inject({ url: '/', method: 'GET', headers: { 'accept-encoding': 'gzip' } }) const payload = zlib.gunzipSync(response.rawPayload) t.assert.equal(payload.toString('utf-8'), JSON.stringify(json)) }) }) describe('It should fallback to the default `gzip` encoding compression :', async () => { test('when `Accept-Encoding` request header value is set to `*`', async (t) => { t.plan(3) const fastify = Fastify() await fastify.register(compressPlugin, { global: true }) fastify.get('/', (_request, reply) => { reply .type('text/plain') .compress(createReadStream('./package.json')) }) const response = await fastify.inject({ url: '/', method: 'GET', headers: { 'accept-encoding': '*' } }) const file = readFileSync('./package.json', 'utf8') const payload = zlib.gunzipSync(response.rawPayload) t.assert.equal(response.headers.vary, 'accept-encoding') t.assert.equal(response.headers['content-encoding'], 'gzip') t.assert.equal(payload.toString('utf-8'), file) }) test('when `Accept-Encoding` request header value is set to multiple `*` directives', async (t) => { t.plan(3) const fastify = Fastify() await fastify.register(compressPlugin, { global: true }) fastify.get('/', (_request, reply) => { reply .type('text/plain') .compress(createReadStream('./package.json')) }) const response = await fastify.inject({ url: '/', method: 'GET', headers: { 'accept-encoding': '*,*' } }) const file = readFileSync('./package.json', 'utf8') const payload = zlib.gunzipSync(response.rawPayload) t.assert.equal(response.headers.vary, 'accept-encoding') t.assert.equal(response.headers['content-encoding'], 'gzip') t.assert.equal(payload.toString('utf-8'), file) }) }) describe('When a custom `zlib` option is provided, it should compress data :`', async () => { test('using the custom `createBrotliCompress()` method', async (t) => { t.plan(5) let usedCustom = false const customZlib = { createBrotliCompress: () => (usedCustom = true) && zlib.createBrotliCompress() } const fastify = Fastify() await fastify.register(compressPlugin, { global: true, zlib: customZlib }) fastify.get('/', (_request, reply) => { reply .type('text/plain') .compress(createReadStream('./package.json')) }) const response = await fastify.inject({ url: '/', method: 'GET', headers: { 'accept-encoding': 'br' } }) t.assert.equal(usedCustom, true) const file = readFileSync('./package.json', 'utf8') const payload = zlib.brotliDecompressSync(response.rawPayload) t.assert.equal(response.headers.vary, 'accept-encoding') t.assert.equal(response.headers['content-encoding'], 'br') t.assert.ok(!response.headers['content-length'], 'no content length') t.assert.equal(payload.toString('utf-8'), file) }) test('using the custom `createDeflate()` method', async (t) => { t.plan(5) let usedCustom = false const customZlib = { createDeflate: () => (usedCustom = true) && zlib.createDeflate() } const fastify = Fastify() await fastify.register(compressPlugin, { global: true, zlib: customZlib }) fastify.get('/', (_request, reply) => { reply .type('text/plain') .compress(createReadStream('./package.json')) }) const response = await fastify.inject({ url: '/', method: 'GET', headers: { 'accept-encoding': 'deflate' } }) t.assert.equal(usedCustom, true) const file = readFileSync('./package.json', 'utf8') const payload = zlib.inflateSync(response.rawPayload) t.assert.equal(response.headers.vary, 'accept-encoding') t.assert.equal(response.headers['content-encoding'], 'deflate') t.assert.ok(!response.headers['content-length'], 'no content length') t.assert.equal(payload.toString('utf-8'), file) }) test('using the custom `createGzip()` method', async (t) => { t.plan(4) let usedCustom = false const customZlib = { createGzip: () => (usedCustom = true) && zlib.createGzip() } const fastify = Fastify() await fastify.register(compressPlugin, { global: true, zlib: customZlib }) fastify.get('/', (_request, reply) => { reply .type('text/plain') .compress(createReadStream('./package.json')) }) const response = await fastify.inject({ url: '/', method: 'GET', headers: { 'accept-encoding': 'gzip' } }) t.assert.equal(usedCustom, true) const file = readFileSync('./package.json', 'utf8') const payload = zlib.gunzipSync(response.rawPayload) t.assert.equal(response.headers.vary, 'accept-encoding') t.assert.equal(response.headers['content-encoding'], 'gzip') t.assert.equal(payload.toString('utf-8'), file) }) test('using the custom `createZstdCompress()` method', async (t) => { if (typeof zlib.createZstdCompress !== 'function') { t.skip('zstd not supported in this Node.js version') return } t.plan(4) let usedCustom = false const customZlib = { createZstdCompress: () => (usedCustom = true) && zlib.createZstdCompress() } const fastify = Fastify() await fastify.register(compressPlugin, { global: true, zlib: customZlib }) fastify.get('/', (_request, reply) => { reply .type('text/plain') .compress(createReadStream('./package.json')) }) const response = await fastify.inject({ url: '/', method: 'GET', headers: { 'accept-encoding': 'zstd' } }) t.assert.equal(usedCustom, true) const file = readFileSync('./package.json', 'utf8') const payload = zlib.zstdDecompressSync(response.rawPayload) t.assert.equal(response.headers.vary, 'accept-encoding') t.assert.equal(response.headers['content-encoding'], 'zstd') t.assert.equal(payload.toString('utf-8'), file) }) }) describe('When a malformed custom `zlib` option is provided, it should compress data :', async () => { test('using the fallback default Node.js core `zlib.createBrotliCompress()` method', async (t) => { t.plan(1) const fastify = Fastify() await fastify.register(compressPlugin, { global: true, threshold: 0, zlib: true // will trigger a fallback on the default zlib.createBrotliCompress }) fastify.get('/', (_request, reply) => { reply .type('text/plain') .compress('hello') }) const response = await fastify.inject({ url: '/', method: 'GET', headers: { 'accept-encoding': 'br' } }) const payload = zlib.brotliDecompressSync(response.rawPayload) t.assert.equal(payload.toString('utf-8'), 'hello') }) test('using the fallback default Node.js core `zlib.createDeflate()` method', async (t) => { t.plan(1) const fastify = Fastify() await fastify.register(compressPlugin, { global: true, threshold: 0, zlib: true // will trigger a fallback on the default zlib.createDeflate }) fastify.get('/', (_request, reply) => { reply .type('text/plain') .compress('hello') }) const response = await fastify.inject({ url: '/', method: 'GET', headers: { 'accept-encoding': 'deflate' } }) const payload = zlib.inflateSync(response.rawPayload) t.assert.equal(payload.toString('utf-8'), 'hello') }) test('using the fallback default Node.js core `zlib.createGzip()` method', async (t) => { t.plan(1) const fastify = Fastify() await fastify.register(compressPlugin, { global: true, threshold: 0, zlib: true // will trigger a fallback on the default zlib.createGzip }) fastify.get('/', (_request, reply) => { reply .type('text/plain') .compress('hello') }) const response = await fastify.inject({ url: '/', method: 'GET', headers: { 'accept-encoding': 'gzip' } }) const payload = zlib.gunzipSync(response.rawPayload) t.assert.equal(payload.toString('utf-8'), 'hello') }) test('using the fallback default Node.js core `zlib.createZstdCompress()` method', async (t) => { if (typeof zlib.createZstdCompress !== 'function') { t.skip('zstd not supported in this Node.js version') return } t.plan(1) const fastify = Fastify() await fastify.register(compressPlugin, { global: true, threshold: 0, zlib: true // will trigger a fallback on the default zlib.createZstdCompress }) fastify.get('/', (_request, reply) => { reply .type('text/plain') .compress('hello') }) const response = await fastify.inject({ url: '/', method: 'GET', headers: { 'accept-encoding': 'zstd' } }) const payload = zlib.zstdDecompressSync(response.rawPayload) t.assert.equal(payload.toString('utf-8'), 'hello') }) }) describe('When `inflateIfDeflated` is `true` and `X-No-Compression` request header is `true` :', async () => { test('it should uncompress payloads using the deflate algorithm', async (t) => { t.plan(4) const fastify = Fastify() await fastify.register(compressPlugin, { threshold: 0, inflateIfDeflated: true }) const json = { hello: 'world' } fastify.get('/', (_request, reply) => { reply.send(zlib.deflateSync(JSON.stringify(json))) }) const response = await fastify.inject({ url: '/', method: 'GET', headers: { 'x-no-compression': true } }) t.assert.equal(response.statusCode, 200) t.assert.ok(!response.headers.vary) t.assert.ok(!response.headers['content-encoding']) t.assert.deepEqual(JSON.parse('' + response.payload), json) }) test('it should uncompress payloads using the gzip algorithm', async (t) => { t.plan(4) const fastify = Fastify() await fastify.register(compressPlugin, { threshold: 0, inflateIfDeflated: true }) const json = { hello: 'world' } fastify.get('/', (_request, reply) => { reply.send(zlib.gzipSync(JSON.stringify(json))) }) const response = await fastify.inject({ url: '/', method: 'GET', headers: { 'x-no-compression': true } }) t.assert.equal(response.statusCode, 200) t.assert.ok(!response.headers.vary) t.assert.ok(!response.headers['content-encoding']) t.assert.deepEqual(JSON.parse('' + response.payload), json) }) }) test('it should not uncompress payloads using the zip algorithm', async (t) => { t.plan(5) const fastify = Fastify() await fastify.register(compressPlugin, { threshold: 0, inflateIfDeflated: true }) const json = { hello: 'world' } const zip = new AdmZip() zip.addFile('file.zip', Buffer.from(JSON.stringify(json), 'utf-8')) const fileBuffer = zip.toBuffer() fastify.get('/', (_request, reply) => { reply.compress(fileBuffer) }) const response = await fastify.inject({ url: '/', method: 'GET', headers: { 'x-no-compression': true } }) t.assert.equal(response.statusCode, 200) t.assert.ok(!response.headers.vary) t.assert.ok(!response.headers['content-encoding']) t.assert.deepEqual(response.rawPayload, fileBuffer) t.assert.equal(response.payload, fileBuffer.toString('utf-8')) }) describe('It should not compress :', async () => { describe('Using `reply.compress()` :', async () => { test('when payload length is smaller than the `threshold` defined value', async (t) => { t.plan(4) const fastify = Fastify() await fastify.register(compressPlugin, { threshold: 128 }) fastify.get('/', (_request, reply) => { reply .type('text/plain') .compress('a message') }) const response = await fastify.inject({ url: '/', method: 'GET', headers: { 'accept-encoding': 'deflate' } }) t.assert.equal(response.statusCode, 200) t.assert.ok(!response.headers.vary) t.assert.ok(!response.headers['content-encoding']) t.assert.equal(response.payload, 'a message') }) test('when `customTypes` is set and does not match `Content-Type` reply header or `mime-db`', async (t) => { t.plan(3) const fastify = Fastify() await fastify.register(compressPlugin, { customTypes: /x-user-header$/u }) fastify.get('/', (_request, reply) => { reply .type('application/x-other-type') .compress(createReadStream('./package.json')) }) const response = await fastify.inject({ url: '/', method: 'GET', headers: { 'accept-encoding': 'gzip' } }) t.assert.ok(!response.headers.vary, 'accept-encoding') t.assert.ok(!response.headers['content-encoding']) t.assert.equal(response.statusCode, 200) }) test('when `customTypes` is a function and returns false on the provided `Content-Type` reply header`', async (t) => { t.plan(3) const fastify = Fastify() await fastify.register(compressPlugin, { customTypes: value => value === 'application/x-user-header' }) fastify.get('/', (_request, reply) => { reply .type('application/x-other-type') .compress(createReadStream('./package.json')) }) const response = await fastify.inject({ url: '/', method: 'GET', headers: { 'accept-encoding': 'gzip' } }) t.assert.ok(!response.headers.vary) t.assert.ok(!response.headers['content-encoding']) t.assert.equal(response.statusCode, 200) }) test('when `X-No-Compression` request header is `true`', async (t) => { t.plan(4) const fastify = Fastify() await fastify.register(compressPlugin, { global: true, threshold: 0 }) const json = { hello: 'world' } fastify.get('/', (_request, reply) => { reply.compress(json) }) const response = await fastify.inject({ url: '/', method: 'GET', headers: { 'x-no-compression': true } }) t.assert.equal(response.statusCode, 200) t.assert.ok(!response.headers.vary) t.assert.ok(!response.headers['content-encoding']) t.assert.deepEqual(JSON.parse(response.payload), json) }) test('when `Content-Type` reply header is not set and the content is not detected as a compressible type', async (t) => { t.plan(3) const fastify = Fastify() await fastify.register(compressPlugin, { threshold: 0 }) fastify.addHook('onSend', async (_request, response) => { response.header('Content-Type', undefined) }) const json = { hello: 'world' } fastify.get('/', (_request, reply) => { // The auto-dectection will fallback as an 'application/json' type reply.compress(json) }) const response = await fastify.inject({ url: '/', method: 'GET', headers: { accept: 'application/json', 'accept-encoding': 'identity' } }) t.assert.ok(!response.headers.vary) t.assert.ok(!response.headers['content-encoding']) t.assert.equal(response.payload, JSON.stringify(json)) }) test('when `Content-Type` reply header is a mime type with undefined compressible values', async (t) => { t.plan(4) const fastify = Fastify() await fastify.register(compressPlugin, { threshold: 0 }) fastify.get('/', (_request, reply) => { reply .type('image/webp') .compress('hello') }) const response = await fastify.inject({ url: '/', method: 'GET', headers: { 'accept-encoding': 'gzip, deflate, br' } }) t.assert.equal(response.statusCode, 200) t.assert.ok(!response.headers.vary) t.assert.ok(!response.headers['content-encoding']) t.assert.equal(response.payload, 'hello') }) test('when `Content-Type` reply header value is `text/event-stream`', async (t) => { t.plan(4) const fastify = Fastify() await fastify.register(compressPlugin, { threshold: 0 }) fastify.get('/', (_req, reply) => { const stream = new PassThrough() reply .type('text/event-stream') .compress(stream) stream.write('event: open\n\n') stream.write('event: change\ndata: schema\n\n') stream.end() }) const response = await fastify.inject({ url: '/', method: 'GET' }) t.assert.equal(response.statusCode, 200) t.assert.ok(!response.headers.vary) t.assert.ok(!response.headers['content-encoding']) t.assert.deepEqual(response.payload, 'event: open\n\nevent: change\ndata: schema\n\n') }) test('when `Content-Type` reply header value is an invalid type', async (t) => { t.plan(4) const fastify = Fastify() await fastify.register(compressPlugin, { threshold: 0 }) fastify.get('/', (_request, reply) => { reply .type('something/invalid') .compress('a message') }) const response = await fastify.inject({ url: '/', method: 'GET', headers: { 'accept-encoding': 'deflate' } }) t.assert.equal(response.statusCode, 200) t.assert.ok(!response.headers.vary) t.assert.ok(!response.headers['content-encoding']) t.assert.equal(response.payload, 'a message') }) test('when `Accept-Encoding` request header is missing', async (t) => { t.plan(3) const fastify = Fastify() await fastify.register(compressPlugin, { global: true }) fastify.get('/', (_request, reply) => { reply .type('text/plain') .compress(createReadStream('./package.json')) }) const response = await fastify.inject({ url: '/', method: 'GET' }) t.assert.equal(response.statusCode, 200) t.assert.ok(!response.headers.vary) t.assert.ok(!response.headers['content-encoding']) }) test('when `Accept-Encoding` request header is set to `identity`', async (t) => { t.plan(3) const fastify = Fastify() await fastify.register(compressPlugin, { global: true, threshold: 0 }) fastify.get('/', (_request, reply) => { reply.compress({ hello: 'world' }) }) const response = await fastify.inject({ url: '/', method: 'GET', headers: { 'accept-encoding': 'identity' } }) const payload = JSON.parse(response.payload) t.assert.ok(!response.headers.vary) t.assert.ok(!response.headers['content-encoding']) t.assert.deepEqual({ hello: 'world' }, payload) }) test('when `Accept-Encoding` request header value is not supported', async (t) => { t.plan(3) const fastify = Fastify() await fastify.register(compressPlugin, { global: true }) fastify.get('/', (_request, reply) => { reply .type('text/plain') .compress(createReadStream('./package.json')) }) const response = await fastify.inject({ url: '/', method: 'GET', headers: { 'accept-encoding': 'invalid' } }) const file = readFileSync('./package.json', 'utf8') t.assert.equal(response.statusCode, 200) t.assert.ok(!response.headers.vary) t.assert.equal(response.payload, file) }) test('when `Accept-Encoding` request header value is not supported (with quality value)', async (t) => { t.plan(3) const fastify = Fastify() await fastify.register(compressPlugin, { global: true }) fastify.get('/', (_request, reply) => { reply .type('text/plain') .compress(createReadStream('./package.json')) }) const response = await fastify.inject({ url: '/', method: 'GET', headers: { 'accept-encoding': 'lzma;q=1.0' } }) const file = readFileSync('./package.json', 'utf8') t.assert.equal(response.statusCode, 200) t.assert.ok(!response.headers.vary) t.assert.equal(response.payload, file) }) test('when `Accept-Encoding` request header is set to `identity and `inflateIfDeflated` is `true``', async (t) => { t.plan(3) const fastify = Fastify() await fastify.register(compressPlugin, { global: true, inflateIfDeflated: true, threshold: 0 }) fastify.get('/', (_request, reply) => { reply.compress({ hello: 'world' }) }) const response = await fastify.inject({ url: '/', method: 'GET', headers: { 'accept-encoding': 'identity' } }) const payload = JSON.parse(response.payload) t.assert.ok(!response.headers.vary) t.assert.ok(!response.headers['content-encoding']) t.assert.deepEqual({ hello: 'world' }, payload) }) }) describe('Using `onSend` hook :', async () => { test('when there is no payload', async (t) => { t.plan(4) const fastify = Fastify() await fastify.register(compressPlugin, { threshold: 0 }) fastify.get('/', (_request, reply) => { reply.send(undefined) }) const response = await fastify.inject({ url: '/', method: 'GET', headers: { 'accept-encoding': 'gzip' } }) t.assert.equal(response.statusCode, 200) t.assert.ok(!response.headers.vary) t.assert.ok(!response.headers['content-encoding']) t.assert.equal(response.payload, '') }) test('when payload length is smaller than the `threshold` defined value', async (t) => { t.plan(4) const fastify = Fastify() await fastify.register(compressPlugin, { threshold: 128 }) fastify.get('/', (_request, reply) => { reply .header('Content-Type', 'text/plain') .send('a message') }) const response = await fastify.inject({ url: '/', method: 'GET', headers: { 'accept-encoding': 'deflate' } }) t.assert.equal(response.statusCode, 200) t.assert.ok(!response.headers.vary) t.assert.ok(!response.headers['content-encoding']) t.assert.equal(response.payload, 'a message') }) test('when `customTypes` is set and does not match `Content-Type` reply header or `mime-db`', async (t) => { t.plan(3) const fastify = Fastify() await fastify.register(compressPlugin, { customTypes: /x-user-header$/u }) fastify.get('/', (_request, reply) => { reply .type('application/x-other-type') .send(createReadStream('./package.json')) }) const response = await fastify.inject({ url: '/', method: 'GET', headers: { 'accept-encoding': 'gzip' } }) t.assert.ok(!response.headers.vary) t.assert.ok(!response.headers['content-encoding']) t.assert.equal(response.statusCode, 200) }) test('when `X-No-Compression` request header is `true`', async (t) => { t.plan(4) const fastify = Fastify() await fastify.register(compressPlugin, { threshold: 0 }) const json = { hello: 'world' } fastify.get('/', (_request, reply) => { reply.send(json) }) const response = await fastify.inject({ url: '/', method: 'GET', headers: { 'x-no-compression': true } }) t.assert.equal(response.statusCode, 200) t.assert.ok(!response.headers.vary) t.assert.ok(!response.headers['content-encoding']) t.assert.deepEqual(JSON.parse(response.payload), json) }) test('when `Content-Type` reply header is not set and the content is not detected as a compressible type', async (t) => { t.plan(3) const fastify = Fastify() await fastify.register(compressPlugin, { threshold: 0 }) fastify.addHook('onSend', async (_request, response) => { response.header('Content-Type', undefined) }) const json = { hello: 'world' } fastify.get('/', (_request, reply) => { // The auto-dectection will fallback as an 'application/json' type reply.send(json) }) const response = await fastify.inject({ url: '/', method: 'GET', headers: { accept: 'application/json', 'accept-encoding': 'identity' } }) t.assert.ok(!response.headers.vary) t.assert.ok(!response.headers['content-encoding']) t.assert.equal(response.payload, JSON.stringify(json)) }) test('when `Content-Type` reply header is a mime type with undefined compressible values', async (t) => { t.plan(4) const fastify = Fastify() await fastify.register(compressPlugin, { threshold: 0 }) fastify.get('/', (_request, reply) => { reply .type('image/webp') .send('hello') }) const response = await fastify.inject({ url: '/', method: 'GET', headers: { 'accept-encoding': 'gzip, deflate, br' } }) t.assert.equal(response.statusCode, 200) t.assert.ok(!response.headers.vary) t.assert.ok(!response.headers['content-encoding']) t.assert.equal(response.payload, 'hello') }) test('when `Content-Type` reply header value is `text/event-stream`', async (t) => { t.plan(4) const fastify = Fastify() await fastify.register(compressPlugin, { threshold: 0 }) fastify.get('/', (_req, reply) => { const stream = new PassThrough() reply .header('Content-Type', 'text/event-stream') .send(stream) stream.write('event: open\n\n') stream.write('event: change\ndata: schema\n\n') stream.end() }) const response = await fastify.inject({ url: '/', method: 'GET' }) t.assert.equal(response.statusCode, 200) t.assert.ok(!response.headers.vary) t.assert.ok(!response.headers['content-encoding']) t.assert.deepEqual(response.payload, 'event: open\n\nevent: change\ndata: schema\n\n') }) test('when `Content-Type` reply header value is an invalid type', async (t) => { t.plan(4) const fastify = Fastify() await fastify.register(compressPlugin, { threshold: 0 }) fastify.get('/', (_request, reply) => { reply .header('Content-Type', 'something/invalid') .send('a message') }) const response = await fastify.inject({ url: '/', method: 'GET', headers: { 'accept-encoding': 'deflate' } }) t.assert.equal(response.statusCode, 200) t.assert.ok(!response.headers.vary) t.assert.ok(!response.headers['content-encoding']) t.assert.equal(response.payload, 'a message') }) test('when `Accept-Encoding` request header is missing', async (t) => { t.plan(3) const fastify = Fastify() await fastify.register(compressPlugin, { global: true }) fastify.get('/', (_request, reply) => { reply .header('Content-Type', 'text/plain') .send(createReadStream('./package.json')) }) const response = await fastify.inject({ url: '/', method: 'GET' }) t.assert.equal(response.statusCode, 200) t.assert.ok(!response.headers.vary) t.assert.ok(!response.headers['content-encoding']) }) test('when `Accept-Encoding` request header is set to `identity`', async (t) => { t.plan(3) const fastify = Fastify() await fastify.register(compressPlugin, { global: true, threshold: 0 }) fastify.get('/', (_request, reply) => { reply.send({ hello: 'world' }) }) const response = await fastify.inject({ url: '/', method: 'GET', headers: { 'accept-encoding': 'identity' } }) const payload = JSON.parse(response.payload) t.assert.ok(!response.headers.vary) t.assert.ok(!response.headers['content-encoding']) t.assert.deepEqual({ hello: 'world' }, payload) }) test('when `Accept-Encoding` request header value is not supported', async (t) => { t.plan(2) const fastify = Fastify() await fastify.register(compressPlugin, { global: true }) fastify.get('/', (_request, reply) => { reply .header('Content-Type', 'text/plain') .send('something') }) const response = await fastify.inject({ url: '/', method: 'GET', headers: { 'accept-encoding': 'invalid' } }) t.assert.equal(response.statusCode, 200) t.assert.equal(response.payload, 'something') }) test('when `Accept-Encoding` request header value is not supported (with quality value)', async (t) => { t.plan(3) const fastify = Fastify() await fastify.register(compressPlugin, { global: true }) fastify.get('/', (_request, reply) => { reply .header('Content-Type', 'text/plain') .send(createReadStream('./package.json')) }) const response = await fastify.inject({ url: '/', method: 'GET', headers: { 'accept-encoding': 'lzma;q=1.0' } }) const file = readFileSync('./package.json', 'utf8') t.assert.equal(response.statusCode, 200) t.assert.ok(!response.headers.vary) t.assert.equal(response.payload, file) }) test('when `Accept-Encoding` request header is set to `identity and `inflateIfDeflated` is `true``', async (t) => { t.plan(3) const fastify = Fastify() await fastify.register(compressPlugin, { global: true, inflateIfDeflated: true, threshold: 0 }) fastify.get('/', (_request, reply) => { reply.send({ hello: 'world' }) }) const response = await fastify.inject({ url: '/', method: 'GET', headers: { 'accept-encoding': 'identity' } }) const payload = JSON.parse(response.payload) t.assert.ok(!response.headers.vary) t.assert.ok(!response.headers['content-encoding']) t.assert.deepEqual({ hello: 'world' }, payload) }) }) }) describe('It should not double-compress :', async () => { test('when using `reply.compress()` to send an already deflated Stream', async (t) => { t.plan(3) const fastify = Fastify() await fastify.register(compressPlugin, { global: true }) fastify.get('/', (_request, reply) => { reply .type('text/plain') .compress( createReadStream('./package.json').pipe(zlib.createDeflate()) ) }) const response = await fastify.inject({ url: '/', method: 'GET', headers: { 'accept-encoding': 'deflate' } }) const file = readFileSync('./package.json', 'utf8') const payload = zlib.inflateSync(response.rawPayload) t.assert.equal(response.headers.vary, 'accept-encoding') t.assert.equal(response.headers['content-encoding'], 'deflate') t.assert.equal(payload.toString('utf-8'), file) }) test('when using `reply.compress()` to send an already gzipped Stream', async (t) => { t.plan(3) const fastify = Fastify() await fastify.register(compressPlugin, { global: true }) fastify.get('/', (_request, reply) => { reply .type('text/plain') .compress( createReadStream('./package.json').pipe(zlib.createGzip()) ) }) const response = await fastify.inject({ url: '/', method: 'GET', headers: { 'accept-encoding': 'gzip' } }) const file = readFileSync('./package.json', 'utf8') const payload = zlib.gunzipSync(response.rawPayload) t.assert.equal(response.headers.vary, 'accept-encoding') t.assert.equal(response.headers['content-encoding'], 'gzip') t.assert.equal(payload.toString('utf-8'), file) }) test('when using `onSend` hook to send an already brotli compressed Stream', async (t) => { t.plan(4) const fastify = Fastify() await fastify.register(compressPlugin, { global: true, threshold: 0 }) const file = readFileSync('./package.json', 'utf8') fastify.get('/', (_request, reply) => { const payload = zlib.brotliCompressSync(file) reply .type('application/json') .header('content-encoding', 'br') .header('content-length', payload.length) .send(payload) }) const response = await fastify.inject({ url: '/', method: 'GET', headers: { 'accept-encoding': 'br' } }) const payload = zlib.brotliDecompressSync(response.rawPayload) t.assert.ok(!response.headers.vary) t.assert.equal(response.headers['content-encoding'], 'br') t.assert.equal(response.headers['content-length'], response.rawPayload.length.toString()) t.assert.equal(payload.toString('utf-8'), file) }) test('when using `onSend` hook to send an already deflated Stream', async (t) => { t.plan(4) const fastify = Fastify() await fastify.register(compressPlugin, { global: true, threshold: 0 }) const file = readFileSync('./package.json', 'utf8') fastify.get('/', (_request, reply) => { const payload = zlib.deflateSync(file) reply .type('application/json') .header('content-encoding', 'deflate') .header('content-length', payload.length) .send(payload) }) const response = await fastify.inject({ url: '/', method: 'GET', headers: { 'accept-encoding': 'deflate' } }) const payload = zlib.inflateSync(response.rawPayload) t.assert.ok(!response.headers.vary) t.assert.equal(response.headers['content-encoding'], 'deflate') t.assert.equal(response.headers['content-length'], response.rawPayload.length.toString()) t.assert.equal(payload.toString('utf-8'), file) }) test('when using `onSend` hook to send an already gzipped Stream', async (t) => { t.plan(4) const fastify = Fastify() await fastify.register(compressPlugin, { global: true, threshold: 0 }) const file = readFileSync('./package.json', 'utf8') fastify.get('/', (_request, reply) => { const payload = zlib.gzipSync(file) reply .type('application/json') .header('conte