Skip to content

Commit

Permalink
Adds decompressor tests
Browse files Browse the repository at this point in the history
(cherry picked from commit 1aa2223)
  • Loading branch information
Karm authored and gsmet committed Nov 12, 2024
1 parent ed41748 commit cbc47ed
Show file tree
Hide file tree
Showing 6 changed files with 279 additions and 30 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package io.quarkus.compressors.it;

import jakarta.ws.rs.Path;

@Path("/decompressed")
public class AllDecompressResource extends DecompressResource {
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package io.quarkus.compressors.it;

import static io.quarkus.compressors.it.Testflow.runTest;
import static io.quarkus.compressors.it.Testflow.runCompressorsTest;
import static io.quarkus.compressors.it.Testflow.runDecompressorsTest;

import java.net.URL;

Expand All @@ -14,7 +15,10 @@
public class RESTEndpointsTest {

@TestHTTPResource(value = "/compressed")
URL url;
URL urlCompressed;

@TestHTTPResource(value = "/decompressed")
URL urlDEcompressed;

@ParameterizedTest
@CsvSource(value = {
Expand All @@ -31,6 +35,88 @@ public class RESTEndpointsTest {
//@formatter:on
}, delimiter = '|', ignoreLeadingAndTrailingWhitespace = true, nullValues = "null")
public void testCompressors(String endpoint, String acceptEncoding, String contentEncoding, String contentLength) {
runTest(url.toString() + endpoint, acceptEncoding, contentEncoding, contentLength);
runCompressorsTest(urlCompressed.toString() + endpoint, acceptEncoding, contentEncoding, contentLength);
}

@ParameterizedTest
@CsvSource(value = {
//@formatter:off
// Context | Accept-Encoding | Content-Encoding | Method
"/text | identity | br | POST",
"/text | identity | gzip | POST",
"/text | identity | deflate | POST",
"/text | identity | br | PUT",
"/text | identity | gzip | PUT",
"/text | identity | deflate | PUT",
"/text | deflate | br | POST",
"/text | deflate | gzip | POST",
"/text | deflate | deflate | POST",
"/text | gzip | br | PUT",
"/text | gzip | gzip | PUT",
"/text | gzip | deflate | PUT",
"/text | br | br | POST",
"/text | br | gzip | POST",
"/text | br | deflate | POST",
"/text | br | br | PUT",
"/text | br | gzip | PUT",
"/text | gzip,br,deflate | deflate | PUT",
"/json | identity | br | POST",
"/json | identity | gzip | POST",
"/json | identity | deflate | POST",
"/json | identity | br | PUT",
"/json | identity | gzip | PUT",
"/json | identity | deflate | PUT",
"/json | deflate | br | POST",
"/json | deflate | gzip | POST",
"/json | deflate | deflate | POST",
"/json | gzip | br | PUT",
"/json | gzip | gzip | PUT",
"/json | gzip | deflate | PUT",
"/json | br | br | POST",
"/json | br | gzip | POST",
"/json | br | deflate | POST",
"/json | br | br | PUT",
"/json | br | gzip | PUT",
"/json | gzip,br,deflate | deflate | PUT",
"/xml | identity | br | POST",
"/xml | identity | gzip | POST",
"/xml | identity | deflate | POST",
"/xml | identity | br | PUT",
"/xml | identity | gzip | PUT",
"/xml | identity | deflate | PUT",
"/xml | deflate | br | POST",
"/xml | deflate | gzip | POST",
"/xml | deflate | deflate | POST",
"/xml | gzip | br | PUT",
"/xml | gzip | gzip | PUT",
"/xml | gzip | deflate | PUT",
"/xml | br | br | POST",
"/xml | br | gzip | POST",
"/xml | br | deflate | POST",
"/xml | br | br | PUT",
"/xml | br | gzip | PUT",
"/xml | gzip,br,deflate | deflate | PUT",
"/xhtml | identity | br | POST",
"/xhtml | identity | gzip | POST",
"/xhtml | identity | deflate | POST",
"/xhtml | identity | br | PUT",
"/xhtml | identity | gzip | PUT",
"/xhtml | identity | deflate | PUT",
"/xhtml | deflate | br | POST",
"/xhtml | deflate | gzip | POST",
"/xhtml | deflate | deflate | POST",
"/xhtml | gzip | br | PUT",
"/xhtml | gzip | gzip | PUT",
"/xhtml | gzip | deflate | PUT",
"/xhtml | br | br | POST",
"/xhtml | br | gzip | POST",
"/xhtml | br | deflate | POST",
"/xhtml | br | br | PUT",
"/xhtml | br | gzip | PUT",
"/xhtml | gzip,br,deflate | deflate | PUT"
//@formatter:on
}, delimiter = '|', ignoreLeadingAndTrailingWhitespace = true, nullValues = "null")
public void testDecompressors(String endpoint, String acceptEncoding, String contentEncoding, String method) {
runDecompressorsTest(urlDEcompressed.toString() + endpoint, acceptEncoding, contentEncoding, method);
}
}
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
# Enables sending clients compressed responses.
quarkus.http.enable-compression=true
# Enables decompressing requests from clients.
quarkus.http.enable-decompression=true
# Brotli is not present by default, so we add it all here:
quarkus.http.compressors=deflate,gzip,br
# This test the level actually makes impact. When left to default,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
package io.quarkus.compressors.it;

import jakarta.ws.rs.Consumes;
import jakarta.ws.rs.POST;
import jakarta.ws.rs.PUT;
import jakarta.ws.rs.Path;
import jakarta.ws.rs.Produces;
import jakarta.ws.rs.core.MediaType;

/**
* Resource with endpoints that consume compressed data
* in POST and PUT bodies from the client.
* Depending on the accept-encoding, the data is then
* compressed again and sent to the client
* </br>
* e.g. Client sends a gzipped POST body and receives
* a brotli compressed response body.
* </br>
* The endpoint looks like a dummy echo service, but
* there is compression and decompression going on behind
* the scenes in Vert.x. -> Netty.
* </br>
* See: https://github.com/quarkusio/quarkus/pull/44348
*/
public class DecompressResource {

@POST
@Path("/text")
@Consumes(MediaType.TEXT_PLAIN)
@Produces(MediaType.TEXT_PLAIN)
public String textPost(String text) {
return text;
}

@PUT
@Path("/text")
@Consumes(MediaType.TEXT_PLAIN)
@Produces(MediaType.TEXT_PLAIN)
public String textPut(String text) {
return text;
}

@POST
@Path("/json")
@Produces(MediaType.APPLICATION_JSON)
@Consumes(MediaType.APPLICATION_JSON)
public String jsonPost(String json) {
return json;
}

@PUT
@Path("/json")
@Produces(MediaType.APPLICATION_JSON)
@Consumes(MediaType.APPLICATION_JSON)
public String jsonPut(String json) {
return json;
}

@POST
@Path("/xml")
@Produces(MediaType.TEXT_XML)
@Consumes(MediaType.TEXT_XML)
public String xmlPost(String xml) {
return xml;
}

@PUT
@Path("/xml")
@Produces(MediaType.TEXT_XML)
@Consumes(MediaType.TEXT_XML)
public String xmlPut(String xml) {
return xml;
}

@POST
@Path("/xhtml")
@Produces(MediaType.APPLICATION_XHTML_XML)
@Consumes(MediaType.APPLICATION_XHTML_XML)
public String xhtmlPost(String xhtml) {
return xhtml;
}

@PUT
@Path("/xhtml")
@Produces(MediaType.APPLICATION_XHTML_XML)
@Consumes(MediaType.APPLICATION_XHTML_XML)
public String xhtmlPut(String xhtml) {
return xhtml;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,15 @@
import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.junit.jupiter.api.Assertions.fail;

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;
import java.util.zip.DeflaterOutputStream;
import java.util.zip.GZIPOutputStream;

import com.aayushatharva.brotli4j.encoder.BrotliOutputStream;

import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
Expand All @@ -34,15 +40,15 @@ public class Testflow {
public static final int COMPRESSION_TOLERANCE_PERCENT = 2;

/**
* This test logic is shared by both "all" module and "some" module.
* See their RESTEndpointsTest classes.
* This test logic is shared by both "all" module and "some" module. See their RESTEndpointsTest classes.
*
* @param endpoint
* @param acceptEncoding
* @param contentEncoding
* @param contentLength
*/
public static void runTest(String endpoint, String acceptEncoding, String contentEncoding, String contentLength) {
public static void runCompressorsTest(String endpoint, String acceptEncoding, String contentEncoding,
String contentLength) {
LOG.infof("Endpoint %s; Accept-Encoding: %s; Content-Encoding: %s; Content-Length: %s",
endpoint, acceptEncoding, contentEncoding, contentLength);
// RestAssured
Expand Down Expand Up @@ -97,31 +103,88 @@ public static void runTest(String endpoint, String acceptEncoding, String conten
expectedLength + " plus " + COMPRESSION_TOLERANCE_PERCENT + "% tolerance, i.e. "
+ expectedLengthWithTolerance + ".");
}
assertEquals(TEXT, decompress(actualEncoding, response.body().getBytes()), "Unexpected body text.");
} catch (InterruptedException | ExecutionException e) {
fail(e);
}
}

final String body;
if (actualEncoding != null && !"identity".equalsIgnoreCase(actualEncoding)) {
EmbeddedChannel channel = null;
if ("gzip".equalsIgnoreCase(actualEncoding)) {
channel = new EmbeddedChannel(newZlibDecoder(ZlibWrapper.GZIP));
} else if ("deflate".equalsIgnoreCase(actualEncoding)) {
channel = new EmbeddedChannel(newZlibDecoder(ZlibWrapper.ZLIB));
} else if ("br".equalsIgnoreCase(actualEncoding)) {
channel = new EmbeddedChannel(new BrotliDecoder());
} else {
fail("Unexpected compression used by server: " + actualEncoding);
}
channel.writeInbound(Unpooled.copiedBuffer(response.body().getBytes()));
channel.finish();
final ByteBuf decompressed = channel.readInbound();
body = decompressed.readCharSequence(decompressed.readableBytes(), StandardCharsets.UTF_8).toString();
} else {
body = response.body().toString(StandardCharsets.UTF_8);
}

assertEquals(TEXT, body,
"Unexpected body text.");
public static void runDecompressorsTest(String endpoint, String acceptEncoding, String contentEncoding,
String method) {
LOG.infof("Endpoint %s; Accept-Encoding: %s; Content-Encoding: %s; Method: %s",
endpoint, acceptEncoding, contentEncoding, method);
final WebClient client = WebClient.create(Vertx.vertx(), new WebClientOptions()
.setLogActivity(true)
.setFollowRedirects(true)
.setDecompressionSupported(false));
final CompletableFuture<HttpResponse<Buffer>> future = new CompletableFuture<>();
client.postAbs(endpoint)
.putHeader(HttpHeaders.CONTENT_ENCODING.toString(), contentEncoding)
.putHeader(HttpHeaders.ACCEPT.toString(), "*/*")
.sendBuffer(compress(contentEncoding, TEXT), ar -> {
if (ar.succeeded()) {
future.complete(ar.result());
} else {
future.completeExceptionally(ar.cause());
}
});
try {
final HttpResponse<Buffer> response = future.get();
final String actualEncoding = response.headers().get("content-encoding");
final String body = decompress(actualEncoding, response.body().getBytes());
assertEquals(OK.code(), response.statusCode(), "Http status must be OK.");
assertEquals(TEXT, body, "Unexpected body text.");
} catch (InterruptedException | ExecutionException e) {
fail(e);
}
}

public static Buffer compress(String algorithm, String payload) {
final ByteArrayOutputStream byteStream = new ByteArrayOutputStream();
if ("gzip".equalsIgnoreCase(algorithm)) {
try (GZIPOutputStream gzipStream = new GZIPOutputStream(byteStream)) {
gzipStream.write(payload.getBytes(StandardCharsets.UTF_8));
} catch (IOException e) {
throw new RuntimeException("Gzip compression failed", e);
}
return Buffer.buffer(byteStream.toByteArray());
} else if ("br".equalsIgnoreCase(algorithm)) {
try (BrotliOutputStream brotliStream = new BrotliOutputStream(byteStream)) {
brotliStream.write(payload.getBytes(StandardCharsets.UTF_8));
} catch (IOException e) {
throw new RuntimeException("Brotli compression failed", e);
}
return Buffer.buffer(byteStream.toByteArray());
} else if ("deflate".equalsIgnoreCase(algorithm)) {
try (DeflaterOutputStream deflateStream = new DeflaterOutputStream(byteStream)) {
deflateStream.write(payload.getBytes(StandardCharsets.UTF_8));
} catch (IOException e) {
throw new RuntimeException("Deflate compression failed", e);
}
return Buffer.buffer(byteStream.toByteArray());
} else {
throw new IllegalArgumentException("Unsupported encoding: " + algorithm);
}
}

public static String decompress(String algorithm, byte[] payload) {
if (algorithm != null && !"identity".equalsIgnoreCase(algorithm)) {
final EmbeddedChannel channel;
if ("gzip".equalsIgnoreCase(algorithm)) {
channel = new EmbeddedChannel(newZlibDecoder(ZlibWrapper.GZIP));
} else if ("deflate".equalsIgnoreCase(algorithm)) {
channel = new EmbeddedChannel(newZlibDecoder(ZlibWrapper.ZLIB));
} else if ("br".equalsIgnoreCase(algorithm)) {
channel = new EmbeddedChannel(new BrotliDecoder());
} else {
throw new RuntimeException("Unexpected compression used by server: " + algorithm);
}
channel.writeInbound(Unpooled.copiedBuffer(payload));
channel.finish();
final ByteBuf decompressed = channel.readInbound();
return decompressed.readCharSequence(decompressed.readableBytes(), StandardCharsets.UTF_8).toString();
} else {
return new String(payload, StandardCharsets.UTF_8);
}
}
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
package io.quarkus.compressors.it;

import static io.quarkus.compressors.it.Testflow.runTest;
import static io.quarkus.compressors.it.Testflow.runCompressorsTest;

import java.net.URL;

Expand Down Expand Up @@ -30,6 +30,6 @@ public class RESTEndpointsTest {
//@formatter:on
}, delimiter = '|', ignoreLeadingAndTrailingWhitespace = true, nullValues = "null")
public void testCompressors(String endpoint, String acceptEncoding, String contentEncoding, String contentLength) {
runTest(url.toString() + endpoint, acceptEncoding, contentEncoding, contentLength);
runCompressorsTest(url.toString() + endpoint, acceptEncoding, contentEncoding, contentLength);
}
}

0 comments on commit cbc47ed

Please sign in to comment.