From a2f101aa919ab2a15fc1cdf5e4f629ce13f7e1ea Mon Sep 17 00:00:00 2001 From: Francesco Nigro Date: Wed, 1 Jun 2022 11:38:31 +0200 Subject: [PATCH] Makes Brotli more native compilation friendly (#12409) Motivation: CompressorHttp2ConnectionEncoder shouldn't load anything specific to Brotli if it isn't available Modification: Allows a null Brotli option to be passed to CompressorHttp2ConnectionEncoder Result: GraalVM Native compilation without Brotli doesn't requires Brotli classes --- .../codec/http/HttpContentCompressor.java | 12 +++++++--- .../CompressorHttp2ConnectionEncoder.java | 24 +++++++++++++++---- 2 files changed, 29 insertions(+), 7 deletions(-) diff --git a/codec-http/src/main/java/io/netty5/handler/codec/http/HttpContentCompressor.java b/codec-http/src/main/java/io/netty5/handler/codec/http/HttpContentCompressor.java index e7bfaca9749..06264d6a9be 100644 --- a/codec-http/src/main/java/io/netty5/handler/codec/http/HttpContentCompressor.java +++ b/codec-http/src/main/java/io/netty5/handler/codec/http/HttpContentCompressor.java @@ -174,8 +174,13 @@ public HttpContentCompressor(int contentSizeThreshold, CompressionOptions... com } else { ObjectUtil.deepCheckNotNull("compressionOptions", compressionOptions); for (CompressionOptions compressionOption : compressionOptions) { - if (compressionOption instanceof BrotliOptions) { - // if we have BrotliOptions, it means Brotli is available + // BrotliOptions' class initialization depends on Brotli classes being on the classpath. + // The Brotli.isAvailable check ensures that BrotliOptions will only get instantiated if Brotli is + // on the classpath. + // This results in the static analysis of native-image identifying the instanceof BrotliOptions check + // and thus BrotliOptions itself as unreachable, enabling native-image to link all classes + // at build time and not complain about the missing Brotli classes. + if (Brotli.isAvailable() && compressionOption instanceof BrotliOptions) { brotliOptions = (BrotliOptions) compressionOption; } else if (compressionOption instanceof GzipOptions) { gzipOptions = (GzipOptions) compressionOption; @@ -205,7 +210,8 @@ public HttpContentCompressor(int contentSizeThreshold, CompressionOptions... com this.factories.put("deflate", ZlibCompressor.newFactory( ZlibWrapper.ZLIB, deflateOptions.compressionLevel())); } - if (this.brotliOptions != null) { + + if (Brotli.isAvailable() && this.brotliOptions != null) { this.factories.put("br", BrotliCompressor.newFactory(brotliOptions.parameters())); } if (this.zstdOptions != null) { diff --git a/codec-http2/src/main/java/io/netty5/handler/codec/http2/CompressorHttp2ConnectionEncoder.java b/codec-http2/src/main/java/io/netty5/handler/codec/http2/CompressorHttp2ConnectionEncoder.java index 1a9ad48da5a..ff25b3d2825 100644 --- a/codec-http2/src/main/java/io/netty5/handler/codec/http2/CompressorHttp2ConnectionEncoder.java +++ b/codec-http2/src/main/java/io/netty5/handler/codec/http2/CompressorHttp2ConnectionEncoder.java @@ -18,6 +18,7 @@ import io.netty5.channel.ChannelHandlerContext; import io.netty5.channel.embedded.EmbeddedChannel; import io.netty5.handler.codec.ByteToMessageDecoder; +import io.netty5.handler.codec.compression.Brotli; import io.netty5.handler.codec.compression.BrotliCompressor; import io.netty5.handler.codec.compression.BrotliOptions; import io.netty5.handler.codec.compression.CompressionOptions; @@ -74,8 +75,7 @@ public class CompressorHttp2ConnectionEncoder extends DecoratingHttp2ConnectionE * with default implementation of {@link StandardCompressionOptions} */ public CompressorHttp2ConnectionEncoder(Http2ConnectionEncoder delegate) { - this(delegate, StandardCompressionOptions.brotli(), StandardCompressionOptions.gzip(), - StandardCompressionOptions.deflate()); + this(delegate, defaultCompressionOptions()); } /** @@ -114,7 +114,13 @@ public CompressorHttp2ConnectionEncoder(Http2ConnectionEncoder delegate, ObjectUtil.deepCheckNotNull("CompressionOptions", compressionOptionsArgs); for (CompressionOptions compressionOptions : compressionOptionsArgs) { - if (compressionOptions instanceof BrotliOptions) { + // BrotliOptions' class initialization depends on Brotli classes being on the classpath. + // The Brotli.isAvailable check ensures that BrotliOptions will only get instantiated if Brotli is on + // the classpath. + // This results in the static analysis of native-image identifying the instanceof BrotliOptions check + // and thus BrotliOptions itself as unreachable, enabling native-image to link all classes at build time + // and not complain about the missing Brotli classes. + if (Brotli.isAvailable() && compressionOptions instanceof BrotliOptions) { brotliOptions = (BrotliOptions) compressionOptions; } else if (compressionOptions instanceof GzipOptions) { gzipCompressionOptions = (GzipOptions) compressionOptions; @@ -142,6 +148,16 @@ public void onStreamRemoved(Http2Stream stream) { }); } + private static CompressionOptions[] defaultCompressionOptions() { + if (Brotli.isAvailable()) { + return new CompressionOptions[] { + StandardCompressionOptions.brotli(), + StandardCompressionOptions.gzip(), + StandardCompressionOptions.deflate() }; + } + return new CompressionOptions[] { StandardCompressionOptions.gzip(), StandardCompressionOptions.deflate() }; + } + @Override public Future writeData(final ChannelHandlerContext ctx, final int streamId, ByteBuf data, int padding, final boolean endOfStream) { @@ -248,7 +264,7 @@ protected Compressor newContentCompressor(ChannelHandlerContext ctx, CharSequenc if (DEFLATE.contentEqualsIgnoreCase(contentEncoding) || X_DEFLATE.contentEqualsIgnoreCase(contentEncoding)) { return newCompressionChannel(ctx, ZlibWrapper.ZLIB); } - if (brotliOptions != null && BR.contentEqualsIgnoreCase(contentEncoding)) { + if (Brotli.isAvailable() && brotliOptions != null && BR.contentEqualsIgnoreCase(contentEncoding)) { return BrotliCompressor.newFactory(brotliOptions.parameters()).get(); } if (zstdOptions != null && ZSTD.contentEqualsIgnoreCase(contentEncoding)) {