Skip to content

Commit

Permalink
Merge pull request #44348 from zakkak/2024-11-06-fix-brotli-decompres…
Browse files Browse the repository at this point in the history
…sion-43392

Enable Brotli decompression
  • Loading branch information
cescoffier authored Nov 12, 2024
2 parents c41dfd6 + 4174cce commit 7e63f4b
Show file tree
Hide file tree
Showing 7 changed files with 309 additions and 46 deletions.
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package io.quarkus.netty.runtime.graal;

import static io.netty.handler.codec.http.HttpHeaderValues.BR;
import static io.netty.handler.codec.http.HttpHeaderValues.DEFLATE;
import static io.netty.handler.codec.http.HttpHeaderValues.GZIP;
import static io.netty.handler.codec.http.HttpHeaderValues.X_DEFLATE;
Expand Down Expand Up @@ -43,13 +44,13 @@
import io.netty.buffer.ByteBufAllocator;
import io.netty.channel.Channel;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelHandler;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.DefaultChannelPromise;
import io.netty.channel.embedded.EmbeddedChannel;
import io.netty.handler.codec.compression.Brotli;
import io.netty.handler.codec.compression.BrotliDecoder;
import io.netty.handler.codec.compression.ZlibCodecFactory;
import io.netty.handler.codec.compression.ZlibWrapper;
import io.netty.handler.codec.http.HttpHeaderValues;
import io.netty.handler.codec.http2.Http2Exception;
import io.netty.handler.ssl.ApplicationProtocolConfig;
import io.netty.handler.ssl.ApplicationProtocolConfig.SelectorFailureBehavior;
Expand Down Expand Up @@ -518,6 +519,10 @@ protected EmbeddedChannel newContentDecoder(String contentEncoding) throws Excep
return new EmbeddedChannel(ctx.channel().id(), ctx.channel().metadata().hasDisconnect(),
ctx.channel().config(), ZlibCodecFactory.newZlibDecoder(wrapper));
}
if (Brotli.isAvailable() && BR.contentEqualsIgnoreCase(contentEncoding)) {
return new EmbeddedChannel(ctx.channel().id(), ctx.channel().metadata().hasDisconnect(),
ctx.channel().config(), new BrotliDecoder());
}

// 'identity' or unsupported
return null;
Expand All @@ -533,21 +538,23 @@ final class Target_io_netty_handler_codec_http2_DelegatingDecompressorFrameListe
@Substitute
protected EmbeddedChannel newContentDecompressor(ChannelHandlerContext ctx, CharSequence contentEncoding)
throws Http2Exception {
if (!HttpHeaderValues.GZIP.contentEqualsIgnoreCase(contentEncoding)
&& !HttpHeaderValues.X_GZIP.contentEqualsIgnoreCase(contentEncoding)) {
if (!HttpHeaderValues.DEFLATE.contentEqualsIgnoreCase(contentEncoding)
&& !HttpHeaderValues.X_DEFLATE.contentEqualsIgnoreCase(contentEncoding)) {
return null;
} else {
ZlibWrapper wrapper = this.strict ? ZlibWrapper.ZLIB : ZlibWrapper.ZLIB_OR_NONE;
return new EmbeddedChannel(ctx.channel().id(), ctx.channel().metadata().hasDisconnect(),
ctx.channel().config(),
new ChannelHandler[] { ZlibCodecFactory.newZlibDecoder(wrapper) });
}
} else {
return new EmbeddedChannel(ctx.channel().id(), ctx.channel().metadata().hasDisconnect(), ctx.channel().config(),
new ChannelHandler[] { ZlibCodecFactory.newZlibDecoder(ZlibWrapper.GZIP) });
if (GZIP.contentEqualsIgnoreCase(contentEncoding) || X_GZIP.contentEqualsIgnoreCase(contentEncoding)) {
return new EmbeddedChannel(ctx.channel().id(), ctx.channel().metadata().hasDisconnect(),
ctx.channel().config(), ZlibCodecFactory.newZlibDecoder(ZlibWrapper.GZIP));
}
if (DEFLATE.contentEqualsIgnoreCase(contentEncoding) || X_DEFLATE.contentEqualsIgnoreCase(contentEncoding)) {
final ZlibWrapper wrapper = strict ? ZlibWrapper.ZLIB : ZlibWrapper.ZLIB_OR_NONE;
// To be strict, 'deflate' means ZLIB, but some servers were not implemented correctly.
return new EmbeddedChannel(ctx.channel().id(), ctx.channel().metadata().hasDisconnect(),
ctx.channel().config(), ZlibCodecFactory.newZlibDecoder(wrapper));
}
if (Brotli.isAvailable() && BR.contentEqualsIgnoreCase(contentEncoding)) {
return new EmbeddedChannel(ctx.channel().id(), ctx.channel().metadata().hasDisconnect(),
ctx.channel().config(), new BrotliDecoder());
}

// 'identity' or unsupported
return null;
}
}

Expand Down
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;
}
}
Loading

0 comments on commit 7e63f4b

Please sign in to comment.