Skip to content

Commit ca8e4f8

Browse files
authored
HTTP API calls hang with 'Accept-Encoding: zstd' (opensearch-project#17408)
Signed-off-by: Andriy Redko <drreta@gmail.com>
1 parent abe2333 commit ca8e4f8

File tree

3 files changed

+77
-4
lines changed

3 files changed

+77
-4
lines changed

CHANGELOG.md

+1
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
3636
- Fix exists queries on nested flat_object fields throws exception ([#16803](https://github.com/opensearch-project/OpenSearch/pull/16803))
3737
- Add highlighting for wildcard search on `match_only_text` field ([#17101](https://github.com/opensearch-project/OpenSearch/pull/17101))
3838
- Fix illegal argument exception when creating a PIT ([#16781](https://github.com/opensearch-project/OpenSearch/pull/16781))
39+
- Fix HTTP API calls that hang with 'Accept-Encoding: zstd' ([#17408](https://github.com/opensearch-project/OpenSearch/pull/17408))
3940

4041
### Security
4142

modules/transport-netty4/src/main/java/org/opensearch/http/netty4/Netty4HttpServerTransport.java

+72-3
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,8 @@
6161

6262
import java.net.InetSocketAddress;
6363
import java.net.SocketOption;
64+
import java.util.ArrayList;
65+
import java.util.List;
6466
import java.util.concurrent.TimeUnit;
6567

6668
import io.netty.bootstrap.ServerBootstrap;
@@ -77,6 +79,12 @@
7779
import io.netty.channel.SimpleChannelInboundHandler;
7880
import io.netty.channel.socket.nio.NioChannelOption;
7981
import io.netty.handler.codec.ByteToMessageDecoder;
82+
import io.netty.handler.codec.compression.Brotli;
83+
import io.netty.handler.codec.compression.CompressionOptions;
84+
import io.netty.handler.codec.compression.DeflateOptions;
85+
import io.netty.handler.codec.compression.GzipOptions;
86+
import io.netty.handler.codec.compression.StandardCompressionOptions;
87+
import io.netty.handler.codec.compression.ZstdEncoder;
8088
import io.netty.handler.codec.http.HttpContentCompressor;
8189
import io.netty.handler.codec.http.HttpContentDecompressor;
8290
import io.netty.handler.codec.http.HttpMessage;
@@ -440,7 +448,7 @@ protected void channelRead0(ChannelHandlerContext ctx, HttpMessage msg) throws E
440448
pipeline.addAfter(
441449
"aggregator",
442450
"encoder_compress",
443-
new HttpContentCompressor(handlingSettings.getCompressionLevel())
451+
new HttpContentCompressor(defaultCompressionOptions(handlingSettings.getCompressionLevel()))
444452
);
445453
}
446454
pipeline.addBefore("handler", "request_creator", requestCreator);
@@ -467,7 +475,10 @@ protected void configureDefaultHttpPipeline(ChannelPipeline pipeline) {
467475
aggregator.setMaxCumulationBufferComponents(transport.maxCompositeBufferComponents);
468476
pipeline.addLast("aggregator", aggregator);
469477
if (handlingSettings.isCompression()) {
470-
pipeline.addLast("encoder_compress", new HttpContentCompressor(handlingSettings.getCompressionLevel()));
478+
pipeline.addLast(
479+
"encoder_compress",
480+
new HttpContentCompressor(defaultCompressionOptions(handlingSettings.getCompressionLevel()))
481+
);
471482
}
472483
pipeline.addLast("request_creator", requestCreator);
473484
pipeline.addLast("response_creator", responseCreator);
@@ -512,7 +523,10 @@ protected void initChannel(Channel childChannel) throws Exception {
512523

513524
if (handlingSettings.isCompression()) {
514525
childChannel.pipeline()
515-
.addLast("encoder_compress", new HttpContentCompressor(handlingSettings.getCompressionLevel()));
526+
.addLast(
527+
"encoder_compress",
528+
new HttpContentCompressor(defaultCompressionOptions(handlingSettings.getCompressionLevel()))
529+
);
516530
}
517531

518532
childChannel.pipeline()
@@ -563,4 +577,59 @@ protected ChannelInboundHandlerAdapter createHeaderVerifier() {
563577
protected ChannelInboundHandlerAdapter createDecompressor() {
564578
return new HttpContentDecompressor();
565579
}
580+
581+
/**
582+
* Copy of {@link HttpContentCompressor} default compression options with ZSTD excluded:
583+
* although zstd-jni is on the classpath, {@link ZstdEncoder} requires direct buffers support
584+
* which by default {@link NettyAllocator} does not provide.
585+
*
586+
* @param compressionLevel
587+
* {@code 1} yields the fastest compression and {@code 9} yields the
588+
* best compression. {@code 0} means no compression. The default
589+
* compression level is {@code 6}.
590+
*
591+
* @return default compression options
592+
*/
593+
private static CompressionOptions[] defaultCompressionOptions(int compressionLevel) {
594+
return defaultCompressionOptions(compressionLevel, 15, 8);
595+
}
596+
597+
/**
598+
* Copy of {@link HttpContentCompressor} default compression options with ZSTD excluded:
599+
* although zstd-jni is on the classpath, {@link ZstdEncoder} requires direct buffers support
600+
* which by default {@link NettyAllocator} does not provide.
601+
*
602+
* @param compressionLevel
603+
* {@code 1} yields the fastest compression and {@code 9} yields the
604+
* best compression. {@code 0} means no compression. The default
605+
* compression level is {@code 6}.
606+
* @param windowBits
607+
* The base two logarithm of the size of the history buffer. The
608+
* value should be in the range {@code 9} to {@code 15} inclusive.
609+
* Larger values result in better compression at the expense of
610+
* memory usage. The default value is {@code 15}.
611+
* @param memLevel
612+
* How much memory should be allocated for the internal compression
613+
* state. {@code 1} uses minimum memory and {@code 9} uses maximum
614+
* memory. Larger values result in better and faster compression
615+
* at the expense of memory usage. The default value is {@code 8}
616+
*
617+
* @return default compression options
618+
*/
619+
private static CompressionOptions[] defaultCompressionOptions(int compressionLevel, int windowBits, int memLevel) {
620+
final List<CompressionOptions> options = new ArrayList<CompressionOptions>(4);
621+
final GzipOptions gzipOptions = StandardCompressionOptions.gzip(compressionLevel, windowBits, memLevel);
622+
final DeflateOptions deflateOptions = StandardCompressionOptions.deflate(compressionLevel, windowBits, memLevel);
623+
624+
options.add(gzipOptions);
625+
options.add(deflateOptions);
626+
options.add(StandardCompressionOptions.snappy());
627+
628+
if (Brotli.isAvailable()) {
629+
options.add(StandardCompressionOptions.brotli());
630+
}
631+
632+
return options.toArray(new CompressionOptions[0]);
633+
}
634+
566635
}

modules/transport-netty4/src/test/java/org/opensearch/http/netty4/Netty4HttpServerTransportTests.java

+4-1
Original file line numberDiff line numberDiff line change
@@ -393,7 +393,10 @@ public void dispatchBadRequest(final RestChannel channel, final ThreadContext th
393393

394394
try (Netty4HttpClient client = Netty4HttpClient.http()) {
395395
DefaultFullHttpRequest request = new DefaultFullHttpRequest(HttpVersion.HTTP_1_1, HttpMethod.GET, url);
396-
request.headers().add(HttpHeaderNames.ACCEPT_ENCODING, randomFrom("deflate", "gzip"));
396+
// ZSTD is not supported at the moment by NettyAllocator (needs direct buffers),
397+
// and Brotly is not on classpath.
398+
final String contentEncoding = randomFrom("deflate", "gzip", "snappy", "br", "zstd");
399+
request.headers().add(HttpHeaderNames.ACCEPT_ENCODING, contentEncoding);
397400
long numOfHugeAllocations = getHugeAllocationCount();
398401
final FullHttpResponse response = client.send(remoteAddress.address(), request);
399402
try {

0 commit comments

Comments
 (0)