@fastify/compress
Version:
Fastify compression utils
1,681 lines (1,400 loc) • 95.9 kB
JavaScript
'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(