Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Bypass deflate content from gzip #1168

Merged
merged 2 commits into from
Jan 5, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -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());
}
Expand Down
22 changes: 11 additions & 11 deletions zuul-core/src/main/java/com/netflix/zuul/util/HttpUtils.java
Original file line number Diff line number Diff line change
Expand Up @@ -15,13 +15,15 @@
*/
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;
import com.netflix.zuul.message.http.HttpHeaderNames;
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;
Expand Down Expand Up @@ -77,24 +79,22 @@ public static String extractClientIpFromXForwardedFor(String xForwardedFor) {
}
}

/**
* return true if the client requested gzip content
*
* @param contentEncoding a <code>String</code> 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());
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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));
artgon marked this conversation as resolved.
Show resolved Hide resolved
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");
Expand Down
36 changes: 33 additions & 3 deletions zuul-core/src/test/java/com/netflix/zuul/util/HttpUtilsTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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
Expand Down