diff --git a/codec-http/pom.xml b/codec-http/pom.xml
index 2c200c0050..25fd038abf 100644
--- a/codec-http/pom.xml
+++ b/codec-http/pom.xml
@@ -91,6 +91,11 @@
native-windows-x86_64
test
+
+ com.github.luben
+ zstd-jni
+ true
+
diff --git a/codec-http/src/main/java/io/netty/handler/codec/http/HttpContentCompressor.java b/codec-http/src/main/java/io/netty/handler/codec/http/HttpContentCompressor.java
index dbfc03c496..f92b467e65 100644
--- a/codec-http/src/main/java/io/netty/handler/codec/http/HttpContentCompressor.java
+++ b/codec-http/src/main/java/io/netty/handler/codec/http/HttpContentCompressor.java
@@ -15,27 +15,28 @@
*/
package io.netty.handler.codec.http;
+import java.util.HashMap;
+import java.util.Map;
+
import io.netty.buffer.ByteBuf;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.embedded.EmbeddedChannel;
import io.netty.handler.codec.MessageToByteEncoder;
-import io.netty.handler.codec.compression.ZlibEncoder;
import io.netty.handler.codec.compression.Brotli;
import io.netty.handler.codec.compression.BrotliEncoder;
-import io.netty.handler.codec.compression.ZlibCodecFactory;
-import io.netty.handler.codec.compression.ZlibWrapper;
import io.netty.handler.codec.compression.BrotliOptions;
import io.netty.handler.codec.compression.CompressionOptions;
import io.netty.handler.codec.compression.DeflateOptions;
import io.netty.handler.codec.compression.GzipOptions;
import io.netty.handler.codec.compression.StandardCompressionOptions;
+import io.netty.handler.codec.compression.ZlibCodecFactory;
+import io.netty.handler.codec.compression.ZlibEncoder;
+import io.netty.handler.codec.compression.ZlibWrapper;
+import io.netty.handler.codec.compression.Zstd;
import io.netty.handler.codec.compression.ZstdEncoder;
import io.netty.handler.codec.compression.ZstdOptions;
import io.netty.util.internal.ObjectUtil;
-import java.util.HashMap;
-import java.util.Map;
-
/**
* Compresses an {@link HttpMessage} and an {@link HttpContent} in {@code gzip} or
* {@code deflate} encoding while respecting the {@code "Accept-Encoding"} header.
@@ -138,7 +139,7 @@ public class HttpContentCompressor extends HttpContentEncoder {
this.deflateOptions = null;
this.zstdOptions = null;
this.factories = null;
- supportsCompressionOptions = false;
+ this.supportsCompressionOptions = false;
}
/**
@@ -170,51 +171,50 @@ public class HttpContentCompressor extends HttpContentEncoder {
DeflateOptions deflateOptions = null;
ZstdOptions zstdOptions = null;
if (compressionOptions == null || compressionOptions.length == 0) {
- brotliOptions = StandardCompressionOptions.brotli();
+ brotliOptions = Brotli.isAvailable() ? StandardCompressionOptions.brotli() : null;
gzipOptions = StandardCompressionOptions.gzip();
deflateOptions = StandardCompressionOptions.deflate();
- zstdOptions = StandardCompressionOptions.zstd();
+ zstdOptions = Zstd.isAvailable() ? StandardCompressionOptions.zstd() : null;
} else {
ObjectUtil.deepCheckNotNull("compressionOptions", compressionOptions);
for (CompressionOptions compressionOption : compressionOptions) {
if (compressionOption instanceof BrotliOptions) {
+ // if we have BrotliOptions, it means Brotli is available
brotliOptions = (BrotliOptions) compressionOption;
} else if (compressionOption instanceof GzipOptions) {
gzipOptions = (GzipOptions) compressionOption;
} else if (compressionOption instanceof DeflateOptions) {
deflateOptions = (DeflateOptions) compressionOption;
} else if (compressionOption instanceof ZstdOptions) {
- zstdOptions = (ZstdOptions) compressionOption;
+ // zstd might not be available
+ zstdOptions = Zstd.isAvailable() ? (ZstdOptions) compressionOption : null;
} else {
throw new IllegalArgumentException("Unsupported " + CompressionOptions.class.getSimpleName() +
": " + compressionOption);
}
}
- if (brotliOptions == null) {
- brotliOptions = StandardCompressionOptions.brotli();
- }
- if (gzipOptions == null) {
- gzipOptions = StandardCompressionOptions.gzip();
- }
- if (deflateOptions == null) {
- deflateOptions = StandardCompressionOptions.deflate();
- }
- if (zstdOptions == null) {
- zstdOptions = StandardCompressionOptions.zstd();
- }
}
- this.brotliOptions = brotliOptions;
+
this.gzipOptions = gzipOptions;
this.deflateOptions = deflateOptions;
+ this.brotliOptions = brotliOptions;
this.zstdOptions = zstdOptions;
- this.factories = new HashMap() {
- {
- put("gzip", new GzipEncoderFactory());
- put("deflate", new DeflateEncoderFactory());
- put("br", new BrEncoderFactory());
- put("zstd", new ZstdEncoderFactory());
- }
- };
+
+ this.factories = new HashMap();
+
+ if (this.gzipOptions != null) {
+ this.factories.put("gzip", new GzipEncoderFactory());
+ }
+ if (this.deflateOptions != null) {
+ this.factories.put("deflate", new DeflateEncoderFactory());
+ }
+ if (this.brotliOptions != null) {
+ this.factories.put("br", new BrEncoderFactory());
+ }
+ if (this.zstdOptions != null) {
+ this.factories.put("zstd", new ZstdEncoderFactory());
+ }
+
this.compressionLevel = -1;
this.windowBits = -1;
this.memLevel = -1;
@@ -314,27 +314,27 @@ public class HttpContentCompressor extends HttpContentEncoder {
}
}
if (brQ > 0.0f || zstdQ > 0.0f || gzipQ > 0.0f || deflateQ > 0.0f) {
- if (brQ != -1.0f && brQ >= zstdQ) {
- return Brotli.isAvailable() ? "br" : null;
- } else if (zstdQ != -1.0f && zstdQ >= gzipQ) {
+ if (brQ != -1.0f && brQ >= zstdQ && this.brotliOptions != null) {
+ return "br";
+ } else if (zstdQ != -1.0f && zstdQ >= gzipQ && this.zstdOptions != null) {
return "zstd";
- } else if (gzipQ != -1.0f && gzipQ >= deflateQ) {
+ } else if (gzipQ != -1.0f && gzipQ >= deflateQ && this.gzipOptions != null) {
return "gzip";
- } else if (deflateQ != -1.0f) {
+ } else if (deflateQ != -1.0f && this.deflateOptions != null) {
return "deflate";
}
}
if (starQ > 0.0f) {
- if (brQ == -1.0f) {
- return Brotli.isAvailable() ? "br" : null;
+ if (brQ == -1.0f && this.brotliOptions != null) {
+ return "br";
}
- if (zstdQ == -1.0f) {
+ if (zstdQ == -1.0f && this.zstdOptions != null) {
return "zstd";
}
- if (gzipQ == -1.0f) {
+ if (gzipQ == -1.0f && this.gzipOptions != null) {
return "gzip";
}
- if (deflateQ == -1.0f) {
+ if (deflateQ == -1.0f && this.deflateOptions != null) {
return "deflate";
}
}
diff --git a/codec-http/src/test/java/io/netty/handler/codec/http/HttpContentCompressorOptionsTest.java b/codec-http/src/test/java/io/netty/handler/codec/http/HttpContentCompressorOptionsTest.java
index 7fce2234d6..285be33f50 100644
--- a/codec-http/src/test/java/io/netty/handler/codec/http/HttpContentCompressorOptionsTest.java
+++ b/codec-http/src/test/java/io/netty/handler/codec/http/HttpContentCompressorOptionsTest.java
@@ -16,6 +16,8 @@
package io.netty.handler.codec.http;
import io.netty.channel.embedded.EmbeddedChannel;
+import io.netty.handler.codec.compression.StandardCompressionOptions;
+
import org.junit.jupiter.api.Test;
import static org.hamcrest.CoreMatchers.instanceOf;
@@ -30,7 +32,12 @@ class HttpContentCompressorOptionsTest {
@Test
void testGetBrTargetContentEncoding() {
- HttpContentCompressor compressor = new HttpContentCompressor();
+ HttpContentCompressor compressor = new HttpContentCompressor(
+ StandardCompressionOptions.gzip(),
+ StandardCompressionOptions.deflate(),
+ StandardCompressionOptions.brotli(),
+ StandardCompressionOptions.zstd()
+ );
String[] tests = {
// Accept-Encoding -> Content-Encoding
@@ -52,7 +59,12 @@ class HttpContentCompressorOptionsTest {
@Test
void testGetZstdTargetContentEncoding() {
- HttpContentCompressor compressor = new HttpContentCompressor();
+ HttpContentCompressor compressor = new HttpContentCompressor(
+ StandardCompressionOptions.gzip(),
+ StandardCompressionOptions.deflate(),
+ StandardCompressionOptions.brotli(),
+ StandardCompressionOptions.zstd()
+ );
String[] tests = {
// Accept-Encoding -> Content-Encoding
diff --git a/codec/src/main/java/io/netty/handler/codec/compression/Zstd.java b/codec/src/main/java/io/netty/handler/codec/compression/Zstd.java
new file mode 100644
index 0000000000..3d03524422
--- /dev/null
+++ b/codec/src/main/java/io/netty/handler/codec/compression/Zstd.java
@@ -0,0 +1,75 @@
+/*
+ * Copyright 2021 The Netty Project
+ *
+ * The Netty Project licenses this file to you under the Apache License,
+ * version 2.0 (the "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at:
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations
+ * under the License.
+ */
+
+package io.netty.handler.codec.compression;
+
+import io.netty.util.internal.PlatformDependent;
+import io.netty.util.internal.logging.InternalLogger;
+import io.netty.util.internal.logging.InternalLoggerFactory;
+
+public final class Zstd {
+
+ private static final InternalLogger logger = InternalLoggerFactory.getInstance(Zstd.class);
+ private static final Throwable cause;
+
+ static {
+ Throwable t = null;
+
+ try {
+ Class.forName("com.github.luben.zstd.Zstd", false,
+ PlatformDependent.getClassLoader(Zstd.class));
+ } catch (ClassNotFoundException e) {
+ t = e;
+ logger.debug(
+ "zstd-jni not in the classpath; Zstd support will be unavailable.");
+ } catch (Throwable e) {
+ t = e;
+ logger.debug("Failed to load zstd-jni; Zstd support will be unavailable.", t);
+ }
+
+ cause = t;
+ }
+
+ /**
+ *
+ * @return true when zstd-jni is in the classpath
+ * and native library is available on this platform and could be loaded
+ */
+ public static boolean isAvailable() {
+ return cause == null;
+ }
+
+ /**
+ * Throws when zstd support is missing from the classpath or is unavailable on this platform
+ * @throws Throwable a ClassNotFoundException if zstd-jni is missing
+ * or a ExceptionInInitializerError if zstd native lib can't be loaded
+ */
+ public static void ensureAvailability() throws Throwable {
+ if (cause != null) {
+ throw cause;
+ }
+ }
+
+ /**
+ * Returns {@link Throwable} of unavailability cause
+ */
+ public static Throwable cause() {
+ return cause;
+ }
+
+ private Zstd() {
+ }
+}