@fastify/compress 9.1.1 → 9.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@fastify/compress",
3
- "version": "9.1.1",
3
+ "version": "9.2.0",
4
4
  "description": "Fastify compression utils",
5
5
  "main": "index.js",
6
6
  "type": "commonjs",
@@ -26,6 +26,7 @@
26
26
  "scripts": {
27
27
  "lint": "eslint",
28
28
  "lint:fix": "eslint --fix",
29
+ "benchmark": "node benchmark/sync-compression.mjs",
29
30
  "test": "npm run test:unit && npm run test:typescript",
30
31
  "test:typescript": "tstyche",
31
32
  "test:unit": "node --test",
@@ -318,6 +318,98 @@ describe('When `global` is not set, it is `true` by default :', async () => {
318
318
  t.assert.equal(zlib.gunzipSync(response.rawPayload).toString('utf-8'), body)
319
319
  })
320
320
 
321
+ test('it should not compress a Fetch API Response holding a non-compressible `Content-Type`', async (t) => {
322
+ t.plan(2)
323
+
324
+ const fastify = Fastify()
325
+ await fastify.register(compressPlugin, { threshold: 0 })
326
+
327
+ const body = 'data: hello from fetch response\n\n'
328
+ fastify.get('/fetch-resp', (_request, reply) => {
329
+ reply.send(new Response(body, { headers: { 'content-type': 'text/event-stream' } }))
330
+ })
331
+
332
+ const response = await fastify.inject({
333
+ url: '/fetch-resp',
334
+ method: 'GET',
335
+ headers: { 'accept-encoding': 'gzip' }
336
+ })
337
+ t.assert.equal(response.headers['content-encoding'], undefined)
338
+ t.assert.equal(response.rawPayload.toString('utf-8'), body)
339
+ })
340
+
341
+ test('it should not compress a Fetch API Response that is already compressed', async (t) => {
342
+ t.plan(2)
343
+
344
+ const fastify = Fastify()
345
+ await fastify.register(compressPlugin, { threshold: 0 })
346
+
347
+ const body = 'hello from fetch response'
348
+ fastify.get('/fetch-resp', (_request, reply) => {
349
+ reply.send(new Response(zlib.brotliCompressSync(body), {
350
+ headers: {
351
+ 'content-type': 'text/plain',
352
+ 'content-encoding': 'br'
353
+ }
354
+ }))
355
+ })
356
+
357
+ const response = await fastify.inject({
358
+ url: '/fetch-resp',
359
+ method: 'GET',
360
+ headers: { 'accept-encoding': 'gzip' }
361
+ })
362
+ t.assert.equal(response.headers['content-encoding'], 'br')
363
+ t.assert.equal(zlib.brotliDecompressSync(response.rawPayload).toString('utf-8'), body)
364
+ })
365
+
366
+ test('it should not compress a partial Fetch API Response', async (t) => {
367
+ t.plan(3)
368
+
369
+ const fastify = Fastify()
370
+ await fastify.register(compressPlugin, { threshold: 0 })
371
+
372
+ const body = 'hello fr'
373
+ fastify.get('/fetch-resp', (_request, reply) => {
374
+ reply.send(new Response(body, {
375
+ status: 206,
376
+ headers: {
377
+ 'content-type': 'text/plain',
378
+ 'content-range': 'bytes 0-7/25'
379
+ }
380
+ }))
381
+ })
382
+
383
+ const response = await fastify.inject({
384
+ url: '/fetch-resp',
385
+ method: 'GET',
386
+ headers: { 'accept-encoding': 'gzip' }
387
+ })
388
+ t.assert.equal(response.statusCode, 206)
389
+ t.assert.equal(response.headers['content-encoding'], undefined)
390
+ t.assert.equal(response.rawPayload.toString('utf-8'), body)
391
+ })
392
+
393
+ test('it should send a bodyless Fetch API Response as is', async (t) => {
394
+ t.plan(3)
395
+
396
+ const fastify = Fastify()
397
+ await fastify.register(compressPlugin, { threshold: 0 })
398
+
399
+ fastify.get('/fetch-resp', (_request, reply) => {
400
+ reply.send(new Response(null, { status: 204, headers: { 'x-custom': 'kept' } }))
401
+ })
402
+
403
+ const response = await fastify.inject({
404
+ url: '/fetch-resp',
405
+ method: 'GET',
406
+ headers: { 'accept-encoding': 'gzip' }
407
+ })
408
+ t.assert.equal(response.statusCode, 204)
409
+ t.assert.equal(response.headers['x-custom'], 'kept')
410
+ t.assert.equal(response.rawPayload.length, 0)
411
+ })
412
+
321
413
  test('it should compress a Web ReadableStream body', async (t) => {
322
414
  t.plan(1)
323
415
 
@@ -2035,11 +2127,14 @@ describe('It should support stream1 :', async () => {
2035
2127
  })
2036
2128
 
2037
2129
  describe('It should remove `Content-Length` header :', async () => {
2130
+ // `./package.json` is small enough to be compressed synchronously, and the synchronous
2131
+ // path knows the compressed size, so it replaces the stale `Content-Length` with an
2132
+ // accurate one instead of removing it. `syncThreshold: 0` restores the streamed behaviour.
2038
2133
  test('using `reply.compress()`', async (t) => {
2039
2134
  t.plan(4)
2040
2135
 
2041
2136
  const fastify = Fastify()
2042
- await fastify.register(compressPlugin, { global: true })
2137
+ await fastify.register(compressPlugin, { global: true, syncThreshold: 0 })
2043
2138
 
2044
2139
  fastify.get('/', (_request, reply) => {
2045
2140
  readFile('./package.json', 'utf8', (err, data) => {
@@ -2099,7 +2194,7 @@ describe('It should remove `Content-Length` header :', async () => {
2099
2194
  t.plan(4)
2100
2195
 
2101
2196
  const fastify = Fastify()
2102
- await fastify.register(compressPlugin, { global: true })
2197
+ await fastify.register(compressPlugin, { global: true, syncThreshold: 0 })
2103
2198
 
2104
2199
  fastify.get('/', (_request, reply) => {
2105
2200
  readFile('./package.json', 'utf8', (err, data) => {
@@ -2851,6 +2946,70 @@ describe('It should send data compressed according to `brotliOptions` :', async
2851
2946
  const compressedPayload = zlib.brotliCompressSync(file, defaultBrotliOptions)
2852
2947
  t.assert.deepEqual(response.rawPayload, compressedPayload)
2853
2948
  })
2949
+
2950
+ test('default BROTLI_PARAM_QUALITY to be 4 when `global` is `false`', async (t) => {
2951
+ t.plan(2)
2952
+
2953
+ const fastify = Fastify()
2954
+ await fastify.register(compressPlugin, { global: false })
2955
+
2956
+ const file = readFileSync('./package.json', 'utf8')
2957
+ fastify.get('/', (_request, reply) => {
2958
+ reply
2959
+ .type('text/plain')
2960
+ .compress(file)
2961
+ })
2962
+
2963
+ const response = await fastify.inject({
2964
+ url: '/',
2965
+ method: 'GET',
2966
+ headers: {
2967
+ 'accept-encoding': 'br'
2968
+ }
2969
+ })
2970
+
2971
+ const defaultBrotliOptions = {
2972
+ params: { [zlib.constants.BROTLI_PARAM_QUALITY]: 4 }
2973
+ }
2974
+ const compressedPayload = zlib.brotliCompressSync(file, defaultBrotliOptions)
2975
+ t.assert.deepEqual(response.rawPayload, compressedPayload)
2976
+
2977
+ // zlib's own default of quality 11 is far more expensive and must not be used
2978
+ const maxQualityPayload = zlib.brotliCompressSync(file)
2979
+ t.assert.notDeepEqual(response.rawPayload, maxQualityPayload)
2980
+ })
2981
+
2982
+ test('user supplied `brotliOptions` still take precedence when `global` is `false`', async (t) => {
2983
+ t.plan(1)
2984
+
2985
+ const brotliOptions = {
2986
+ params: {
2987
+ [zlib.constants.BROTLI_PARAM_MODE]: zlib.constants.BROTLI_MODE_TEXT,
2988
+ [zlib.constants.BROTLI_PARAM_QUALITY]: 8
2989
+ }
2990
+ }
2991
+
2992
+ const fastify = Fastify()
2993
+ await fastify.register(compressPlugin, { global: false, brotliOptions })
2994
+
2995
+ const file = readFileSync('./package.json', 'utf8')
2996
+ fastify.get('/', (_request, reply) => {
2997
+ reply
2998
+ .type('text/plain')
2999
+ .compress(file)
3000
+ })
3001
+
3002
+ const response = await fastify.inject({
3003
+ url: '/',
3004
+ method: 'GET',
3005
+ headers: {
3006
+ 'accept-encoding': 'br'
3007
+ }
3008
+ })
3009
+
3010
+ const compressedPayload = zlib.brotliCompressSync(file, brotliOptions)
3011
+ t.assert.deepEqual(response.rawPayload, compressedPayload)
3012
+ })
2854
3013
  })
2855
3014
 
2856
3015
  describe('It should send data compressed according to `zlibOptions` :', async () => {
@@ -22,7 +22,9 @@ async function routes (fastify) {
22
22
 
23
23
  test('should compress large payload without premature close', async (t) => {
24
24
  const fastify = Fastify()
25
- await fastify.register(fastifyCompress, { encodings: ['gzip'], global: true })
25
+ // `syncThreshold: 0` keeps this payload on the stream pipeline this regression covers,
26
+ // whatever default the host parallelism would otherwise produce
27
+ await fastify.register(fastifyCompress, { encodings: ['gzip'], global: true, syncThreshold: 0 })
26
28
  await fastify.register(routes)
27
29
 
28
30
  const response = await fastify.inject({
@@ -381,6 +381,32 @@ test('reply.compress should preserve a Fetch Response status and headers', async
381
381
  t.assert.equal(zlib.gunzipSync(res.rawPayload).toString('utf8'), 'from reply.compress')
382
382
  })
383
383
 
384
+ test('reply.compress should not compress a Fetch Response holding a non-compressible `Content-Type`', async (t) => {
385
+ t.plan(2)
386
+ const fastify = Fastify()
387
+ await fastify.register(compressPlugin, { global: true, threshold: 0 })
388
+ fastify.get('/', (_req, reply) => {
389
+ reply.compress(new Response('data: from reply.compress\n\n', {
390
+ headers: { 'content-type': 'text/event-stream' }
391
+ }))
392
+ })
393
+ const res = await fastify.inject({ url: '/', method: 'GET', headers: { 'accept-encoding': 'gzip' } })
394
+ t.assert.equal(res.headers['content-encoding'], undefined)
395
+ t.assert.equal(res.rawPayload.toString('utf8'), 'data: from reply.compress\n\n')
396
+ })
397
+
398
+ test('reply.compress should send a bodyless Fetch Response as is', async (t) => {
399
+ t.plan(2)
400
+ const fastify = Fastify()
401
+ await fastify.register(compressPlugin, { global: true, threshold: 0 })
402
+ fastify.get('/', (_req, reply) => {
403
+ reply.compress(new Response(null, { status: 204 }))
404
+ })
405
+ const res = await fastify.inject({ url: '/', method: 'GET', headers: { 'accept-encoding': 'gzip' } })
406
+ t.assert.equal(res.statusCode, 204)
407
+ t.assert.equal(res.rawPayload.length, 0)
408
+ })
409
+
384
410
  test('reply.compress should handle Web ReadableStream', async (t) => {
385
411
  t.plan(1)
386
412
  const fastify = Fastify()