Repository: netty/netty
Issue: Make content compression configurable per content type
Labels: not a bug

### Expected behavior
Content compression can be enabled for specific content types. This mean we would like to configure ContentCompressor so we can define what content types should be compressed; in this case we wouldn't compress JPEG or GIF files as those formats are already compressed.

This PR tried to fix the issue: https://github.com/netty/netty/pull/3847 but it seems it was abandoned.

### Actual behavior
Compression affects all the content types,  even if there's little no gain from compressing that type (e.g., JPEG files).

### Netty version
netty-4.1.43.Final



Issue comments:
Very good idea. Recently I had the same thoughts. Also, it would be cool to have size limited compression. So for small messages like < 20 bytes you don't need compression. 
@dvlato @doom369 if I'm not mistaken this should be already possible by just subclassing `HttpContentCompressor` and overriding `acceptOutboundMessage(Object)`.
@njhill you are right. Also, `HttpContentCompressor` already has `contentSizeThreshold`. However, there is no such option for WebSockets. And it is not that trivial there :).
@dvlato @doom369 guys this is already exist see #8910 we use this functionality on production.
You can implement something like this:
```
public class SelectiveCompressionFilterProvider implements WebSocketExtensionFilterProvider {

    private final int contentSizeThreshold;

    public SelectiveCompressionFilterProvider(int contentSizeThreshold) {
        this.contentSizeThreshold = contentSizeThreshold;
    }

    @Override
    public WebSocketExtensionFilter encoderFilter() {
        return webSocketFrame -> {
            if (contentSizeThreshold > 0) {
                return (webSocketFrame instanceof BinaryWebSocketFrame || webSocketFrame instanceof TextWebSocketFrame)
                    && webSocketFrame.isFinalFragment()
                    && webSocketFrame.content().readableBytes() < contentSizeThreshold;
            }

            return false;
        };
    }

    @Override
    public WebSocketExtensionFilter decoderFilter() {
        return WebSocketExtensionFilter.NEVER_SKIP;
    }

}
```
and pass it to `PerMessageDeflateServerExtensionHandshaker` for example.
@amizurov cool, thanks for hint!
@dvlato @njhill here the code that allow to skip certain content types:

```
public final class BlynkHttpCompressor extends HttpContentCompressor {

    @Override
    protected Result beginEncode(HttpResponse response, String acceptEncoding) throws Exception {
        if (response.headers().containsValue(HttpHeaderNames.CONTENT_TYPE, "font/woff2", false)) {
            return null;
        }
        return super.beginEncode(response, acceptEncoding);
    }

}
```
@dvlato as @doom369 said you can easily implement this. So closing this issue
Hi, 
I'd already tried the beginEncode() solution and now I've added some support in 'acceptOutboundMessage'  but I still see high spikes in the latency of our application (in the 99% percentile) when the compression filter has been added even though the client does not send any 'accept-encoding' header! Has anyone also experienced this issue?


@dvlato it could be anything. You need to profile in order to make correct conclusions that the problem in `HttpContentCompressor`. According to the code in `HttpContentCompressor.beginEncode()` it does nothing if no encoding header was provided.

Pull request review comments:

Patch that resolved the issue:
diff --git a/codec-http/src/main/java/io/netty/handler/codec/http/HttpContentCompressor.java b/codec-http/src/main/java/io/netty/handler/codec/http/HttpContentCompressor.java
index e80469f26e4..b40fae66bc5 100644
--- a/codec-http/src/main/java/io/netty/handler/codec/http/HttpContentCompressor.java
+++ b/codec-http/src/main/java/io/netty/handler/codec/http/HttpContentCompressor.java
@@ -132,15 +132,15 @@ public void handlerAdded(ChannelHandlerContext ctx) throws Exception {
     }
 
     @Override
-    protected Result beginEncode(HttpResponse headers, String acceptEncoding) throws Exception {
+    protected Result beginEncode(HttpResponse httpResponse, String acceptEncoding) throws Exception {
         if (this.contentSizeThreshold > 0) {
-            if (headers instanceof HttpContent &&
-                    ((HttpContent) headers).content().readableBytes() < contentSizeThreshold) {
+            if (httpResponse instanceof HttpContent &&
+                    ((HttpContent) httpResponse).content().readableBytes() < contentSizeThreshold) {
                 return null;
             }
         }
 
-        String contentEncoding = headers.headers().get(HttpHeaderNames.CONTENT_ENCODING);
+        String contentEncoding = httpResponse.headers().get(HttpHeaderNames.CONTENT_ENCODING);
         if (contentEncoding != null) {
             // Content-Encoding was set, either as something specific or as the IDENTITY encoding
             // Therefore, we should NOT encode here
diff --git a/codec-http/src/main/java/io/netty/handler/codec/http/HttpContentEncoder.java b/codec-http/src/main/java/io/netty/handler/codec/http/HttpContentEncoder.java
index 813965089c4..6f1070c21fd 100644
--- a/codec-http/src/main/java/io/netty/handler/codec/http/HttpContentEncoder.java
+++ b/codec-http/src/main/java/io/netty/handler/codec/http/HttpContentEncoder.java
@@ -288,8 +288,8 @@ private boolean encodeContent(HttpContent c, List<Object> out) {
     /**
      * Prepare to encode the HTTP message content.
      *
-     * @param headers
-     *        the headers
+     * @param httpResponse
+     *        the http response
      * @param acceptEncoding
      *        the value of the {@code "Accept-Encoding"} header
      *
@@ -299,7 +299,7 @@ private boolean encodeContent(HttpContent c, List<Object> out) {
      *         {@code null} if {@code acceptEncoding} is unsupported or rejected
      *         and thus the content should be handled as-is (i.e. no encoding).
      */
-    protected abstract Result beginEncode(HttpResponse headers, String acceptEncoding) throws Exception;
+    protected abstract Result beginEncode(HttpResponse httpResponse, String acceptEncoding) throws Exception;
 
     @Override
     public void handlerRemoved(ChannelHandlerContext ctx) throws Exception {


Test patch:
diff --git a/codec-http/src/test/java/io/netty/handler/codec/http/HttpContentEncoderTest.java b/codec-http/src/test/java/io/netty/handler/codec/http/HttpContentEncoderTest.java
index 6301ee8c0b5..9bb88385a8c 100644
--- a/codec-http/src/test/java/io/netty/handler/codec/http/HttpContentEncoderTest.java
+++ b/codec-http/src/test/java/io/netty/handler/codec/http/HttpContentEncoderTest.java
@@ -41,7 +41,7 @@ public class HttpContentEncoderTest {
 
     private static final class TestEncoder extends HttpContentEncoder {
         @Override
-        protected Result beginEncode(HttpResponse headers, String acceptEncoding) {
+        protected Result beginEncode(HttpResponse httpResponse, String acceptEncoding) {
             return new Result("test", new EmbeddedChannel(new MessageToByteEncoder<ByteBuf>() {
                 @Override
                 protected void encode(ChannelHandlerContext ctx, ByteBuf in, ByteBuf out) throws Exception {
@@ -395,7 +395,7 @@ public void testHttp1_0() throws Exception {
     public void testCleanupThrows() {
         HttpContentEncoder encoder = new HttpContentEncoder() {
             @Override
-            protected Result beginEncode(HttpResponse headers, String acceptEncoding) throws Exception {
+            protected Result beginEncode(HttpResponse httpResponse, String acceptEncoding) throws Exception {
                 return new Result("myencoding", new EmbeddedChannel(
                         new ChannelInboundHandlerAdapter() {
                     @Override