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
This commit is contained in:
parent
66070686cb
commit
447edd2c4e
|
|
@ -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
|
||||||
|
|
||||||
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
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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 {
|
||||||
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 {
|
||||||
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 {
|
||||||
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 {
|
||||||
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;
|
||||||
|
|
||||||
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 {
|
||||||
|
|
||||||
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 {
|
||||||
content += " scheme=" + request.getScheme();
|
content += " scheme=" + request.getScheme();
|
||||||
content += " remoteaddr=" + request.getRemoteAddr();
|
content += " remoteaddr=" + request.getRemoteAddr();
|
||||||
}
|
}
|
||||||
|
if (this.writeWithoutContentLength) {
|
||||||
|
response.setContentType("text/plain");
|
||||||
|
ServletOutputStream outputStream = response.getOutputStream();
|
||||||
|
StreamUtils.copy(content.getBytes(), outputStream);
|
||||||
|
outputStream.flush();
|
||||||
|
}
|
||||||
|
else {
|
||||||
response.getWriter().write(content);
|
response.getWriter().write(content);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue