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() { + } +}