@@ -16,159 +16,226 @@ const isDeflate = require('is-deflate')
16
16
const encodingNegotiator = require ( 'encoding-negotiator' )
17
17
18
18
function compressPlugin ( fastify , opts , next ) {
19
- fastify . decorateReply ( 'compress' , compress )
19
+ const globalParams = processParams ( opts )
20
20
21
- if ( opts . global !== false ) {
22
- fastify . addHook ( 'onSend' , onSend )
21
+ if ( opts . encodings && opts . encodings . length < 1 ) {
22
+ next ( new Error ( 'The `encodings` option array must have at least 1 item.' ) )
23
+ return
24
+ }
25
+
26
+ if ( globalParams . encodings . length < 1 ) {
27
+ next ( new Error ( 'None of the passed `encodings` were supported — compression not possible.' ) )
28
+ return
29
+ }
30
+
31
+ fastify . decorateReply ( 'compress' , null )
32
+
33
+ // add onSend hook onto each route as needed
34
+ fastify . addHook ( 'onRoute' , ( routeOptions ) => {
35
+ if ( routeOptions . config && typeof routeOptions . config . compress !== 'undefined' ) {
36
+ if ( typeof routeOptions . config . compress === 'object' ) {
37
+ const mergedCompressParams = Object . assign ( { } , globalParams , processParams ( routeOptions . config . compress ) )
38
+ // if the current endpoint has a custom compress configuration ...
39
+ buildRouteCompress ( fastify , mergedCompressParams , routeOptions )
40
+ } else if ( routeOptions . config . compress === false ) {
41
+ // don't apply any compress settings
42
+ } else {
43
+ throw new Error ( 'Unknown value for route compress configuration' )
44
+ }
45
+ } else if ( globalParams . global ) {
46
+ // if the plugin is set globally ( meaning that all the routes will be compressed )
47
+ // As the endpoint, does not have a custom rateLimit configuration, use the global one.
48
+ buildRouteCompress ( fastify , globalParams , routeOptions )
49
+ } else {
50
+ // if no options are specified and the plugin is not global, then we still want to decorate
51
+ // the reply in this case
52
+ buildRouteCompress ( fastify , globalParams , routeOptions , true )
53
+ }
54
+ } )
55
+
56
+ next ( )
57
+ }
58
+
59
+ function processParams ( opts ) {
60
+ if ( ! opts ) {
61
+ return
23
62
}
24
63
25
- const onUnsupportedEncoding = opts . onUnsupportedEncoding
26
- const inflateIfDeflated = opts . inflateIfDeflated === true
27
- const threshold = typeof opts . threshold === 'number' ? opts . threshold : 1024
28
- const compressibleTypes = opts . customTypes instanceof RegExp ? opts . customTypes : / ^ t e x t \/ | \+ j s o n $ | \+ t e x t $ | \+ x m l $ | o c t e t - s t r e a m $ /
29
- const compressStream = {
64
+ const params = {
65
+ global : ( typeof opts . global === 'boolean' ) ? opts . global : true
66
+ }
67
+
68
+ params . onUnsupportedEncoding = opts . onUnsupportedEncoding
69
+ params . inflateIfDeflated = opts . inflateIfDeflated === true
70
+ params . threshold = typeof opts . threshold === 'number' ? opts . threshold : 1024
71
+ params . compressibleTypes = opts . customTypes instanceof RegExp ? opts . customTypes : / ^ t e x t \/ | \+ j s o n $ | \+ t e x t $ | \+ x m l $ | o c t e t - s t r e a m $ /
72
+ params . compressStream = {
30
73
gzip : ( opts . zlib || zlib ) . createGzip || zlib . createGzip ,
31
74
deflate : ( opts . zlib || zlib ) . createDeflate || zlib . createDeflate
32
75
}
33
- const uncompressStream = {
76
+ params . uncompressStream = {
34
77
gzip : ( opts . zlib || zlib ) . createGunzip || zlib . createGunzip ,
35
78
deflate : ( opts . zlib || zlib ) . createInflate || zlib . createInflate
36
79
}
37
80
38
81
const supportedEncodings = [ 'gzip' , 'deflate' , 'identity' ]
39
82
if ( opts . brotli ) {
40
- compressStream . br = opts . brotli . compressStream
83
+ params . compressStream . br = opts . brotli . compressStream
41
84
supportedEncodings . unshift ( 'br' )
42
85
} else if ( zlib . createBrotliCompress ) {
43
- compressStream . br = zlib . createBrotliCompress
86
+ params . compressStream . br = zlib . createBrotliCompress
44
87
supportedEncodings . unshift ( 'br' )
45
88
}
46
89
47
- if ( opts . encodings && opts . encodings . length < 1 ) {
48
- next ( new Error ( 'The `encodings` option array must have at least 1 item.' ) )
49
- }
50
-
51
- const encodings = Array . isArray ( opts . encodings )
90
+ params . encodings = Array . isArray ( opts . encodings )
52
91
? supportedEncodings
53
92
. filter ( encoding => opts . encodings . includes ( encoding ) )
54
93
. sort ( ( a , b ) => opts . encodings . indexOf ( a ) - supportedEncodings . indexOf ( b ) )
55
94
: supportedEncodings
56
95
57
- if ( encodings . length < 1 ) {
58
- next ( new Error ( 'None of the passed `encodings` were supported — compression not possible.' ) )
96
+ return params
97
+ }
98
+
99
+ function buildRouteCompress ( fastify , params , routeOptions , decorateOnly ) {
100
+ // In order to provide a compress method with the same parameter set as the route itself has
101
+ // we do the decorate the reply at the start of the request
102
+ if ( Array . isArray ( routeOptions . onRequest ) ) {
103
+ routeOptions . onRequest . push ( onRequest )
104
+ } else if ( typeof routeOptions . onRequest === 'function' ) {
105
+ routeOptions . onRequest = [ routeOptions . onRequest , onRequest ]
106
+ } else {
107
+ routeOptions . onRequest = [ onRequest ]
59
108
}
60
109
61
- next ( )
110
+ const compressFn = compress ( params )
111
+ function onRequest ( req , reply , next ) {
112
+ reply . compress = compressFn
113
+ next ( )
114
+ }
115
+
116
+ if ( decorateOnly ) {
117
+ return
118
+ }
62
119
63
- function compress ( payload ) {
120
+ if ( Array . isArray ( routeOptions . onSend ) ) {
121
+ routeOptions . onSend . push ( onSend )
122
+ } else if ( typeof routeOptions . onSend === 'function' ) {
123
+ routeOptions . onSend = [ routeOptions . onSend , onSend ]
124
+ } else {
125
+ routeOptions . onSend = [ onSend ]
126
+ }
127
+
128
+ function onSend ( req , reply , payload , next ) {
64
129
if ( payload == null ) {
65
- this . res . log . debug ( 'compress: missing payload' )
66
- this . send ( new Error ( 'Internal server error' ) )
67
- return
130
+ reply . res . log . debug ( 'compress: missing payload' )
131
+ return next ( )
68
132
}
69
133
70
134
var stream , encoding
71
135
var noCompress =
72
136
// don't compress on x-no-compression header
73
- ( this . request . headers [ 'x-no-compression' ] !== undefined ) ||
137
+ ( req . headers [ 'x-no-compression' ] !== undefined ) ||
74
138
// don't compress if not one of the indicated compressible types
75
- ( shouldCompress ( this . getHeader ( 'Content-Type' ) || 'application/json' , compressibleTypes ) === false ) ||
139
+ ( shouldCompress ( reply . getHeader ( 'Content-Type' ) || 'application/json' , params . compressibleTypes ) === false ) ||
76
140
// don't compress on missing or identity `accept-encoding` header
77
- ( ( encoding = getEncodingHeader ( encodings , this . request ) ) == null || encoding === 'identity' )
141
+ ( ( encoding = getEncodingHeader ( params . encodings , req ) ) == null || encoding === 'identity' )
78
142
79
- if ( encoding == null && onUnsupportedEncoding != null ) {
80
- var encodingHeader = this . request . headers [ 'accept-encoding' ]
81
-
82
- var errorPayload
143
+ if ( encoding == null && params . onUnsupportedEncoding != null ) {
144
+ var encodingHeader = req . headers [ 'accept-encoding' ]
83
145
try {
84
- errorPayload = onUnsupportedEncoding ( encodingHeader , this . request , this )
85
- } catch ( ex ) {
86
- errorPayload = ex
146
+ var errorPayload = params . onUnsupportedEncoding ( encodingHeader , reply . request , reply )
147
+ return next ( null , errorPayload )
148
+ } catch ( err ) {
149
+ return next ( err )
87
150
}
88
- return this . send ( errorPayload )
89
151
}
90
152
91
153
if ( noCompress ) {
92
- if ( inflateIfDeflated && isStream ( stream = maybeUnzip ( payload , this . serialize . bind ( this ) ) ) ) {
154
+ if ( params . inflateIfDeflated && isStream ( stream = maybeUnzip ( payload ) ) ) {
93
155
encoding === undefined
94
- ? this . removeHeader ( 'Content-Encoding' )
95
- : this . header ( 'Content-Encoding' , 'identity' )
96
- pump ( stream , payload = unzipStream ( uncompressStream ) , onEnd . bind ( this ) )
97
- }
98
- return this . send ( payload )
99
- }
100
-
101
- if ( typeof payload . pipe !== 'function' ) {
102
- if ( ! Buffer . isBuffer ( payload ) && typeof payload !== 'string' ) {
103
- payload = this . serialize ( payload )
156
+ ? reply . removeHeader ( 'Content-Encoding' )
157
+ : reply . header ( 'Content-Encoding' , 'identity' )
158
+ pump ( stream , payload = unzipStream ( params . uncompressStream ) , onEnd . bind ( reply ) )
104
159
}
160
+ return next ( null , payload )
105
161
}
106
162
107
163
if ( typeof payload . pipe !== 'function' ) {
108
- if ( Buffer . byteLength ( payload ) < threshold ) {
109
- return this . send ( payload )
164
+ if ( Buffer . byteLength ( payload ) < params . threshold ) {
165
+ return next ( )
110
166
}
111
167
payload = intoStream ( payload )
112
168
}
113
169
114
- this
170
+ reply
115
171
. header ( 'Content-Encoding' , encoding )
116
172
. removeHeader ( 'content-length' )
117
173
118
- stream = zipStream ( compressStream , encoding )
119
- pump ( payload , stream , onEnd . bind ( this ) )
120
- this . send ( stream )
174
+ stream = zipStream ( params . compressStream , encoding )
175
+ pump ( payload , stream , onEnd . bind ( reply ) )
176
+ next ( null , stream )
121
177
}
178
+ }
122
179
123
- function onSend ( req , reply , payload , next ) {
180
+ function compress ( params ) {
181
+ return function ( payload ) {
124
182
if ( payload == null ) {
125
- reply . res . log . debug ( 'compress: missing payload' )
126
- return next ( )
183
+ this . res . log . debug ( 'compress: missing payload' )
184
+ this . send ( new Error ( 'Internal server error' ) )
185
+ return
127
186
}
128
187
129
188
var stream , encoding
130
189
var noCompress =
131
190
// don't compress on x-no-compression header
132
- ( req . headers [ 'x-no-compression' ] !== undefined ) ||
133
- // don't compress if not one of the indiated compressible types
134
- ( shouldCompress ( reply . getHeader ( 'Content-Type' ) || 'application/json' , compressibleTypes ) === false ) ||
191
+ ( this . request . headers [ 'x-no-compression' ] !== undefined ) ||
192
+ // don't compress if not one of the indicated compressible types
193
+ ( shouldCompress ( this . getHeader ( 'Content-Type' ) || 'application/json' , params . compressibleTypes ) === false ) ||
135
194
// don't compress on missing or identity `accept-encoding` header
136
- ( ( encoding = getEncodingHeader ( encodings , req ) ) == null || encoding === 'identity' )
195
+ ( ( encoding = getEncodingHeader ( params . encodings , this . request ) ) == null || encoding === 'identity' )
137
196
138
- if ( encoding == null && onUnsupportedEncoding != null ) {
139
- var encodingHeader = req . headers [ 'accept-encoding' ]
197
+ if ( encoding == null && params . onUnsupportedEncoding != null ) {
198
+ var encodingHeader = this . request . headers [ 'accept-encoding' ]
199
+
200
+ var errorPayload
140
201
try {
141
- var errorPayload = onUnsupportedEncoding ( encodingHeader , reply . request , reply )
142
- return next ( null , errorPayload )
143
- } catch ( err ) {
144
- return next ( err )
202
+ errorPayload = params . onUnsupportedEncoding ( encodingHeader , this . request , this )
203
+ } catch ( ex ) {
204
+ errorPayload = ex
145
205
}
206
+ return this . send ( errorPayload )
146
207
}
147
208
148
209
if ( noCompress ) {
149
- if ( inflateIfDeflated && isStream ( stream = maybeUnzip ( payload ) ) ) {
210
+ if ( params . inflateIfDeflated && isStream ( stream = maybeUnzip ( payload , this . serialize . bind ( this ) ) ) ) {
150
211
encoding === undefined
151
- ? reply . removeHeader ( 'Content-Encoding' )
152
- : reply . header ( 'Content-Encoding' , 'identity' )
153
- pump ( stream , payload = unzipStream ( uncompressStream ) , onEnd . bind ( reply ) )
212
+ ? this . removeHeader ( 'Content-Encoding' )
213
+ : this . header ( 'Content-Encoding' , 'identity' )
214
+ pump ( stream , payload = unzipStream ( params . uncompressStream ) , onEnd . bind ( this ) )
154
215
}
155
- return next ( null , payload )
216
+ return this . send ( payload )
156
217
}
157
218
158
219
if ( typeof payload . pipe !== 'function' ) {
159
- if ( Buffer . byteLength ( payload ) < threshold ) {
160
- return next ( )
220
+ if ( ! Buffer . isBuffer ( payload ) && typeof payload !== 'string' ) {
221
+ payload = this . serialize ( payload )
222
+ }
223
+ }
224
+
225
+ if ( typeof payload . pipe !== 'function' ) {
226
+ if ( Buffer . byteLength ( payload ) < params . threshold ) {
227
+ return this . send ( payload )
161
228
}
162
229
payload = intoStream ( payload )
163
230
}
164
231
165
- reply
232
+ this
166
233
. header ( 'Content-Encoding' , encoding )
167
234
. removeHeader ( 'content-length' )
168
235
169
- stream = zipStream ( compressStream , encoding )
170
- pump ( payload , stream , onEnd . bind ( reply ) )
171
- next ( null , stream )
236
+ stream = zipStream ( params . compressStream , encoding )
237
+ pump ( payload , stream , onEnd . bind ( this ) )
238
+ this . send ( stream )
172
239
}
173
240
}
174
241
@@ -247,6 +314,6 @@ function unzipStream (inflate, maxRecursion) {
247
314
}
248
315
249
316
module . exports = fp ( compressPlugin , {
250
- fastify : '>=1.3 .0' ,
317
+ fastify : '>=2.11 .0' ,
251
318
name : 'fastify-compress'
252
319
} )
0 commit comments