Repository: netty/netty (Java)

Issue:
Extend decompression of HttpContentDecompressor.java
### Actual behaviour

At the moment the [HttpContentDecompressor.java](https://github.com/netty/netty/blob/cf5c467f4ead5ba5e1e36f5c4903af78bd49c35c/codec-http/src/main/java/io/netty/handler/codec/http/HttpContentDecompressor.java#LL58C9-L73) support:

- deflate
- gzip
- brotli

I wonder if we should extend it ?
I am specifically in need of supporting `snappy` 

```java
        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());
        }
```

I am opening this issue to talk about that.
Is it something Netty would be interested in seeing happening ?
I could be doing the PR if needed.


### Netty version

4.1





Hints:
Extend the `HttpContentDecompressor` and override the `newContentDecoder` for Snappy and delegate the rest to `super#newContentDecoder`.
> Extend the `HttpContentDecompressor` and override the `newContentDecoder` for Snappy and delegate the rest to `super#newContentDecoder`.

Thanks for the guidance @hyperxpro, I assume this means Pr's are welcome ? I'll be working on it then ^^
They're! ^^

Patch:
diff --git a/codec-http/src/main/java/io/netty/handler/codec/http/HttpContentDecompressor.java b/codec-http/src/main/java/io/netty/handler/codec/http/HttpContentDecompressor.java
--- a/codec-http/src/main/java/io/netty/handler/codec/http/HttpContentDecompressor.java
+++ b/codec-http/src/main/java/io/netty/handler/codec/http/HttpContentDecompressor.java
@@ -20,12 +20,14 @@
 import static io.netty.handler.codec.http.HttpHeaderValues.GZIP;
 import static io.netty.handler.codec.http.HttpHeaderValues.X_DEFLATE;
 import static io.netty.handler.codec.http.HttpHeaderValues.X_GZIP;
+import static io.netty.handler.codec.http.HttpHeaderValues.SNAPPY;
 
 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.compression.SnappyFrameDecoder;
 
 /**
  * Decompresses an {@link HttpMessage} and an {@link HttpContent} compressed in
@@ -72,6 +74,11 @@ protected EmbeddedChannel newContentDecoder(String contentEncoding) throws Excep
               ctx.channel().config(), new BrotliDecoder());
         }
 
+        if (SNAPPY.contentEqualsIgnoreCase(contentEncoding)) {
+            return new EmbeddedChannel(ctx.channel().id(), ctx.channel().metadata().hasDisconnect(),
+                    ctx.channel().config(), new SnappyFrameDecoder());
+        }
+
         // 'identity' or unsupported
         return null;
     }
diff --git a/codec-http/src/main/java/io/netty/handler/codec/http/HttpHeaderValues.java b/codec-http/src/main/java/io/netty/handler/codec/http/HttpHeaderValues.java
--- a/codec-http/src/main/java/io/netty/handler/codec/http/HttpHeaderValues.java
+++ b/codec-http/src/main/java/io/netty/handler/codec/http/HttpHeaderValues.java
@@ -119,6 +119,12 @@ public final class HttpHeaderValues {
      * {@code "br"}
      */
     public static final AsciiString BR = AsciiString.cached("br");
+
+    /**
+     * {@code "snappy"}
+     */
+    public static final AsciiString SNAPPY = AsciiString.cached("snappy");
+
     /**
      * {@code "zstd"}
      */


Test patch:
diff --git a/codec-http/src/test/java/io/netty/handler/codec/http/HttpContentDecoderTest.java b/codec-http/src/test/java/io/netty/handler/codec/http/HttpContentDecoderTest.java
--- a/codec-http/src/test/java/io/netty/handler/codec/http/HttpContentDecoderTest.java
+++ b/codec-http/src/test/java/io/netty/handler/codec/http/HttpContentDecoderTest.java
@@ -56,6 +56,10 @@ public class HttpContentDecoderTest {
             31, -117, 8, 8, 12, 3, -74, 84, 0, 3, 50, 0, -53, 72, -51, -55, -55,
             -41, 81, 40, -49, 47, -54, 73, 1, 0, 58, 114, -85, -1, 12, 0, 0, 0
     };
+    private static final byte[] SNAPPY_HELLO_WORLD = {
+            -1, 6, 0, 0, 115, 78, 97, 80, 112, 89, 1, 16, 0, 0, 11, -66, -63,
+            -22, 104, 101, 108, 108, 111, 44, 32, 119, 111, 114, 108, 100
+    };
     private static final String SAMPLE_STRING = "Hello, I am Meow!. A small kitten. :)" +
             "I sleep all day, and meow all night.";
     private static final byte[] SAMPLE_BZ_BYTES = new byte[]{27, 72, 0, 0, -60, -102, 91, -86, 103, 20,
@@ -147,7 +151,7 @@ public void testChunkedRequestDecompression() {
     }
 
     @Test
-    public void testResponseDecompression() {
+    public void testSnappyResponseDecompression() {
         // baseline test: response decoder, content decompressor && request aggregator work as expected
         HttpResponseDecoder decoder = new HttpResponseDecoder();
         HttpContentDecoder decompressor = new HttpContentDecompressor();
@@ -155,9 +159,37 @@ public void testResponseDecompression() {
         EmbeddedChannel channel = new EmbeddedChannel(decoder, decompressor, aggregator);
 
         String headers = "HTTP/1.1 200 OK\r\n" +
-                         "Content-Length: " + GZ_HELLO_WORLD.length + "\r\n" +
-                         "Content-Encoding: gzip\r\n" +
+                         "Content-Length: " + SNAPPY_HELLO_WORLD.length + "\r\n" +
+                         "Content-Encoding: snappy\r\n" +
                          "\r\n";
+        ByteBuf buf = Unpooled.copiedBuffer(headers.getBytes(CharsetUtil.UTF_8), SNAPPY_HELLO_WORLD);
+        assertTrue(channel.writeInbound(buf));
+
+        Object o = channel.readInbound();
+        assertThat(o, is(instanceOf(FullHttpResponse.class)));
+        FullHttpResponse resp = (FullHttpResponse) o;
+        assertEquals(HELLO_WORLD.length(), resp.headers().getInt(HttpHeaderNames.CONTENT_LENGTH).intValue());
+        assertEquals(HELLO_WORLD, resp.content().toString(CharsetUtil.UTF_8));
+        resp.release();
+
+        assertHasInboundMessages(channel, false);
+        assertHasOutboundMessages(channel, false);
+        assertFalse(channel.finish()); // assert that no messages are left in channel
+    }
+
+    @Test
+    public void testResponseDecompression() {
+        // baseline test: response decoder, content decompressor && request aggregator work as expected
+        HttpResponseDecoder decoder = new HttpResponseDecoder();
+        HttpContentDecoder decompressor = new HttpContentDecompressor();
+        HttpObjectAggregator aggregator = new HttpObjectAggregator(1024);
+
+        EmbeddedChannel channel = new EmbeddedChannel(decoder, decompressor, aggregator);
+
+        String headers = "HTTP/1.1 200 OK\r\n" +
+                "Content-Length: " + GZ_HELLO_WORLD.length + "\r\n" +
+                "Content-Encoding: gzip\r\n" +
+                "\r\n";
         ByteBuf buf = Unpooled.copiedBuffer(headers.getBytes(CharsetUtil.US_ASCII), GZ_HELLO_WORLD);
         assertTrue(channel.writeInbound(buf));