UNPKG

@fastify/compress

Version:
1,681 lines (1,400 loc) 95.9 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') 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 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 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') }) }) 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) }) }) 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') }) }) 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('content-encoding', 'gzip') .header('content-length', payload.length) .send(payload) }) const response = await fastify.inject({ url: '/', method: 'GET', headers: { 'accept-encoding': 'gzip' } }) const payload = zlib.gunzipSync(response.rawPayload) t.assert.ok(!response.headers.vary) t.assert.equal(response.headers['content-encoding'], 'gzip') t.assert.equal(response.headers['content-length'], response.rawPayload.length.toString()) t.assert.equal(payload.toString('utf-8'), file) }) }) describe('It should not compress Stream data and add a `Content-Encoding` reply header :', async () => { describe('Using `onSend` hook if `Accept-Encoding` request header value is `identity`', async () => { test('when `inflateIfDeflated` is `true` and `encodings` is not set', async (t) => { t.plan(4) const fastify = Fastify() await fastify.register(compressPlugin, { global: true, inflateIfDeflated: true }) fastify.get('/', (_request, reply) => { reply .header('Content-Type', 'application/octet-stream') .send(createReadStream('./package.json')) }) const response = await fastify.inject({ url: '/', method: 'GET', headers: { 'accept-encoding': 'identity' } }) const file = readFileSync('./package.json', 'utf8') t.assert.equal(response.statusCode, 200) t.assert.ok(!response.headers.vary) t.assert.equal(response.headers['content-encoding'], 'identity') t.assert.equal(file, response.payload) }) test('when `inflateIfDeflated` is `true` and `encodings` is set', async (t) => { t.plan(4) const fastify = Fastify() await fastify.register(compressPlugin, { global: true, inflateIfDeflated: true, encodings: ['deflate', 'gzip'] }) fastify.get('/', (_request, reply) => { reply.send(createReadStream('./package.json')) }) const response = await fastify.inject({ url: '/', method: 'GET', headers: { accept: 'application/json', 'accept-encoding': 'identity' } }) const file = readFileSync('./package.json', 'utf-8') t.assert.equal(response.statusCode, 200) t.assert.ok(!response.headers.vary) t.assert.equal(response.headers['content-encoding'], 'identity') t.assert.deepEqual(response.payload, file) }) }) describe('Using `reply.compress()` if `Accept-Encoding` request header value is `identity`', async () => { test('when `inflateIfDeflated` is `true` and `encodings` is not set', async (t) => { t.plan(4) const fastify = Fastify() await fastify.register(compressPlugin, { global: true, inflateIfDeflated: true }) fastify.get('/', (_request, reply) => { reply .type('application/octet-stream') .compress(createReadStream('./package.json')) }) const response = await fastify.inject({ url: '/', method: 'GET', headers: { 'accept-encoding': 'identity' } }) const file = readFileSync('./package.json', 'utf8') t.assert.equal(response.statusCode, 200) t.assert.ok(!response.headers.vary) t.assert.equal(response.headers['content-encoding'], 'identity') t.assert.equal(file, response.payload) }) test('when `inflateIfDeflated` is `true` and `encodings` is set', async (t) => { t.plan(4) const fastify = Fastify() await fastify.register(compressPlugin, { global: true, inflateIfDeflated: true, encodings: ['deflate', 'gzip'] }) fastify.get('/', (_request, reply) => { reply.compress(createReadStream('./package.json')) }) const response = await fastify.inject({ url: '/', method: 'GET', headers: { accept: 'application/json', 'accept-encoding': 'identity' } }) const file = readFileSync('./package.json', 'utf-8') t.assert.equal(response.statusCode, 200) t.assert.ok(!response.headers.vary) t.assert.equal(response.headers['content-encoding'], 'identity') t.assert.deepEqual(response.payload, file) }) }) }) test('It should return a serialized payload when `inflateIfDeflated` is `true` and `X-No-Compression` request header is `true`', async (t) => { t.plan(8) const fastify = Fastify() await fastify.register(compressPlugin, { global: true, inflateIfDeflated: true, threshold: 0 }) const json = { hello: 'world' } const compressedBufferPayload = zlib.brotliCompressSync(Buffer.from(json.toString())) fastify.get('/one', (_request, reply) => { reply.send(json) }) fastify.get('/two', (_request, reply) => { reply.send(compressedBufferPayload) }) const one = await fastify.inject(