Commit 447edd2c authored by Phillip Webb's avatar Phillip Webb

Allow gzip compression without `Content-Length`

Ensure that gzip compression is applied when the `Content-Length` header
is not specified. Prior to this commit Tomcat and Jetty would compress a
response that didn't contain the header, but Undertow would not.

Fixes gh-4769
parent 66070686
...@@ -36,6 +36,7 @@ import io.undertow.server.handlers.encoding.ContentEncodingRepository; ...@@ -36,6 +36,7 @@ import io.undertow.server.handlers.encoding.ContentEncodingRepository;
import io.undertow.server.handlers.encoding.EncodingHandler; import io.undertow.server.handlers.encoding.EncodingHandler;
import io.undertow.server.handlers.encoding.GzipEncodingProvider; import io.undertow.server.handlers.encoding.GzipEncodingProvider;
import io.undertow.servlet.api.DeploymentManager; import io.undertow.servlet.api.DeploymentManager;
import io.undertow.util.Headers;
import io.undertow.util.HttpString; import io.undertow.util.HttpString;
import org.apache.commons.logging.Log; import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory; import org.apache.commons.logging.LogFactory;
...@@ -161,7 +162,7 @@ public class UndertowEmbeddedServletContainer implements EmbeddedServletContaine ...@@ -161,7 +162,7 @@ public class UndertowEmbeddedServletContainer implements EmbeddedServletContaine
private Predicate[] getCompressionPredicates(Compression compression) { private Predicate[] getCompressionPredicates(Compression compression) {
List<Predicate> predicates = new ArrayList<Predicate>(); List<Predicate> predicates = new ArrayList<Predicate>();
predicates.add(Predicates.maxContentSize(compression.getMinResponseSize())); predicates.add(new MaxSizePredicate(compression.getMinResponseSize()));
predicates.add(new CompressibleMimeTypePredicate(compression.getMimeTypes())); predicates.add(new CompressibleMimeTypePredicate(compression.getMimeTypes()));
if (compression.getExcludedUserAgents() != null) { if (compression.getExcludedUserAgents() != null) {
for (String agent : compression.getExcludedUserAgents()) { for (String agent : compression.getExcludedUserAgents()) {
...@@ -294,4 +295,25 @@ public class UndertowEmbeddedServletContainer implements EmbeddedServletContaine ...@@ -294,4 +295,25 @@ public class UndertowEmbeddedServletContainer implements EmbeddedServletContaine
} }
/**
* Predicate that returns true if the Content-Size of a request is above a given value
* or is missing.
*/
private static class MaxSizePredicate implements Predicate {
private final Predicate maxContentSize;
public MaxSizePredicate(int size) {
this.maxContentSize = Predicates.maxContentSize(size);
}
@Override
public boolean resolve(HttpServerExchange value) {
if (value.getResponseHeaders().contains(Headers.CONTENT_LENGTH)) {
return this.maxContentSize.resolve(value);
}
return true;
}
}
} }
...@@ -360,7 +360,7 @@ public abstract class AbstractEmbeddedServletContainerFactoryTests { ...@@ -360,7 +360,7 @@ public abstract class AbstractEmbeddedServletContainerFactoryTests {
ssl.setEnabled(false); ssl.setEnabled(false);
factory.setSsl(ssl); factory.setSsl(ssl);
this.container = factory.getEmbeddedServletContainer( this.container = factory.getEmbeddedServletContainer(
new ServletRegistrationBean(new ExampleServlet(true), "/hello")); new ServletRegistrationBean(new ExampleServlet(true, false), "/hello"));
this.container.start(); this.container.start();
SSLConnectionSocketFactory socketFactory = new SSLConnectionSocketFactory( SSLConnectionSocketFactory socketFactory = new SSLConnectionSocketFactory(
new SSLContextBuilder() new SSLContextBuilder()
...@@ -378,7 +378,7 @@ public abstract class AbstractEmbeddedServletContainerFactoryTests { ...@@ -378,7 +378,7 @@ public abstract class AbstractEmbeddedServletContainerFactoryTests {
AbstractEmbeddedServletContainerFactory factory = getFactory(); AbstractEmbeddedServletContainerFactory factory = getFactory();
factory.setSsl(getSsl(null, "password", "src/test/resources/test.jks")); factory.setSsl(getSsl(null, "password", "src/test/resources/test.jks"));
this.container = factory.getEmbeddedServletContainer( this.container = factory.getEmbeddedServletContainer(
new ServletRegistrationBean(new ExampleServlet(true), "/hello")); new ServletRegistrationBean(new ExampleServlet(true, false), "/hello"));
this.container.start(); this.container.start();
SSLConnectionSocketFactory socketFactory = new SSLConnectionSocketFactory( SSLConnectionSocketFactory socketFactory = new SSLConnectionSocketFactory(
new SSLContextBuilder() new SSLContextBuilder()
...@@ -658,6 +658,24 @@ public abstract class AbstractEmbeddedServletContainerFactoryTests { ...@@ -658,6 +658,24 @@ public abstract class AbstractEmbeddedServletContainerFactoryTests {
assertFalse(doTestCompression(10000, null, new String[] { "testUserAgent" })); assertFalse(doTestCompression(10000, null, new String[] { "testUserAgent" }));
} }
@Test
public void compressionWithoutContentSizeHeader() throws Exception {
AbstractEmbeddedServletContainerFactory factory = getFactory();
Compression compression = new Compression();
compression.setEnabled(true);
factory.setCompression(compression);
this.container = factory.getEmbeddedServletContainer(
new ServletRegistrationBean(new ExampleServlet(false, true), "/hello"));
this.container.start();
TestGzipInputStreamFactory inputStreamFactory = new TestGzipInputStreamFactory();
Map<String, InputStreamFactory> contentDecoderMap = Collections
.singletonMap("gzip", (InputStreamFactory) inputStreamFactory);
getResponse(getLocalUrl("/hello"),
new HttpComponentsClientHttpRequestFactory(HttpClientBuilder.create()
.setContentDecoderRegistry(contentDecoderMap).build()));
assertThat(inputStreamFactory.wasCompressionUsed(), equalTo(true));
}
@Test @Test
public void mimeMappingsAreCorrectlyConfigured() throws Exception { public void mimeMappingsAreCorrectlyConfigured() throws Exception {
AbstractEmbeddedServletContainerFactory factory = getFactory(); AbstractEmbeddedServletContainerFactory factory = getFactory();
...@@ -824,7 +842,7 @@ public abstract class AbstractEmbeddedServletContainerFactoryTests { ...@@ -824,7 +842,7 @@ public abstract class AbstractEmbeddedServletContainerFactoryTests {
protected void assertForwardHeaderIsUsed(EmbeddedServletContainerFactory factory) protected void assertForwardHeaderIsUsed(EmbeddedServletContainerFactory factory)
throws IOException, URISyntaxException { throws IOException, URISyntaxException {
this.container = factory.getEmbeddedServletContainer( this.container = factory.getEmbeddedServletContainer(
new ServletRegistrationBean(new ExampleServlet(true), "/hello")); new ServletRegistrationBean(new ExampleServlet(true, false), "/hello"));
this.container.start(); this.container.start();
assertThat(getResponse(getLocalUrl("/hello"), "X-Forwarded-For:140.211.11.130"), assertThat(getResponse(getLocalUrl("/hello"), "X-Forwarded-For:140.211.11.130"),
containsString("remoteaddr=140.211.11.130")); containsString("remoteaddr=140.211.11.130"));
......
...@@ -20,9 +20,12 @@ import java.io.IOException; ...@@ -20,9 +20,12 @@ import java.io.IOException;
import javax.servlet.GenericServlet; import javax.servlet.GenericServlet;
import javax.servlet.ServletException; import javax.servlet.ServletException;
import javax.servlet.ServletOutputStream;
import javax.servlet.ServletRequest; import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse; import javax.servlet.ServletResponse;
import org.springframework.util.StreamUtils;
/** /**
* Simple example Servlet used for testing. * Simple example Servlet used for testing.
* *
...@@ -33,12 +36,15 @@ public class ExampleServlet extends GenericServlet { ...@@ -33,12 +36,15 @@ public class ExampleServlet extends GenericServlet {
private final boolean echoRequestInfo; private final boolean echoRequestInfo;
private final boolean writeWithoutContentLength;
public ExampleServlet() { public ExampleServlet() {
this(false); this(false, false);
} }
public ExampleServlet(boolean echoRequestInfo) { public ExampleServlet(boolean echoRequestInfo, boolean writeWithoutContentLength) {
this.echoRequestInfo = echoRequestInfo; this.echoRequestInfo = echoRequestInfo;
this.writeWithoutContentLength = writeWithoutContentLength;
} }
@Override @Override
...@@ -49,7 +55,15 @@ public class ExampleServlet extends GenericServlet { ...@@ -49,7 +55,15 @@ public class ExampleServlet extends GenericServlet {
content += " scheme=" + request.getScheme(); content += " scheme=" + request.getScheme();
content += " remoteaddr=" + request.getRemoteAddr(); content += " remoteaddr=" + request.getRemoteAddr();
} }
response.getWriter().write(content); if (this.writeWithoutContentLength) {
response.setContentType("text/plain");
ServletOutputStream outputStream = response.getOutputStream();
StreamUtils.copy(content.getBytes(), outputStream);
outputStream.flush();
}
else {
response.getWriter().write(content);
}
} }
} }
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment