diff --git a/zuul-core/src/main/java/com/netflix/zuul/filters/common/GZipResponseFilter.java b/zuul-core/src/main/java/com/netflix/zuul/filters/common/GZipResponseFilter.java index 779fed0518..64afe9fd7b 100644 --- a/zuul-core/src/main/java/com/netflix/zuul/filters/common/GZipResponseFilter.java +++ b/zuul-core/src/main/java/com/netflix/zuul/filters/common/GZipResponseFilter.java @@ -78,10 +78,10 @@ public boolean shouldFilter(HttpResponseMessage response) { // Check the headers to see if response is already gzipped. final Headers respHeaders = response.getHeaders(); - boolean isResponseGzipped = HttpUtils.isGzipped(respHeaders); + boolean isResponseCompressed = HttpUtils.isCompressed(respHeaders); // Decide what to do.; - final boolean shouldGzip = isGzippableContentType(response) && isGzipRequested && !isResponseGzipped && isRightSizeForGzip(response); + final boolean shouldGzip = isGzippableContentType(response) && isGzipRequested && !isResponseCompressed && isRightSizeForGzip(response); if (shouldGzip) { response.getContext().set(CommonContextKeys.GZIPPER, getGzipper()); } diff --git a/zuul-core/src/main/java/com/netflix/zuul/util/HttpUtils.java b/zuul-core/src/main/java/com/netflix/zuul/util/HttpUtils.java index 9f7a13b3a1..2a5202ef2b 100644 --- a/zuul-core/src/main/java/com/netflix/zuul/util/HttpUtils.java +++ b/zuul-core/src/main/java/com/netflix/zuul/util/HttpUtils.java @@ -15,6 +15,7 @@ */ package com.netflix.zuul.util; +import com.google.common.annotations.VisibleForTesting; import com.google.common.base.Strings; import com.netflix.zuul.message.Headers; import com.netflix.zuul.message.ZuulMessage; @@ -22,6 +23,7 @@ import com.netflix.zuul.message.http.HttpRequestInfo; import io.netty.channel.Channel; import io.netty.channel.ChannelHandlerContext; +import io.netty.handler.codec.http.HttpHeaderValues; import io.netty.handler.codec.http2.Http2StreamChannel; import javax.annotation.Nullable; import org.slf4j.Logger; @@ -77,24 +79,22 @@ public static String extractClientIpFromXForwardedFor(String xForwardedFor) { } } - /** - * return true if the client requested gzip content - * - * @param contentEncoding a String value - * @return true if the content-encoding param containg gzip - */ - public static boolean isGzipped(String contentEncoding) { - return contentEncoding.contains("gzip"); + @VisibleForTesting + static boolean isCompressed(String contentEncoding) { + return contentEncoding.contains(HttpHeaderValues.GZIP.toString()) || + contentEncoding.contains(HttpHeaderValues.DEFLATE.toString()) || + contentEncoding.contains(HttpHeaderValues.BR.toString()) || + contentEncoding.contains(HttpHeaderValues.COMPRESS.toString()); } - public static boolean isGzipped(Headers headers) { + public static boolean isCompressed(Headers headers) { String ce = headers.getFirst(HttpHeaderNames.CONTENT_ENCODING); - return ce != null && isGzipped(ce); + return ce != null && isCompressed(ce); } public static boolean acceptsGzip(Headers headers) { String ae = headers.getFirst(HttpHeaderNames.ACCEPT_ENCODING); - return ae != null && isGzipped(ae); + return ae != null && ae.contains(HttpHeaderValues.GZIP.toString()); } /** diff --git a/zuul-core/src/test/java/com/netflix/zuul/filters/common/GZipResponseFilterTest.java b/zuul-core/src/test/java/com/netflix/zuul/filters/common/GZipResponseFilterTest.java index 46bdba6c12..49b86c69dd 100644 --- a/zuul-core/src/test/java/com/netflix/zuul/filters/common/GZipResponseFilterTest.java +++ b/zuul-core/src/test/java/com/netflix/zuul/filters/common/GZipResponseFilterTest.java @@ -102,6 +102,67 @@ public void prepareResponseBody_NeedsGZipping() throws Exception { assertEquals(0, result.getHeaders().getAll("Content-Length").size()); } + @Test + public void prepareResponseBody_NeedsGZipping_gzipDeflate() throws Exception { + originalRequestHeaders.set("Accept-Encoding", "gzip,deflate"); + + byte[] originBody = "blah".getBytes(); + response.getHeaders().set("Content-Length", Integer.toString(originBody.length)); + Mockito.when(filter.isRightSizeForGzip(response)).thenReturn(true); //Force GZip for small response + response.setHasBody(true); + assertTrue(filter.shouldFilter(response)); + + final HttpResponseMessage result = filter.apply(response); + final HttpContent hc1 = filter.processContentChunk(response, new DefaultHttpContent(Unpooled.wrappedBuffer(originBody)).retain()); + final HttpContent hc2 = filter.processContentChunk(response, new DefaultLastHttpContent()); + final byte[] body = new byte[hc1.content().readableBytes() + hc2.content().readableBytes()]; + final int hc1Len = hc1.content().readableBytes(); + final int hc2Len = hc2.content().readableBytes(); + hc1.content().readBytes(body, 0, hc1Len); + hc2.content().readBytes(body, hc1Len, hc2Len); + + String bodyStr; + // Check body is a gzipped version of the origin body. + try (ByteArrayInputStream bais = new ByteArrayInputStream(body); + GZIPInputStream gzis = new GZIPInputStream(bais); + ByteArrayOutputStream baos = new ByteArrayOutputStream()) { + int b; + while ((b = gzis.read()) != -1) { + baos.write(b); + } + bodyStr = baos.toString("UTF-8"); + } + assertEquals("blah", bodyStr); + assertEquals("gzip", result.getHeaders().getFirst("Content-Encoding")); + + // Check Content-Length header has been removed + assertEquals(0, result.getHeaders().getAll("Content-Length").size()); + } + + @Test + public void prepareResponseBody_alreadyZipped() throws Exception { + originalRequestHeaders.set("Accept-Encoding", "gzip,deflate"); + + byte[] originBody = "blah".getBytes(); + response.getHeaders().set("Content-Length", Integer.toString(originBody.length)); + response.getHeaders().set("Content-Type", "application/json"); + response.getHeaders().set("Content-Encoding", "gzip"); + response.setHasBody(true); + assertFalse(filter.shouldFilter(response)); + } + + @Test + public void prepareResponseBody_alreadyDeflated() throws Exception { + originalRequestHeaders.set("Accept-Encoding", "gzip,deflate"); + + byte[] originBody = "blah".getBytes(); + response.getHeaders().set("Content-Length", Integer.toString(originBody.length)); + response.getHeaders().set("Content-Type", "application/json"); + response.getHeaders().set("Content-Encoding", "deflate"); + response.setHasBody(true); + assertFalse(filter.shouldFilter(response)); + } + @Test public void prepareResponseBody_NeedsGZipping_butTooSmall() throws Exception { originalRequestHeaders.set("Accept-Encoding", "gzip"); diff --git a/zuul-core/src/test/java/com/netflix/zuul/util/HttpUtilsTest.java b/zuul-core/src/test/java/com/netflix/zuul/util/HttpUtilsTest.java index 791d579021..2df011d3f1 100644 --- a/zuul-core/src/test/java/com/netflix/zuul/util/HttpUtilsTest.java +++ b/zuul-core/src/test/java/com/netflix/zuul/util/HttpUtilsTest.java @@ -21,6 +21,7 @@ import static org.junit.Assert.assertNull; import static org.junit.Assert.assertTrue; +import com.netflix.zuul.message.Headers; import org.junit.Test; import org.junit.runner.RunWith; import org.junit.runners.JUnit4; @@ -33,17 +34,46 @@ public class HttpUtilsTest { @Test public void detectsGzip() { - assertTrue(HttpUtils.isGzipped("gzip")); + assertTrue(HttpUtils.isCompressed("gzip")); + } + + @Test + public void detectsDeflate() { + assertTrue(HttpUtils.isCompressed("deflate")); + } + + @Test + public void detectsCompress() { + assertTrue(HttpUtils.isCompressed("compress")); + } + + @Test + public void detectsBR() { + assertTrue(HttpUtils.isCompressed("br")); } @Test public void detectsNonGzip() { - assertFalse(HttpUtils.isGzipped("identity")); + assertFalse(HttpUtils.isCompressed("identity")); } @Test public void detectsGzipAmongOtherEncodings() { - assertTrue(HttpUtils.isGzipped("gzip, deflate")); + assertTrue(HttpUtils.isCompressed("gzip, deflate")); + } + + @Test + public void acceptsGzip() { + Headers headers = new Headers(); + headers.add("Accept-Encoding", "gzip, deflate"); + assertTrue(HttpUtils.acceptsGzip(headers)); + } + + @Test + public void acceptsGzip_only() { + Headers headers = new Headers(); + headers.add("Accept-Encoding", "deflate"); + assertFalse(HttpUtils.acceptsGzip(headers)); } @Test