1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16 package io.netty5.handler.codec.http;
17
18 import io.netty5.handler.codec.compression.Brotli;
19 import io.netty5.handler.codec.compression.BrotliCompressor;
20 import io.netty5.handler.codec.compression.BrotliOptions;
21 import io.netty5.handler.codec.compression.CompressionOptions;
22 import io.netty5.handler.codec.compression.Compressor;
23 import io.netty5.handler.codec.compression.DeflateOptions;
24 import io.netty5.handler.codec.compression.GzipOptions;
25 import io.netty5.handler.codec.compression.StandardCompressionOptions;
26 import io.netty5.handler.codec.compression.ZlibCompressor;
27 import io.netty5.handler.codec.compression.ZlibWrapper;
28 import io.netty5.handler.codec.compression.Zstd;
29 import io.netty5.handler.codec.compression.ZstdCompressor;
30 import io.netty5.handler.codec.compression.ZstdOptions;
31 import io.netty5.util.internal.ObjectUtil;
32
33 import java.util.HashMap;
34 import java.util.Map;
35 import java.util.function.Supplier;
36
37
38
39
40
41
42
43
44 public class HttpContentCompressor extends HttpContentEncoder {
45
46 private final boolean supportsCompressionOptions;
47 private final BrotliOptions brotliOptions;
48 private final GzipOptions gzipOptions;
49 private final DeflateOptions deflateOptions;
50 private final ZstdOptions zstdOptions;
51
52 private final int compressionLevel;
53 private final int windowBits;
54 private final int memLevel;
55 private final int contentSizeThreshold;
56 private final Map<String, Supplier<? extends Compressor>> factories;
57
58
59
60
61
62 public HttpContentCompressor() {
63 this(6);
64 }
65
66
67
68
69
70
71
72
73
74
75 @Deprecated
76 public HttpContentCompressor(int compressionLevel) {
77 this(compressionLevel, 15, 8, 0);
78 }
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99 @Deprecated
100 public HttpContentCompressor(int compressionLevel, int windowBits, int memLevel) {
101 this(compressionLevel, windowBits, memLevel, 0);
102 }
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127 @Deprecated
128 public HttpContentCompressor(int compressionLevel, int windowBits, int memLevel, int contentSizeThreshold) {
129 this.compressionLevel = ObjectUtil.checkInRange(compressionLevel, 0, 9, "compressionLevel");
130 this.windowBits = ObjectUtil.checkInRange(windowBits, 9, 15, "windowBits");
131 this.memLevel = ObjectUtil.checkInRange(memLevel, 1, 9, "memLevel");
132 this.contentSizeThreshold = ObjectUtil.checkPositiveOrZero(contentSizeThreshold, "contentSizeThreshold");
133 brotliOptions = null;
134 gzipOptions = null;
135 deflateOptions = null;
136 zstdOptions = null;
137 factories = null;
138 supportsCompressionOptions = false;
139 }
140
141
142
143
144
145
146
147
148 public HttpContentCompressor(CompressionOptions... compressionOptions) {
149 this(0, compressionOptions);
150 }
151
152
153
154
155
156
157
158
159
160
161
162
163 public HttpContentCompressor(int contentSizeThreshold, CompressionOptions... compressionOptions) {
164 this.contentSizeThreshold = ObjectUtil.checkPositiveOrZero(contentSizeThreshold, "contentSizeThreshold");
165 BrotliOptions brotliOptions = null;
166 GzipOptions gzipOptions = null;
167 DeflateOptions deflateOptions = null;
168 ZstdOptions zstdOptions = null;
169 if (compressionOptions == null || compressionOptions.length == 0) {
170 brotliOptions = Brotli.isAvailable() ? StandardCompressionOptions.brotli() : null;
171 gzipOptions = StandardCompressionOptions.gzip();
172 deflateOptions = StandardCompressionOptions.deflate();
173 zstdOptions = Zstd.isAvailable() ? StandardCompressionOptions.zstd() : null;
174 } else {
175 ObjectUtil.deepCheckNotNull("compressionOptions", compressionOptions);
176 for (CompressionOptions compressionOption : compressionOptions) {
177
178
179
180
181
182
183 if (Brotli.isAvailable() && compressionOption instanceof BrotliOptions) {
184 brotliOptions = (BrotliOptions) compressionOption;
185 } else if (compressionOption instanceof GzipOptions) {
186 gzipOptions = (GzipOptions) compressionOption;
187 } else if (compressionOption instanceof DeflateOptions) {
188 deflateOptions = (DeflateOptions) compressionOption;
189 } else if (compressionOption instanceof ZstdOptions) {
190 zstdOptions = (ZstdOptions) compressionOption;
191 } else {
192 throw new IllegalArgumentException("Unsupported " + CompressionOptions.class.getSimpleName() +
193 ": " + compressionOption);
194 }
195 }
196 }
197
198 this.gzipOptions = gzipOptions;
199 this.deflateOptions = deflateOptions;
200 this.brotliOptions = brotliOptions;
201 this.zstdOptions = zstdOptions;
202
203 factories = new HashMap<>();
204
205 if (this.gzipOptions != null) {
206 factories.put("gzip", ZlibCompressor.newFactory(
207 ZlibWrapper.GZIP, gzipOptions.compressionLevel()));
208 }
209 if (this.deflateOptions != null) {
210 factories.put("deflate", ZlibCompressor.newFactory(
211 ZlibWrapper.ZLIB, deflateOptions.compressionLevel()));
212 }
213
214 if (Brotli.isAvailable() && this.brotliOptions != null) {
215 factories.put("br", BrotliCompressor.newFactory(brotliOptions.parameters()));
216 }
217 if (this.zstdOptions != null) {
218 factories.put("zstd", ZstdCompressor.newFactory(zstdOptions.compressionLevel(),
219 zstdOptions.blockSize(), zstdOptions.maxEncodeSize()));
220 }
221
222 compressionLevel = -1;
223 windowBits = -1;
224 memLevel = -1;
225 supportsCompressionOptions = true;
226 }
227
228 @Override
229 protected Result beginEncode(HttpResponse httpResponse, String acceptEncoding) {
230 if (contentSizeThreshold > 0) {
231 if (httpResponse instanceof HttpContent &&
232 ((HttpContent<?>) httpResponse).payload().readableBytes() < contentSizeThreshold) {
233 return null;
234 }
235 }
236
237 String contentEncoding = httpResponse.headers().get(HttpHeaderNames.CONTENT_ENCODING);
238 if (contentEncoding != null) {
239
240
241 return null;
242 }
243
244 if (supportsCompressionOptions) {
245 String targetContentEncoding = determineEncoding(acceptEncoding);
246 if (targetContentEncoding == null) {
247 return null;
248 }
249
250 Supplier<? extends Compressor> compressorFactory = factories.get(targetContentEncoding);
251
252 if (compressorFactory == null) {
253 throw new Error();
254 }
255
256 return new Result(targetContentEncoding, compressorFactory.get());
257 } else {
258 ZlibWrapper wrapper = determineWrapper(acceptEncoding);
259 if (wrapper == null) {
260 return null;
261 }
262
263 String targetContentEncoding;
264 switch (wrapper) {
265 case GZIP:
266 targetContentEncoding = "gzip";
267 break;
268 case ZLIB:
269 targetContentEncoding = "deflate";
270 break;
271 default:
272 throw new Error();
273 }
274
275 return new Result(
276 targetContentEncoding, ZlibCompressor.newFactory(wrapper, compressionLevel).get());
277 }
278 }
279
280 @SuppressWarnings("FloatingPointEquality")
281 protected String determineEncoding(String acceptEncoding) {
282 float starQ = -1.0f;
283 float brQ = -1.0f;
284 float zstdQ = -1.0f;
285 float gzipQ = -1.0f;
286 float deflateQ = -1.0f;
287 for (String encoding : acceptEncoding.split(",")) {
288 float q = 1.0f;
289 int equalsPos = encoding.indexOf('=');
290 if (equalsPos != -1) {
291 try {
292 q = Float.parseFloat(encoding.substring(equalsPos + 1));
293 } catch (NumberFormatException e) {
294
295 q = 0.0f;
296 }
297 }
298 if (encoding.contains("*")) {
299 starQ = q;
300 } else if (encoding.contains("br") && q > brQ) {
301 brQ = q;
302 } else if (encoding.contains("zstd") && q > zstdQ) {
303 zstdQ = q;
304 } else if (encoding.contains("gzip") && q > gzipQ) {
305 gzipQ = q;
306 } else if (encoding.contains("deflate") && q > deflateQ) {
307 deflateQ = q;
308 }
309 }
310 if (brQ > 0.0f || zstdQ > 0.0f || gzipQ > 0.0f || deflateQ > 0.0f) {
311 if (brQ != -1.0f && brQ >= zstdQ && brotliOptions != null) {
312 return "br";
313 } else if (zstdQ != -1.0f && zstdQ >= gzipQ && zstdOptions != null) {
314 return "zstd";
315 } else if (gzipQ != -1.0f && gzipQ >= deflateQ && gzipOptions != null) {
316 return "gzip";
317 } else if (deflateQ != -1.0f && deflateOptions != null) {
318 return "deflate";
319 }
320 }
321 if (starQ > 0.0f) {
322 if (brQ == -1.0f && brotliOptions != null) {
323 return "br";
324 }
325 if (zstdQ == -1.0f && zstdOptions != null) {
326 return "zstd";
327 }
328 if (gzipQ == -1.0f && gzipOptions != null) {
329 return "gzip";
330 }
331 if (deflateQ == -1.0f && deflateOptions != null) {
332 return "deflate";
333 }
334 }
335 return null;
336 }
337
338 @Deprecated
339 @SuppressWarnings("FloatingPointEquality")
340 protected ZlibWrapper determineWrapper(String acceptEncoding) {
341 float starQ = -1.0f;
342 float gzipQ = -1.0f;
343 float deflateQ = -1.0f;
344 for (String encoding : acceptEncoding.split(",")) {
345 float q = 1.0f;
346 int equalsPos = encoding.indexOf('=');
347 if (equalsPos != -1) {
348 try {
349 q = Float.parseFloat(encoding.substring(equalsPos + 1));
350 } catch (NumberFormatException e) {
351
352 q = 0.0f;
353 }
354 }
355 if (encoding.contains("*")) {
356 starQ = q;
357 } else if (encoding.contains("gzip") && q > gzipQ) {
358 gzipQ = q;
359 } else if (encoding.contains("deflate") && q > deflateQ) {
360 deflateQ = q;
361 }
362 }
363 if (gzipQ > 0.0f || deflateQ > 0.0f) {
364 if (gzipQ >= deflateQ) {
365 return ZlibWrapper.GZIP;
366 } else {
367 return ZlibWrapper.ZLIB;
368 }
369 }
370 if (starQ > 0.0f) {
371 if (gzipQ == -1.0f) {
372 return ZlibWrapper.GZIP;
373 }
374 if (deflateQ == -1.0f) {
375 return ZlibWrapper.ZLIB;
376 }
377 }
378 return null;
379 }
380 }