@fastify/compress 7.0.2 → 8.0.0-pre.fv5.1
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/.github/workflows/ci.yml +1 -1
- package/.taprc +1 -0
- package/index.js +5 -5
- package/lib/utils.js +42 -1
- package/package.json +15 -15
- package/test/regression/issue-288.test.js +62 -0
- package/test/utils.test.js +43 -1
- package/types/index.d.ts +14 -7
package/.github/workflows/ci.yml
CHANGED
package/.taprc
CHANGED
package/index.js
CHANGED
|
@@ -7,12 +7,12 @@ const fp = require('fastify-plugin')
|
|
|
7
7
|
const encodingNegotiator = require('@fastify/accept-negotiator')
|
|
8
8
|
const pump = require('pump')
|
|
9
9
|
const mimedb = require('mime-db')
|
|
10
|
-
const intoStream = require('into-stream')
|
|
11
10
|
const peek = require('peek-stream')
|
|
12
11
|
const { Minipass } = require('minipass')
|
|
13
12
|
const pumpify = require('pumpify')
|
|
13
|
+
const { Readable } = require('readable-stream')
|
|
14
14
|
|
|
15
|
-
const { isStream, isGzip, isDeflate } = require('./lib/utils')
|
|
15
|
+
const { isStream, isGzip, isDeflate, intoAsyncIterator } = require('./lib/utils')
|
|
16
16
|
|
|
17
17
|
const InvalidRequestEncodingError = createError('FST_CP_ERR_INVALID_CONTENT_ENCODING', 'Unsupported Content-Encoding: %s', 415)
|
|
18
18
|
const InvalidRequestCompressedPayloadError = createError('FST_CP_ERR_INVALID_CONTENT', 'Could not decompress the request payload using the provided encoding', 400)
|
|
@@ -276,7 +276,7 @@ function buildRouteCompress (fastify, params, routeOptions, decorateOnly) {
|
|
|
276
276
|
if (Buffer.byteLength(payload) < params.threshold) {
|
|
277
277
|
return next()
|
|
278
278
|
}
|
|
279
|
-
payload =
|
|
279
|
+
payload = Readable.from(intoAsyncIterator(payload))
|
|
280
280
|
}
|
|
281
281
|
|
|
282
282
|
setVaryHeader(reply)
|
|
@@ -400,7 +400,7 @@ function compress (params) {
|
|
|
400
400
|
if (Buffer.byteLength(payload) < params.threshold) {
|
|
401
401
|
return this.send(payload)
|
|
402
402
|
}
|
|
403
|
-
payload =
|
|
403
|
+
payload = Readable.from(intoAsyncIterator(payload))
|
|
404
404
|
}
|
|
405
405
|
|
|
406
406
|
setVaryHeader(this)
|
|
@@ -509,7 +509,7 @@ function maybeUnzip (payload, serialize) {
|
|
|
509
509
|
// handle case where serialize doesn't return a string or Buffer
|
|
510
510
|
if (!Buffer.isBuffer(buf)) return result
|
|
511
511
|
if (isCompressed(buf) === 0) return result
|
|
512
|
-
return
|
|
512
|
+
return Readable.from(intoAsyncIterator(result))
|
|
513
513
|
}
|
|
514
514
|
|
|
515
515
|
function zipStream (deflate, encoding) {
|
package/lib/utils.js
CHANGED
|
@@ -35,4 +35,45 @@ function isStream (stream) {
|
|
|
35
35
|
return stream !== null && typeof stream === 'object' && typeof stream.pipe === 'function'
|
|
36
36
|
}
|
|
37
37
|
|
|
38
|
-
|
|
38
|
+
/**
|
|
39
|
+
* Provide a async iteratable for Readable.from
|
|
40
|
+
*/
|
|
41
|
+
async function * intoAsyncIterator (payload) {
|
|
42
|
+
if (typeof payload === 'object') {
|
|
43
|
+
if (Buffer.isBuffer(payload)) {
|
|
44
|
+
yield payload
|
|
45
|
+
return
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
if (
|
|
49
|
+
// ArrayBuffer
|
|
50
|
+
payload instanceof ArrayBuffer ||
|
|
51
|
+
// NodeJS.TypedArray
|
|
52
|
+
ArrayBuffer.isView(payload)
|
|
53
|
+
) {
|
|
54
|
+
yield Buffer.from(payload)
|
|
55
|
+
return
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
// Iterator
|
|
59
|
+
if (Symbol.iterator in payload) {
|
|
60
|
+
for (const chunk of payload) {
|
|
61
|
+
yield chunk
|
|
62
|
+
}
|
|
63
|
+
return
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
// Async Iterator
|
|
67
|
+
if (Symbol.asyncIterator in payload) {
|
|
68
|
+
for await (const chunk of payload) {
|
|
69
|
+
yield chunk
|
|
70
|
+
}
|
|
71
|
+
return
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
// string
|
|
76
|
+
yield payload
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
module.exports = { isGzip, isDeflate, isStream, intoAsyncIterator }
|
package/package.json
CHANGED
|
@@ -1,41 +1,41 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@fastify/compress",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "8.0.0-pre.fv5.1",
|
|
4
4
|
"description": "Fastify compression utils",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"type": "commonjs",
|
|
7
7
|
"types": "types/index.d.ts",
|
|
8
8
|
"dependencies": {
|
|
9
9
|
"@fastify/accept-negotiator": "^1.1.0",
|
|
10
|
-
"fastify-plugin": "^4.5.
|
|
11
|
-
"into-stream": "^6.0.0",
|
|
10
|
+
"fastify-plugin": "^4.5.1",
|
|
12
11
|
"mime-db": "^1.52.0",
|
|
13
|
-
"minipass": "^7.0.
|
|
12
|
+
"minipass": "^7.0.4",
|
|
14
13
|
"peek-stream": "^1.1.3",
|
|
15
14
|
"pump": "^3.0.0",
|
|
16
|
-
"pumpify": "^2.0.1"
|
|
15
|
+
"pumpify": "^2.0.1",
|
|
16
|
+
"readable-stream": "^4.5.2"
|
|
17
17
|
},
|
|
18
18
|
"devDependencies": {
|
|
19
|
-
"@fastify/pre-commit": "^2.0
|
|
20
|
-
"@types/node": "^20.
|
|
21
|
-
"@typescript-eslint/eslint-plugin": "^7.
|
|
22
|
-
"@typescript-eslint/parser": "^7.
|
|
23
|
-
"adm-zip": "^0.5.
|
|
24
|
-
"fastify": "^4.
|
|
19
|
+
"@fastify/pre-commit": "^2.1.0",
|
|
20
|
+
"@types/node": "^20.12.7",
|
|
21
|
+
"@typescript-eslint/eslint-plugin": "^7.6.0",
|
|
22
|
+
"@typescript-eslint/parser": "^7.6.0",
|
|
23
|
+
"adm-zip": "^0.5.12",
|
|
24
|
+
"fastify": "^4.26.2",
|
|
25
25
|
"jsonstream": "^1.0.3",
|
|
26
26
|
"standard": "^17.1.0",
|
|
27
|
-
"tap": "^
|
|
28
|
-
"tsd": "^0.
|
|
29
|
-
"typescript": "^5.
|
|
27
|
+
"tap": "^18.7.2",
|
|
28
|
+
"tsd": "^0.31.0",
|
|
29
|
+
"typescript": "^5.4.5"
|
|
30
30
|
},
|
|
31
31
|
"scripts": {
|
|
32
|
-
"coverage": "npm run test:unit -- --coverage-report=html",
|
|
33
32
|
"lint": "standard",
|
|
34
33
|
"lint:fix": "npm run lint -- --fix",
|
|
35
34
|
"lint:fix:typescript": "npm run lint:fix -- --parser @typescript-eslint/parser --plugin @typescript-eslint/eslint-plugin \"**/*.d.ts\"",
|
|
36
35
|
"test": "npm run test:unit && npm run test:typescript",
|
|
37
36
|
"test:typescript": "tsd",
|
|
38
37
|
"test:unit": "tap",
|
|
38
|
+
"test:coverage": "npm run test:unit -- --coverage-report=html",
|
|
39
39
|
"test:unit:verbose": "npm run test:unit -- -Rspec"
|
|
40
40
|
},
|
|
41
41
|
"keywords": [
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
|
|
3
|
+
const { test } = require('tap')
|
|
4
|
+
const Fastify = require('fastify')
|
|
5
|
+
const fastifyCompress = require('../..')
|
|
6
|
+
const { request } = require('node:http')
|
|
7
|
+
|
|
8
|
+
function fetch (url) {
|
|
9
|
+
return new Promise(function (resolve, reject) {
|
|
10
|
+
request(url, function (response) {
|
|
11
|
+
// we need to use Buffer.concat to prevent wrong utf8 handling
|
|
12
|
+
let body = Buffer.from('')
|
|
13
|
+
response.on('data', function (chunk) {
|
|
14
|
+
body = Buffer.concat([body, Buffer.from(chunk, 'utf-8')])
|
|
15
|
+
})
|
|
16
|
+
response.once('error', reject)
|
|
17
|
+
response.once('end', function () {
|
|
18
|
+
resolve(body.toString())
|
|
19
|
+
})
|
|
20
|
+
})
|
|
21
|
+
.once('error', reject)
|
|
22
|
+
.end()
|
|
23
|
+
})
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
test('should not corrupt the file content', async (t) => {
|
|
27
|
+
t.plan(2)
|
|
28
|
+
|
|
29
|
+
// provide 2 byte unicode content
|
|
30
|
+
const twoByteUnicodeContent = new Array(5_000)
|
|
31
|
+
.fill('0')
|
|
32
|
+
.map(() => {
|
|
33
|
+
const random = new Array(10).fill('A').join('🍃')
|
|
34
|
+
return random + '- FASTIFY COMPRESS,🍃 FASTIFY COMPRESS'
|
|
35
|
+
})
|
|
36
|
+
.join('\n')
|
|
37
|
+
const fastify = new Fastify()
|
|
38
|
+
t.teardown(() => fastify.close())
|
|
39
|
+
|
|
40
|
+
fastify.register(async (instance, opts) => {
|
|
41
|
+
await fastify.register(fastifyCompress)
|
|
42
|
+
// compression
|
|
43
|
+
instance.get('/compress', async (req, reply) => {
|
|
44
|
+
return twoByteUnicodeContent
|
|
45
|
+
})
|
|
46
|
+
})
|
|
47
|
+
|
|
48
|
+
// no compression
|
|
49
|
+
fastify.get('/no-compress', async (req, reply) => {
|
|
50
|
+
return twoByteUnicodeContent
|
|
51
|
+
})
|
|
52
|
+
|
|
53
|
+
const address = await fastify.listen({ port: 0, host: '127.0.0.1' })
|
|
54
|
+
|
|
55
|
+
const [body1, body2] = await Promise.all([
|
|
56
|
+
fetch(`${address}/compress`),
|
|
57
|
+
fetch(`${address}/no-compress`)
|
|
58
|
+
])
|
|
59
|
+
|
|
60
|
+
t.equal(body1, body2)
|
|
61
|
+
t.equal(body1, twoByteUnicodeContent)
|
|
62
|
+
})
|
package/test/utils.test.js
CHANGED
|
@@ -4,7 +4,7 @@ const { createReadStream } = require('node:fs')
|
|
|
4
4
|
const { Socket } = require('node:net')
|
|
5
5
|
const { Duplex, PassThrough, Readable, Stream, Transform, Writable } = require('node:stream')
|
|
6
6
|
const { test } = require('tap')
|
|
7
|
-
const { isStream, isDeflate, isGzip } = require('../lib/utils')
|
|
7
|
+
const { isStream, isDeflate, isGzip, intoAsyncIterator } = require('../lib/utils')
|
|
8
8
|
|
|
9
9
|
test('isStream() utility should be able to detect Streams', async (t) => {
|
|
10
10
|
t.plan(12)
|
|
@@ -61,3 +61,45 @@ test('isGzip() utility should be able to detect gzip compressed Buffer', async (
|
|
|
61
61
|
t.equal(isGzip(undefined), false)
|
|
62
62
|
t.equal(isGzip(''), false)
|
|
63
63
|
})
|
|
64
|
+
|
|
65
|
+
test('intoAsyncIterator() utility should handle different data', async (t) => {
|
|
66
|
+
t.plan(8)
|
|
67
|
+
|
|
68
|
+
const buf = Buffer.from('foo')
|
|
69
|
+
const str = 'foo'
|
|
70
|
+
const arr = [str, str]
|
|
71
|
+
const arrayBuffer = new ArrayBuffer(8)
|
|
72
|
+
const typedArray = new Int32Array(arrayBuffer)
|
|
73
|
+
const asyncIterator = (async function * () {
|
|
74
|
+
yield str
|
|
75
|
+
})()
|
|
76
|
+
const obj = {}
|
|
77
|
+
|
|
78
|
+
for await (const buffer of intoAsyncIterator(buf)) {
|
|
79
|
+
t.equal(buffer, buf)
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
for await (const string of intoAsyncIterator(str)) {
|
|
83
|
+
t.equal(string, str)
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
for await (const chunk of intoAsyncIterator(arr)) {
|
|
87
|
+
t.equal(chunk, str)
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
for await (const chunk of intoAsyncIterator(arrayBuffer)) {
|
|
91
|
+
t.equal(chunk.toString(), Buffer.from(arrayBuffer).toString())
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
for await (const chunk of intoAsyncIterator(typedArray)) {
|
|
95
|
+
t.equal(chunk.toString(), Buffer.from(typedArray).toString())
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
for await (const chunk of intoAsyncIterator(asyncIterator)) {
|
|
99
|
+
t.equal(chunk, str)
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
for await (const chunk of intoAsyncIterator(obj)) {
|
|
103
|
+
t.equal(chunk, obj)
|
|
104
|
+
}
|
|
105
|
+
})
|
package/types/index.d.ts
CHANGED
|
@@ -2,13 +2,12 @@ import {
|
|
|
2
2
|
FastifyPluginCallback,
|
|
3
3
|
FastifyReply,
|
|
4
4
|
FastifyRequest,
|
|
5
|
+
RouteOptions as FastifyRouteOptions,
|
|
5
6
|
RawServerBase,
|
|
6
|
-
RawServerDefault
|
|
7
|
-
|
|
8
|
-
} from '
|
|
9
|
-
import {
|
|
10
|
-
import { Stream } from 'stream'
|
|
11
|
-
import { BrotliOptions, ZlibOptions } from 'zlib'
|
|
7
|
+
RawServerDefault
|
|
8
|
+
} from 'fastify';
|
|
9
|
+
import { Stream } from 'stream';
|
|
10
|
+
import { BrotliOptions, ZlibOptions } from 'zlib';
|
|
12
11
|
|
|
13
12
|
declare module 'fastify' {
|
|
14
13
|
export interface FastifyContextConfig {
|
|
@@ -26,7 +25,7 @@ declare module 'fastify' {
|
|
|
26
25
|
}
|
|
27
26
|
|
|
28
27
|
interface FastifyReply {
|
|
29
|
-
compress(input: Stream | Input
|
|
28
|
+
compress(input: Stream | Input): void;
|
|
30
29
|
}
|
|
31
30
|
|
|
32
31
|
export interface RouteOptions {
|
|
@@ -61,6 +60,14 @@ type EncodingToken = 'br' | 'deflate' | 'gzip' | 'identity';
|
|
|
61
60
|
|
|
62
61
|
type CompressibleContentTypeFunction = (contentType: string) => boolean;
|
|
63
62
|
|
|
63
|
+
type Input =
|
|
64
|
+
| Buffer
|
|
65
|
+
| NodeJS.TypedArray
|
|
66
|
+
| ArrayBuffer
|
|
67
|
+
| string
|
|
68
|
+
| Iterable<Buffer | string>
|
|
69
|
+
| AsyncIterable<Buffer | string>;
|
|
70
|
+
|
|
64
71
|
declare namespace fastifyCompress {
|
|
65
72
|
|
|
66
73
|
export interface FastifyCompressOptions {
|