1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16 package io.netty.handler.codec.http;
17
18 import java.util.HashMap;
19 import java.util.Map;
20
21 import io.netty.buffer.ByteBuf;
22 import io.netty.channel.ChannelHandlerContext;
23 import io.netty.channel.embedded.EmbeddedChannel;
24 import io.netty.handler.codec.MessageToByteEncoder;
25 import io.netty.handler.codec.compression.Brotli;
26 import io.netty.handler.codec.compression.BrotliEncoder;
27 import io.netty.handler.codec.compression.BrotliOptions;
28 import io.netty.handler.codec.compression.CompressionOptions;
29 import io.netty.handler.codec.compression.DeflateOptions;
30 import io.netty.handler.codec.compression.GzipOptions;
31 import io.netty.handler.codec.compression.StandardCompressionOptions;
32 import io.netty.handler.codec.compression.ZlibCodecFactory;
33 import io.netty.handler.codec.compression.ZlibEncoder;
34 import io.netty.handler.codec.compression.ZlibWrapper;
35 import io.netty.handler.codec.compression.Zstd;
36 import io.netty.handler.codec.compression.ZstdEncoder;
37 import io.netty.handler.codec.compression.ZstdOptions;
38 import io.netty.handler.codec.compression.SnappyFrameEncoder;
39 import io.netty.handler.codec.compression.SnappyOptions;
40 import io.netty.util.internal.ObjectUtil;
41
42
43
44
45
46
47
48
49 public class HttpContentCompressor extends HttpContentEncoder {
50
51 private final boolean supportsCompressionOptions;
52 private final BrotliOptions brotliOptions;
53 private final GzipOptions gzipOptions;
54 private final DeflateOptions deflateOptions;
55 private final ZstdOptions zstdOptions;
56 private final SnappyOptions snappyOptions;
57
58 private final int compressionLevel;
59 private final int windowBits;
60 private final int memLevel;
61 private final int contentSizeThreshold;
62 private ChannelHandlerContext ctx;
63 private final Map<String, CompressionEncoderFactory> factories;
64
65
66
67
68
69 public HttpContentCompressor() {
70 this(6);
71 }
72
73
74
75
76
77
78
79
80
81
82 @Deprecated
83 public HttpContentCompressor(int compressionLevel) {
84 this(compressionLevel, 15, 8, 0);
85 }
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106 @Deprecated
107 public HttpContentCompressor(int compressionLevel, int windowBits, int memLevel) {
108 this(compressionLevel, windowBits, memLevel, 0);
109 }
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134 @Deprecated
135 public HttpContentCompressor(int compressionLevel, int windowBits, int memLevel, int contentSizeThreshold) {
136 this.compressionLevel = ObjectUtil.checkInRange(compressionLevel, 0, 9, "compressionLevel");
137 this.windowBits = ObjectUtil.checkInRange(windowBits, 9, 15, "windowBits");
138 this.memLevel = ObjectUtil.checkInRange(memLevel, 1, 9, "memLevel");
139 this.contentSizeThreshold = ObjectUtil.checkPositiveOrZero(contentSizeThreshold, "contentSizeThreshold");
140 this.brotliOptions = null;
141 this.gzipOptions = null;
142 this.deflateOptions = null;
143 this.zstdOptions = null;
144 this.snappyOptions = null;
145 this.factories = null;
146 this.supportsCompressionOptions = false;
147 }
148
149
150
151
152
153
154
155
156 public HttpContentCompressor(CompressionOptions... compressionOptions) {
157 this(0, compressionOptions);
158 }
159
160
161
162
163
164
165
166
167
168
169
170
171 public HttpContentCompressor(int contentSizeThreshold, CompressionOptions... compressionOptions) {
172 this.contentSizeThreshold = ObjectUtil.checkPositiveOrZero(contentSizeThreshold, "contentSizeThreshold");
173 BrotliOptions brotliOptions = null;
174 GzipOptions gzipOptions = null;
175 DeflateOptions deflateOptions = null;
176 ZstdOptions zstdOptions = null;
177 SnappyOptions snappyOptions = null;
178 if (compressionOptions == null || compressionOptions.length == 0) {
179 brotliOptions = Brotli.isAvailable() ? StandardCompressionOptions.brotli() : null;
180 gzipOptions = StandardCompressionOptions.gzip();
181 deflateOptions = StandardCompressionOptions.deflate();
182 zstdOptions = Zstd.isAvailable() ? StandardCompressionOptions.zstd() : null;
183 snappyOptions = StandardCompressionOptions.snappy();
184 } else {
185 ObjectUtil.deepCheckNotNull("compressionOptions", compressionOptions);
186 for (CompressionOptions compressionOption : compressionOptions) {
187
188
189
190
191
192
193 if (Brotli.isAvailable() && compressionOption instanceof BrotliOptions) {
194 brotliOptions = (BrotliOptions) compressionOption;
195 } else if (compressionOption instanceof GzipOptions) {
196 gzipOptions = (GzipOptions) compressionOption;
197 } else if (compressionOption instanceof DeflateOptions) {
198 deflateOptions = (DeflateOptions) compressionOption;
199 } else if (compressionOption instanceof ZstdOptions) {
200 zstdOptions = (ZstdOptions) compressionOption;
201 } else if (compressionOption instanceof SnappyOptions) {
202 snappyOptions = (SnappyOptions) compressionOption;
203 } else {
204 throw new IllegalArgumentException("Unsupported " + CompressionOptions.class.getSimpleName() +
205 ": " + compressionOption);
206 }
207 }
208 }
209
210 this.gzipOptions = gzipOptions;
211 this.deflateOptions = deflateOptions;
212 this.brotliOptions = brotliOptions;
213 this.zstdOptions = zstdOptions;
214 this.snappyOptions = snappyOptions;
215
216 this.factories = new HashMap<String, CompressionEncoderFactory>();
217
218 if (this.gzipOptions != null) {
219 this.factories.put("gzip", new GzipEncoderFactory());
220 }
221 if (this.deflateOptions != null) {
222 this.factories.put("deflate", new DeflateEncoderFactory());
223 }
224 if (Brotli.isAvailable() && this.brotliOptions != null) {
225 this.factories.put("br", new BrEncoderFactory());
226 }
227 if (this.zstdOptions != null) {
228 this.factories.put("zstd", new ZstdEncoderFactory());
229 }
230 if (this.snappyOptions != null) {
231 this.factories.put("snappy", new SnappyEncoderFactory());
232 }
233
234 this.compressionLevel = -1;
235 this.windowBits = -1;
236 this.memLevel = -1;
237 supportsCompressionOptions = true;
238 }
239
240 @Override
241 public void handlerAdded(ChannelHandlerContext ctx) throws Exception {
242 this.ctx = ctx;
243 }
244
245 @Override
246 protected Result beginEncode(HttpResponse httpResponse, String acceptEncoding) throws Exception {
247 if (this.contentSizeThreshold > 0) {
248 if (httpResponse instanceof HttpContent &&
249 ((HttpContent) httpResponse).content().readableBytes() < contentSizeThreshold) {
250 return null;
251 }
252 }
253
254 String contentEncoding = httpResponse.headers().get(HttpHeaderNames.CONTENT_ENCODING);
255 if (contentEncoding != null) {
256
257
258 return null;
259 }
260
261 if (supportsCompressionOptions) {
262 String targetContentEncoding = determineEncoding(acceptEncoding);
263 if (targetContentEncoding == null) {
264 return null;
265 }
266
267 CompressionEncoderFactory encoderFactory = factories.get(targetContentEncoding);
268
269 if (encoderFactory == null) {
270 throw new Error();
271 }
272
273 return new Result(targetContentEncoding,
274 new EmbeddedChannel(ctx.channel().id(), ctx.channel().metadata().hasDisconnect(),
275 ctx.channel().config(), encoderFactory.createEncoder()));
276 } else {
277 ZlibWrapper wrapper = determineWrapper(acceptEncoding);
278 if (wrapper == null) {
279 return null;
280 }
281
282 String targetContentEncoding;
283 switch (wrapper) {
284 case GZIP:
285 targetContentEncoding = "gzip";
286 break;
287 case ZLIB:
288 targetContentEncoding = "deflate";
289 break;
290 default:
291 throw new Error();
292 }
293
294 return new Result(
295 targetContentEncoding,
296 new EmbeddedChannel(ctx.channel().id(), ctx.channel().metadata().hasDisconnect(),
297 ctx.channel().config(), ZlibCodecFactory.newZlibEncoder(
298 wrapper, compressionLevel, windowBits, memLevel)));
299 }
300 }
301
302 @SuppressWarnings("FloatingPointEquality")
303 protected String determineEncoding(String acceptEncoding) {
304 float starQ = -1.0f;
305 float brQ = -1.0f;
306 float zstdQ = -1.0f;
307 float snappyQ = -1.0f;
308 float gzipQ = -1.0f;
309 float deflateQ = -1.0f;
310 for (String encoding : acceptEncoding.split(",")) {
311 float q = 1.0f;
312 int equalsPos = encoding.indexOf('=');
313 if (equalsPos != -1) {
314 try {
315 q = Float.parseFloat(encoding.substring(equalsPos + 1));
316 } catch (NumberFormatException e) {
317
318 q = 0.0f;
319 }
320 }
321 if (encoding.contains("*")) {
322 starQ = q;
323 } else if (encoding.contains("br") && q > brQ) {
324 brQ = q;
325 } else if (encoding.contains("zstd") && q > zstdQ) {
326 zstdQ = q;
327 } else if (encoding.contains("snappy") && q > snappyQ) {
328 snappyQ = q;
329 } else if (encoding.contains("gzip") && q > gzipQ) {
330 gzipQ = q;
331 } else if (encoding.contains("deflate") && q > deflateQ) {
332 deflateQ = q;
333 }
334 }
335 if (brQ > 0.0f || zstdQ > 0.0f || snappyQ > 0.0f || gzipQ > 0.0f || deflateQ > 0.0f) {
336 if (brQ != -1.0f && brQ >= zstdQ && this.brotliOptions != null) {
337 return "br";
338 } else if (zstdQ != -1.0f && zstdQ >= snappyQ && this.zstdOptions != null) {
339 return "zstd";
340 } else if (snappyQ != -1.0f && snappyQ >= gzipQ && this.snappyOptions != null) {
341 return "snappy";
342 } else if (gzipQ != -1.0f && gzipQ >= deflateQ && this.gzipOptions != null) {
343 return "gzip";
344 } else if (deflateQ != -1.0f && this.deflateOptions != null) {
345 return "deflate";
346 }
347 }
348 if (starQ > 0.0f) {
349 if (brQ == -1.0f && this.brotliOptions != null) {
350 return "br";
351 }
352 if (zstdQ == -1.0f && this.zstdOptions != null) {
353 return "zstd";
354 }
355 if (snappyQ == -1.0f && this.snappyOptions != null) {
356 return "snappy";
357 }
358 if (gzipQ == -1.0f && this.gzipOptions != null) {
359 return "gzip";
360 }
361 if (deflateQ == -1.0f && this.deflateOptions != null) {
362 return "deflate";
363 }
364 }
365 return null;
366 }
367
368 @Deprecated
369 @SuppressWarnings("FloatingPointEquality")
370 protected ZlibWrapper determineWrapper(String acceptEncoding) {
371 float starQ = -1.0f;
372 float gzipQ = -1.0f;
373 float deflateQ = -1.0f;
374 for (String encoding : acceptEncoding.split(",")) {
375 float q = 1.0f;
376 int equalsPos = encoding.indexOf('=');
377 if (equalsPos != -1) {
378 try {
379 q = Float.parseFloat(encoding.substring(equalsPos + 1));
380 } catch (NumberFormatException e) {
381
382 q = 0.0f;
383 }
384 }
385 if (encoding.contains("*")) {
386 starQ = q;
387 } else if (encoding.contains("gzip") && q > gzipQ) {
388 gzipQ = q;
389 } else if (encoding.contains("deflate") && q > deflateQ) {
390 deflateQ = q;
391 }
392 }
393 if (gzipQ > 0.0f || deflateQ > 0.0f) {
394 if (gzipQ >= deflateQ) {
395 return ZlibWrapper.GZIP;
396 } else {
397 return ZlibWrapper.ZLIB;
398 }
399 }
400 if (starQ > 0.0f) {
401 if (gzipQ == -1.0f) {
402 return ZlibWrapper.GZIP;
403 }
404 if (deflateQ == -1.0f) {
405 return ZlibWrapper.ZLIB;
406 }
407 }
408 return null;
409 }
410
411
412
413
414
415 private final class GzipEncoderFactory implements CompressionEncoderFactory {
416
417 @Override
418 public MessageToByteEncoder<ByteBuf> createEncoder() {
419 return ZlibCodecFactory.newZlibEncoder(
420 ZlibWrapper.GZIP, gzipOptions.compressionLevel(),
421 gzipOptions.windowBits(), gzipOptions.memLevel());
422 }
423 }
424
425
426
427
428
429 private final class DeflateEncoderFactory implements CompressionEncoderFactory {
430
431 @Override
432 public MessageToByteEncoder<ByteBuf> createEncoder() {
433 return ZlibCodecFactory.newZlibEncoder(
434 ZlibWrapper.ZLIB, deflateOptions.compressionLevel(),
435 deflateOptions.windowBits(), deflateOptions.memLevel());
436 }
437 }
438
439
440
441
442
443 private final class BrEncoderFactory implements CompressionEncoderFactory {
444
445 @Override
446 public MessageToByteEncoder<ByteBuf> createEncoder() {
447 return new BrotliEncoder(brotliOptions.parameters());
448 }
449 }
450
451
452
453
454
455 private final class ZstdEncoderFactory implements CompressionEncoderFactory {
456
457 @Override
458 public MessageToByteEncoder<ByteBuf> createEncoder() {
459 return new ZstdEncoder(zstdOptions.compressionLevel(),
460 zstdOptions.blockSize(), zstdOptions.maxEncodeSize());
461 }
462 }
463
464
465
466
467
468 private static final class SnappyEncoderFactory implements CompressionEncoderFactory {
469
470 @Override
471 public MessageToByteEncoder<ByteBuf> createEncoder() {
472 return new SnappyFrameEncoder();
473 }
474 }
475 }