diff --git a/build.gradle b/build.gradle index 49946c9894..d55b7d9a51 100644 --- a/build.gradle +++ b/build.gradle @@ -121,6 +121,7 @@ ext { // Testing brotli4jVersion = '1.16.0' + zstdJniVersion = '1.5.6-1' jacksonDatabindVersion = '2.17.0' testAddonVersion = reactorCoreVersion assertJVersion = '3.25.3' diff --git a/reactor-netty-http/build.gradle b/reactor-netty-http/build.gradle index 19b93d4312..652b342c1f 100644 --- a/reactor-netty-http/build.gradle +++ b/reactor-netty-http/build.gradle @@ -150,6 +150,9 @@ dependencies { testRuntimeOnly "org.slf4j:jcl-over-slf4j:$slf4jVersion" testRuntimeOnly "ch.qos.logback:logback-classic:$logbackVersion" + // Needed for zstd compression + testImplementation "com.github.luben:zstd-jni:$zstdJniVersion" + // Needed for Brotli compression testImplementation "com.aayushatharva.brotli4j:brotli4j:$brotli4jVersion" if (osdetector.classifier == "linux-aarch_64" || osdetector.classifier == "osx-aarch_64") { diff --git a/reactor-netty-http/src/test/java/reactor/netty/http/HttpCompressionClientServerTests.java b/reactor-netty-http/src/test/java/reactor/netty/http/HttpCompressionClientServerTests.java index cd11c0e744..22bebe782c 100644 --- a/reactor-netty-http/src/test/java/reactor/netty/http/HttpCompressionClientServerTests.java +++ b/reactor-netty-http/src/test/java/reactor/netty/http/HttpCompressionClientServerTests.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2017-2023 VMware, Inc. or its affiliates, All Rights Reserved. + * Copyright (c) 2017-2024 VMware, Inc. or its affiliates, All Rights Reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -29,6 +29,7 @@ import com.aayushatharva.brotli4j.decoder.DirectDecompress; import io.netty.buffer.Unpooled; import io.netty.handler.codec.compression.Brotli; +import io.netty.handler.codec.compression.Zstd; import io.netty.handler.codec.http.HttpHeaders; import io.netty.handler.ssl.util.InsecureTrustManagerFactory; import io.netty.handler.ssl.util.SelfSignedCertificate; @@ -225,6 +226,37 @@ void brotliServerCompressionEnabled(HttpServer server, HttpClient client) throws assertThat(new String(decompressedData, Charset.defaultCharset())).isEqualTo("reply"); } + @ParameterizedCompressionTest + void zstdServerCompressionEnabled(HttpServer server, HttpClient client) throws Exception { + assertThat(Zstd.isAvailable()).isTrue(); + disposableServer = + server.compress(true) + .handle((in, out) -> out.sendString(Mono.just("reply"))) + .bindNow(Duration.ofSeconds(10)); + + //don't activate compression on the client options to avoid auto-handling (which removes the header) + Tuple2 resp = + //edit the header manually to attempt to trigger compression on server side + client.port(disposableServer.port()) + .compress(false) + .headers(h -> h.add("Accept-Encoding", "zstd, gzip")) + .get() + .uri("/test") + .responseSingle((res, buf) -> buf.asByteArray() + .zipWith(Mono.just(res.responseHeaders()))) + .block(Duration.ofSeconds(10)); + + assertThat(resp).isNotNull(); + assertThat(resp.getT2().get("content-encoding")).isEqualTo("zstd"); + + final byte[] compressedData = resp.getT1(); + assertThat(new String(compressedData, Charset.defaultCharset())).isNotEqualTo("reply"); + + final byte[] decompressedData = com.github.luben.zstd.Zstd.decompress(compressedData, 1_000); + assertThat(decompressedData).isNotEmpty(); + assertThat(new String(decompressedData, Charset.defaultCharset())).isEqualTo("reply"); + } + @ParameterizedCompressionTest void serverCompressionEnabledSmallResponse(HttpServer server, HttpClient client) { disposableServer =