From dde194e2b9a57c1269104a0c9a26f975a53a010f Mon Sep 17 00:00:00 2001 From: Ivan Sopov Date: Sat, 20 Jun 2015 17:36:32 +0300 Subject: [PATCH 1/2] Use native compression for all embedded containers, not just Tomcat See gh-3296 --- .../autoconfigure/web/ServerProperties.java | 13 ++- .../web/ServerPropertiesTests.java | 13 +++ spring-boot/pom.xml | 5 ++ ...tConfigurableEmbeddedServletContainer.java | 51 ++++++++++++ .../JettyEmbeddedServletContainerFactory.java | 62 +++++++++++++- ...TomcatEmbeddedServletContainerFactory.java | 13 +++ .../UndertowEmbeddedServletContainer.java | 40 ++++++++- ...dertowEmbeddedServletContainerFactory.java | 4 +- ...tEmbeddedServletContainerFactoryTests.java | 82 +++++++++++++++++++ ...yEmbeddedServletContainerFactoryTests.java | 42 ++++++++++ 10 files changed, 320 insertions(+), 5 deletions(-) diff --git a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/ServerProperties.java b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/ServerProperties.java index cb5abd2057..38a74dc08b 100644 --- a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/ServerProperties.java +++ b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/ServerProperties.java @@ -31,6 +31,8 @@ import org.apache.catalina.valves.RemoteIpValve; import org.apache.coyote.AbstractProtocol; import org.apache.coyote.ProtocolHandler; import org.apache.coyote.http11.AbstractHttp11Protocol; +import org.springframework.boot.context.embedded.AbstractConfigurableEmbeddedServletContainer; +import org.springframework.boot.context.embedded.AbstractConfigurableEmbeddedServletContainer.CompressionProperties; import org.springframework.boot.context.embedded.ConfigurableEmbeddedServletContainer; import org.springframework.boot.context.embedded.EmbeddedServletContainerCustomizer; import org.springframework.boot.context.embedded.EmbeddedServletContainerCustomizerBeanPostProcessor; @@ -99,6 +101,8 @@ public class ServerProperties implements EmbeddedServletContainerCustomizer, Ord private final Undertow undertow = new Undertow(); + private CompressionProperties compression = new CompressionProperties(); + @NestedConfigurationProperty private JspServlet jspServlet; @@ -120,6 +124,10 @@ public class ServerProperties implements EmbeddedServletContainerCustomizer, Ord return this.undertow; } + public CompressionProperties getCompression() { + return this.compression; + } + public String getContextPath() { return this.contextPath; } @@ -239,6 +247,10 @@ public class ServerProperties implements EmbeddedServletContainerCustomizer, Ord if (getJspServlet() != null) { container.setJspServlet(getJspServlet()); } + if (container instanceof AbstractConfigurableEmbeddedServletContainer) { + ((AbstractConfigurableEmbeddedServletContainer) container) + .setCompression(getCompression()); + } if (container instanceof TomcatEmbeddedServletContainerFactory) { getTomcat() .customizeTomcat((TomcatEmbeddedServletContainerFactory) container); @@ -587,7 +599,6 @@ public class ServerProperties implements EmbeddedServletContainerCustomizer, Ord valve.setSuffix(".log"); factory.addContextValves(valve); } - } public static class Undertow { diff --git a/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/web/ServerPropertiesTests.java b/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/web/ServerPropertiesTests.java index e0a0023132..962eb74f52 100644 --- a/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/web/ServerPropertiesTests.java +++ b/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/web/ServerPropertiesTests.java @@ -37,6 +37,7 @@ import static org.hamcrest.core.IsInstanceOf.instanceOf; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertThat; +import static org.junit.Assert.assertTrue; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.never; import static org.mockito.Mockito.verify; @@ -105,6 +106,18 @@ public class ServerPropertiesTests { .getInternalProxies()); } + @Test + public void testCompressionBinding() throws Exception { + Map map = new HashMap(); + map.put("server.compression.enabled", "true"); + map.put("server.compression.mimeTypes", "foo/bar"); + map.put("server.compression.minSize", "228"); + bindProperties(map); + assertTrue(this.properties.getCompression().isEnabled()); + assertEquals("foo/bar", this.properties.getCompression().getMimeTypes()); + assertEquals(228, this.properties.getCompression().getMinSize()); + } + @Test public void testCustomizeTomcat() throws Exception { ConfigurableEmbeddedServletContainer factory = mock(ConfigurableEmbeddedServletContainer.class); diff --git a/spring-boot/pom.xml b/spring-boot/pom.xml index 2780f4f475..08311a71e9 100644 --- a/spring-boot/pom.xml +++ b/spring-boot/pom.xml @@ -154,6 +154,11 @@ jetty-util true + + org.eclipse.jetty + jetty-servlets + true + io.undertow undertow-servlet diff --git a/spring-boot/src/main/java/org/springframework/boot/context/embedded/AbstractConfigurableEmbeddedServletContainer.java b/spring-boot/src/main/java/org/springframework/boot/context/embedded/AbstractConfigurableEmbeddedServletContainer.java index dfe985fc71..db8b75893b 100644 --- a/spring-boot/src/main/java/org/springframework/boot/context/embedded/AbstractConfigurableEmbeddedServletContainer.java +++ b/spring-boot/src/main/java/org/springframework/boot/context/embedded/AbstractConfigurableEmbeddedServletContainer.java @@ -23,6 +23,7 @@ import java.util.Arrays; import java.util.LinkedHashSet; import java.util.List; import java.util.Set; +import java.util.StringTokenizer; import java.util.concurrent.TimeUnit; import org.springframework.util.Assert; @@ -67,6 +68,8 @@ public abstract class AbstractConfigurableEmbeddedServletContainer implements private JspServlet jspServlet = new JspServlet(); + private CompressionProperties compression; + /** * Create a new {@link AbstractConfigurableEmbeddedServletContainer} instance. */ @@ -278,6 +281,14 @@ public abstract class AbstractConfigurableEmbeddedServletContainer implements return this.jspServlet; } + public CompressionProperties getCompression() { + return this.compression; + } + + public void setCompression(CompressionProperties compression) { + this.compression = compression; + } + /** * Utility method that can be used by subclasses wishing to combine the specified * {@link ServletContextInitializer} parameters with those defined in this instance. @@ -306,4 +317,44 @@ public abstract class AbstractConfigurableEmbeddedServletContainer implements .getClassLoader()); } + public static class CompressionProperties { + private boolean enabled = false; + private String mimeTypes = "text/html,text/xml,text/plain,text/css"; + private int minSize = 2048; + + public boolean isEnabled() { + return enabled; + } + + public void setEnabled(boolean enabled) { + this.enabled = enabled; + } + + public String getMimeTypes() { + return mimeTypes; + } + + public void setMimeTypes(String mimeTypes) { + this.mimeTypes = mimeTypes; + } + + public int getMinSize() { + return minSize; + } + + public void setMinSize(int minSize) { + this.minSize = minSize; + } + + public List getMimeTypesList() { + List mimeTypesList = new ArrayList(); + StringTokenizer tok = new StringTokenizer(mimeTypes, ",", false); + while (tok.hasMoreTokens()) { + mimeTypesList.add(tok.nextToken()); + } + return mimeTypesList; + } + + } + } diff --git a/spring-boot/src/main/java/org/springframework/boot/context/embedded/jetty/JettyEmbeddedServletContainerFactory.java b/spring-boot/src/main/java/org/springframework/boot/context/embedded/jetty/JettyEmbeddedServletContainerFactory.java index a7bf274f0a..c40655a331 100644 --- a/spring-boot/src/main/java/org/springframework/boot/context/embedded/jetty/JettyEmbeddedServletContainerFactory.java +++ b/spring-boot/src/main/java/org/springframework/boot/context/embedded/jetty/JettyEmbeddedServletContainerFactory.java @@ -18,6 +18,7 @@ package org.springframework.boot.context.embedded.jetty; import java.io.File; import java.io.IOException; +import java.lang.reflect.Method; import java.net.InetSocketAddress; import java.net.URL; import java.util.ArrayList; @@ -37,6 +38,7 @@ import org.eclipse.jetty.server.ServerConnector; import org.eclipse.jetty.server.SessionManager; import org.eclipse.jetty.server.SslConnectionFactory; import org.eclipse.jetty.server.handler.ErrorHandler; +import org.eclipse.jetty.server.handler.HandlerWrapper; import org.eclipse.jetty.servlet.ErrorPageErrorHandler; import org.eclipse.jetty.servlet.ServletHolder; import org.eclipse.jetty.servlet.ServletMapping; @@ -59,6 +61,7 @@ import org.springframework.context.ResourceLoaderAware; import org.springframework.core.io.ResourceLoader; import org.springframework.util.Assert; import org.springframework.util.ClassUtils; +import org.springframework.util.ReflectionUtils; import org.springframework.util.ResourceUtils; import org.springframework.util.StringUtils; @@ -113,6 +116,12 @@ public class JettyEmbeddedServletContainerFactory extends super(contextPath, port); } + private static final String[] GZIP_HANDLER_CLASSNAMES = new String[] { + "org.eclipse.jetty.servlets.gzip.GzipHandler", // Jetty 9.2 + "org.eclipse.jetty.server.handler.gzip.GzipHandler", // Jetty 9.3 + "org.eclipse.jetty.server.handler.GzipHandler" // Jetty 8 + }; + @Override public EmbeddedServletContainer getEmbeddedServletContainer( ServletContextInitializer... initializers) { @@ -120,7 +129,12 @@ public class JettyEmbeddedServletContainerFactory extends int port = (getPort() >= 0 ? getPort() : 0); Server server = new Server(new InetSocketAddress(getAddress(), port)); configureWebAppContext(context, initializers); - server.setHandler(context); + if (getCompression() != null && getCompression().isEnabled()) { + setupGzipHandler(context, server); + } + else { + server.setHandler(context); + } this.logger.info("Server initialized with port: " + port); if (getSsl() != null && getSsl().isEnabled()) { SslContextFactory sslContextFactory = new SslContextFactory(); @@ -135,6 +149,52 @@ public class JettyEmbeddedServletContainerFactory extends return getJettyEmbeddedServletContainer(server); } + private void setupGzipHandler(JettyEmbeddedWebAppContext context, Server server) + throws LinkageError { + boolean done = false; + for (String gzipHandlerClassName : GZIP_HANDLER_CLASSNAMES) { + if (ClassUtils.isPresent(gzipHandlerClassName, null)) { + try { + Class gzipHandlerClass = ClassUtils.forName(gzipHandlerClassName, + null); + + HandlerWrapper gzipHandler = (HandlerWrapper) gzipHandlerClass + .newInstance(); + gzipHandler.setHandler(context); + + Method minGzipSizeMethod = ReflectionUtils.findMethod( + gzipHandlerClass, "setMinGzipSize", int.class); + minGzipSizeMethod.invoke(gzipHandler, getCompression().getMinSize()); + + Method mimeTypesMethod = ReflectionUtils.findMethod(gzipHandlerClass, + "setMimeTypes", String.class); + if (mimeTypesMethod != null) { + // Jetty 8 & Jety 9.2 + mimeTypesMethod.invoke(gzipHandler, getCompression() + .getMimeTypes()); + } + else { + // Jetty 9.3 + mimeTypesMethod = ReflectionUtils.findMethod(gzipHandlerClass, + "setIncludedMimeTypes", String[].class); + List mimeTypes = getCompression().getMimeTypesList(); + mimeTypesMethod.invoke(gzipHandler, + (Object) mimeTypes.toArray(new String[mimeTypes.size()])); + } + server.setHandler(gzipHandler); + done = true; + break; + } + catch (Exception e) { + throw new RuntimeException(e); + } + } + } + if (!done) { + throw new IllegalStateException("Jetty GzipHandler is not in classpath"); + } + } + private SslServerConnectorFactory getSslServerConnectorFactory() { if (ClassUtils.isPresent("org.eclipse.jetty.server.ssl.SslSocketConnector", null)) { return new Jetty8SslServerConnectorFactory(); diff --git a/spring-boot/src/main/java/org/springframework/boot/context/embedded/tomcat/TomcatEmbeddedServletContainerFactory.java b/spring-boot/src/main/java/org/springframework/boot/context/embedded/tomcat/TomcatEmbeddedServletContainerFactory.java index 96d9d1f750..9045648a15 100644 --- a/spring-boot/src/main/java/org/springframework/boot/context/embedded/tomcat/TomcatEmbeddedServletContainerFactory.java +++ b/spring-boot/src/main/java/org/springframework/boot/context/embedded/tomcat/TomcatEmbeddedServletContainerFactory.java @@ -46,7 +46,9 @@ import org.apache.catalina.loader.WebappLoader; import org.apache.catalina.startup.Tomcat; import org.apache.catalina.startup.Tomcat.FixContextListener; import org.apache.coyote.AbstractProtocol; +import org.apache.coyote.ProtocolHandler; import org.apache.coyote.http11.AbstractHttp11JsseProtocol; +import org.apache.coyote.http11.AbstractHttp11Protocol; import org.springframework.beans.BeanUtils; import org.springframework.boot.context.embedded.AbstractEmbeddedServletContainerFactory; import org.springframework.boot.context.embedded.EmbeddedServletContainer; @@ -255,6 +257,17 @@ public class TomcatEmbeddedServletContainerFactory extends connector.setSecure(true); } + if (getCompression() != null && getCompression().isEnabled()) { + ProtocolHandler handler = connector.getProtocolHandler(); + if (handler instanceof AbstractHttp11Protocol) { + @SuppressWarnings("rawtypes") + AbstractHttp11Protocol protocol = (AbstractHttp11Protocol) handler; + protocol.setCompression("on"); + protocol.setCompressionMinSize(getCompression().getMinSize()); + protocol.setCompressableMimeTypes(getCompression().getMimeTypes()); + } + } + for (TomcatConnectorCustomizer customizer : this.tomcatConnectorCustomizers) { customizer.customize(connector); } diff --git a/spring-boot/src/main/java/org/springframework/boot/context/embedded/undertow/UndertowEmbeddedServletContainer.java b/spring-boot/src/main/java/org/springframework/boot/context/embedded/undertow/UndertowEmbeddedServletContainer.java index fd2c8928e3..82dfe4583a 100644 --- a/spring-boot/src/main/java/org/springframework/boot/context/embedded/undertow/UndertowEmbeddedServletContainer.java +++ b/spring-boot/src/main/java/org/springframework/boot/context/embedded/undertow/UndertowEmbeddedServletContainer.java @@ -19,8 +19,17 @@ package org.springframework.boot.context.embedded.undertow; import io.undertow.Handlers; import io.undertow.Undertow; import io.undertow.Undertow.Builder; +import io.undertow.attribute.ConstantExchangeAttribute; +import io.undertow.attribute.ExchangeAttribute; +import io.undertow.attribute.ResponseHeaderAttribute; +import io.undertow.predicate.Predicate; +import io.undertow.predicate.Predicates; import io.undertow.server.HttpHandler; +import io.undertow.server.handlers.encoding.ContentEncodingRepository; +import io.undertow.server.handlers.encoding.EncodingHandler; +import io.undertow.server.handlers.encoding.GzipEncodingProvider; import io.undertow.servlet.api.DeploymentManager; +import io.undertow.util.HttpString; import java.lang.reflect.Field; import java.net.ServerSocket; @@ -31,8 +40,10 @@ import javax.servlet.ServletException; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; +import org.springframework.boot.context.embedded.AbstractConfigurableEmbeddedServletContainer.CompressionProperties; import org.springframework.boot.context.embedded.EmbeddedServletContainer; import org.springframework.boot.context.embedded.EmbeddedServletContainerException; +import org.springframework.http.HttpHeaders; import org.springframework.util.ReflectionUtils; import org.springframework.util.StringUtils; @@ -59,16 +70,20 @@ public class UndertowEmbeddedServletContainer implements EmbeddedServletContaine private final boolean autoStart; + private final CompressionProperties compression; + private Undertow undertow; private boolean started = false; public UndertowEmbeddedServletContainer(Builder builder, DeploymentManager manager, - String contextPath, int port, boolean autoStart) { + String contextPath, int port, boolean autoStart, + CompressionProperties compression) { this.builder = builder; this.manager = manager; this.contextPath = contextPath; this.autoStart = autoStart; + this.compression = compression; } @Override @@ -98,6 +113,29 @@ public class UndertowEmbeddedServletContainer implements EmbeddedServletContaine } private HttpHandler getContextHandler(HttpHandler servletHandler) { + if (compression != null && compression.isEnabled()) { + ContentEncodingRepository encodingRepository = new ContentEncodingRepository(); + + List mimeTypes = compression.getMimeTypesList(); + Predicate[] mimePredicates = new Predicate[mimeTypes.size()]; + ResponseHeaderAttribute mimeHeader = new ResponseHeaderAttribute( + new HttpString(HttpHeaders.CONTENT_TYPE)); + for (int i = 0; i < mimeTypes.size(); i++) { + mimePredicates[i] = Predicates.equals(new ExchangeAttribute[] { + mimeHeader, new ConstantExchangeAttribute(mimeTypes.get(i)) }); + + } + + Predicate mimeAndSizePredicate = Predicates.and( + Predicates.maxContentSize(compression.getMinSize()), + Predicates.or(mimePredicates)); + + encodingRepository.addEncodingHandler("gzip", new GzipEncodingProvider(), 50, + mimeAndSizePredicate); + servletHandler = new EncodingHandler(encodingRepository) + .setNext(servletHandler); + } + if (StringUtils.isEmpty(this.contextPath)) { return servletHandler; } diff --git a/spring-boot/src/main/java/org/springframework/boot/context/embedded/undertow/UndertowEmbeddedServletContainerFactory.java b/spring-boot/src/main/java/org/springframework/boot/context/embedded/undertow/UndertowEmbeddedServletContainerFactory.java index 2fb0abde8d..6fef6b9ba8 100644 --- a/spring-boot/src/main/java/org/springframework/boot/context/embedded/undertow/UndertowEmbeddedServletContainerFactory.java +++ b/spring-boot/src/main/java/org/springframework/boot/context/embedded/undertow/UndertowEmbeddedServletContainerFactory.java @@ -219,7 +219,7 @@ public class UndertowEmbeddedServletContainerFactory extends int port = getPort(); Builder builder = createBuilder(port); return new UndertowEmbeddedServletContainer(builder, manager, getContextPath(), - port, port >= 0); + port, port >= 0, getCompression()); } private Builder createBuilder(int port) { @@ -470,7 +470,7 @@ public class UndertowEmbeddedServletContainerFactory extends protected UndertowEmbeddedServletContainer getUndertowEmbeddedServletContainer( Builder builder, DeploymentManager manager, int port) { return new UndertowEmbeddedServletContainer(builder, manager, getContextPath(), - port, port >= 0); + port, port >= 0, getCompression()); } @Override diff --git a/spring-boot/src/test/java/org/springframework/boot/context/embedded/AbstractEmbeddedServletContainerFactoryTests.java b/spring-boot/src/test/java/org/springframework/boot/context/embedded/AbstractEmbeddedServletContainerFactoryTests.java index 30a3be1bee..9ff051ace4 100644 --- a/spring-boot/src/test/java/org/springframework/boot/context/embedded/AbstractEmbeddedServletContainerFactoryTests.java +++ b/spring-boot/src/test/java/org/springframework/boot/context/embedded/AbstractEmbeddedServletContainerFactoryTests.java @@ -20,6 +20,7 @@ import java.io.File; import java.io.FileInputStream; import java.io.FileWriter; import java.io.IOException; +import java.io.InputStream; import java.net.URI; import java.net.URISyntaxException; import java.nio.charset.Charset; @@ -27,6 +28,8 @@ import java.security.KeyStore; import java.util.Arrays; import java.util.Date; import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.zip.GZIPInputStream; import javax.net.ssl.SSLException; import javax.servlet.GenericServlet; @@ -36,8 +39,10 @@ import javax.servlet.ServletRequest; import javax.servlet.ServletResponse; import org.apache.http.client.HttpClient; +import org.apache.http.client.entity.InputStreamFactory; import org.apache.http.conn.ssl.SSLConnectionSocketFactory; import org.apache.http.conn.ssl.TrustSelfSignedStrategy; +import org.apache.http.impl.client.HttpClientBuilder; import org.apache.http.impl.client.HttpClients; import org.apache.http.ssl.SSLContextBuilder; import org.junit.After; @@ -46,6 +51,7 @@ import org.junit.Test; import org.junit.rules.ExpectedException; import org.junit.rules.TemporaryFolder; import org.mockito.InOrder; +import org.springframework.boot.context.embedded.AbstractConfigurableEmbeddedServletContainer.CompressionProperties; import org.springframework.boot.context.embedded.Ssl.ClientAuth; import org.springframework.http.HttpMethod; import org.springframework.http.HttpStatus; @@ -58,6 +64,7 @@ import org.springframework.util.SocketUtils; import org.springframework.util.StreamUtils; import org.springframework.util.concurrent.ListenableFuture; +import static java.util.Collections.singletonMap; import static org.hamcrest.Matchers.containsString; import static org.hamcrest.Matchers.equalTo; import static org.hamcrest.Matchers.is; @@ -65,7 +72,9 @@ import static org.hamcrest.Matchers.lessThan; import static org.hamcrest.Matchers.notNullValue; import static org.hamcrest.Matchers.nullValue; import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertThat; +import static org.junit.Assert.assertTrue; import static org.mockito.Matchers.anyObject; import static org.mockito.Mockito.inOrder; import static org.mockito.Mockito.mock; @@ -520,6 +529,79 @@ public abstract class AbstractEmbeddedServletContainerFactoryTests { assertThat(getFactory().getSessionTimeout(), equalTo(30 * 60)); } + @Test + public void compression() throws Exception { + assertTrue(internalTestCompression(10000, null)); + } + + @Test + public void noCompressionForSmallResponse() throws Exception { + assertFalse(internalTestCompression(100, null)); + } + + @Test + public void noCompressionForMimeType() throws Exception { + assertFalse(internalTestCompression(10000, "text/html,text/xml,text/css")); + } + + protected String setupFactoryForCompression(int contentSize, String mimeTypes) + throws Exception { + char[] chars = new char[contentSize]; + Arrays.fill(chars, 'F'); + String testContent = new String(chars); + + AbstractEmbeddedServletContainerFactory factory = getFactory(); + + FileCopyUtils.copy(testContent, + new FileWriter(this.temporaryFolder.newFile("test.txt"))); + factory.setDocumentRoot(this.temporaryFolder.getRoot()); + CompressionProperties compression = new CompressionProperties(); + compression.setEnabled(true); + if (mimeTypes != null) { + compression.setMimeTypes(mimeTypes); + } + factory.setCompression(compression); + + this.container = factory.getEmbeddedServletContainer(); + this.container.start(); + return testContent; + } + + private boolean internalTestCompression(int contentSize, String mimeTypes) + throws Exception { + String testContent = setupFactoryForCompression(contentSize, mimeTypes); + + class TestGzipInputStreamFactory implements InputStreamFactory { + + final AtomicBoolean requested = new AtomicBoolean(false); + + @Override + public InputStream create(InputStream instream) throws IOException { + if (requested.get()) { + throw new IllegalStateException( + "On deflated InputStream already requested"); + } + requested.set(true); + return new GZIPInputStream(instream); + } + + } + + TestGzipInputStreamFactory gzipTestInputStreamFactory = new TestGzipInputStreamFactory(); + + String response = getResponse( + getLocalUrl("/test.txt"), + new HttpComponentsClientHttpRequestFactory(HttpClientBuilder + .create() + .setContentDecoderRegistry( + singletonMap("gzip", + (InputStreamFactory) gzipTestInputStreamFactory)) + .build())); + assertThat(response, equalTo(testContent)); + boolean wasCompressionUsed = gzipTestInputStreamFactory.requested.get(); + return wasCompressionUsed; + } + private void addTestTxtFile(AbstractEmbeddedServletContainerFactory factory) throws IOException { FileCopyUtils.copy("test", diff --git a/spring-boot/src/test/java/org/springframework/boot/context/embedded/jetty/JettyEmbeddedServletContainerFactoryTests.java b/spring-boot/src/test/java/org/springframework/boot/context/embedded/jetty/JettyEmbeddedServletContainerFactoryTests.java index cffab998ba..270dc0a22b 100644 --- a/spring-boot/src/test/java/org/springframework/boot/context/embedded/jetty/JettyEmbeddedServletContainerFactoryTests.java +++ b/spring-boot/src/test/java/org/springframework/boot/context/embedded/jetty/JettyEmbeddedServletContainerFactoryTests.java @@ -16,11 +16,17 @@ package org.springframework.boot.context.embedded.jetty; +import java.io.IOException; import java.util.Arrays; import java.util.HashMap; import java.util.Map; import java.util.concurrent.TimeUnit; +import javax.servlet.ServletException; +import javax.servlet.http.HttpServlet; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + import org.eclipse.jetty.server.Handler; import org.eclipse.jetty.server.Server; import org.eclipse.jetty.server.ServerConnector; @@ -32,8 +38,12 @@ import org.eclipse.jetty.webapp.Configuration; import org.eclipse.jetty.webapp.WebAppContext; import org.junit.Test; import org.mockito.InOrder; +import org.springframework.boot.context.embedded.AbstractEmbeddedServletContainerFactory; import org.springframework.boot.context.embedded.AbstractEmbeddedServletContainerFactoryTests; +import org.springframework.boot.context.embedded.ServletRegistrationBean; import org.springframework.boot.context.embedded.Ssl; +import org.springframework.boot.context.embedded.AbstractConfigurableEmbeddedServletContainer.CompressionProperties; +import org.springframework.http.HttpHeaders; import static org.hamcrest.Matchers.equalTo; import static org.hamcrest.Matchers.is; @@ -173,6 +183,38 @@ public class JettyEmbeddedServletContainerFactoryTests extends assertThat(getJspServlet().getInitParameters(), is(equalTo(initParameters))); } + @Override + @SuppressWarnings("serial") + // work-around for Jetty issue - https://bugs.eclipse.org/bugs/show_bug.cgi?id=470646 + protected String setupFactoryForCompression(final int contentSize, String mimeTypes) + throws Exception { + char[] chars = new char[contentSize]; + Arrays.fill(chars, 'F'); + final String testContent = new String(chars); + + AbstractEmbeddedServletContainerFactory factory = getFactory(); + + CompressionProperties compression = new CompressionProperties(); + compression.setEnabled(true); + if (mimeTypes != null) { + compression.setMimeTypes(mimeTypes); + } + factory.setCompression(compression); + + this.container = factory.getEmbeddedServletContainer(new ServletRegistrationBean( + new HttpServlet() { + @Override + protected void doGet(HttpServletRequest req, HttpServletResponse resp) + throws ServletException, IOException { + resp.setContentLength(contentSize); + resp.setHeader(HttpHeaders.CONTENT_TYPE, "text/plain"); + resp.getWriter().print(testContent); + } + }, "/test.txt")); + this.container.start(); + return testContent; + } + @Override protected ServletHolder getJspServlet() { WebAppContext context = (WebAppContext) ((JettyEmbeddedServletContainer) this.container) From 00d594dcda6edbd7086b888e60bd0aa6562ad2e7 Mon Sep 17 00:00:00 2001 From: Andy Wilkinson Date: Mon, 29 Jun 2015 14:50:48 +0100 Subject: [PATCH 2/2] Replace GzipFilter and Tomcat compression with general purpose approach Closes gh-3296 --- spring-boot-autoconfigure/pom.xml | 5 - .../web/GzipFilterAutoConfiguration.java | 53 ---- .../web/GzipFilterProperties.java | 242 ------------------ .../autoconfigure/web/ServerProperties.java | 70 +---- .../main/resources/META-INF/spring.factories | 1 - .../web/GzipFilterAutoConfigurationTests.java | 123 --------- .../web/ServerPropertiesTests.java | 80 ------ .../appendix-application-properties.adoc | 20 +- spring-boot-docs/src/main/asciidoc/howto.adoc | 52 +--- .../boot/gradle/WarPackagingTests.java | 7 +- .../src/main/resources/application.properties | 2 + .../jetty/SampleJettyApplicationTests.java | 34 +++ .../src/main/resources/application.properties | 2 + .../jetty8/SampleJetty8ApplicationTests.java | 35 ++- .../src/main/resources/application.properties | 2 + .../tomcat/SampleTomcatApplicationTests.java | 34 +++ .../src/main/resources/application.properties | 4 +- .../SampleUndertowApplicationTests.java | 33 +++ .../spring-boot-starter-jetty/pom.xml | 4 + ...tConfigurableEmbeddedServletContainer.java | 48 +--- .../boot/context/embedded/Compression.java | 68 +++++ .../ConfigurableEmbeddedServletContainer.java | 7 + .../JettyEmbeddedServletContainerFactory.java | 145 +++++++---- ...TomcatEmbeddedServletContainerFactory.java | 7 +- .../UndertowEmbeddedServletContainer.java | 83 +++--- ...tEmbeddedServletContainerFactoryTests.java | 16 +- ...yEmbeddedServletContainerFactoryTests.java | 8 +- 27 files changed, 414 insertions(+), 771 deletions(-) delete mode 100644 spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/GzipFilterAutoConfiguration.java delete mode 100644 spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/GzipFilterProperties.java delete mode 100644 spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/web/GzipFilterAutoConfigurationTests.java create mode 100644 spring-boot-samples/spring-boot-sample-jetty/src/main/resources/application.properties create mode 100644 spring-boot-samples/spring-boot-sample-jetty8/src/main/resources/application.properties create mode 100644 spring-boot-samples/spring-boot-sample-tomcat/src/main/resources/application.properties create mode 100644 spring-boot/src/main/java/org/springframework/boot/context/embedded/Compression.java diff --git a/spring-boot-autoconfigure/pom.xml b/spring-boot-autoconfigure/pom.xml index 4ff4029fc0..cd63dfc249 100644 --- a/spring-boot-autoconfigure/pom.xml +++ b/spring-boot-autoconfigure/pom.xml @@ -200,11 +200,6 @@ HikariCP-java6 true - - org.eclipse.jetty - jetty-servlets - true - org.eclipse.jetty jetty-webapp diff --git a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/GzipFilterAutoConfiguration.java b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/GzipFilterAutoConfiguration.java deleted file mode 100644 index e50fe1ec78..0000000000 --- a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/GzipFilterAutoConfiguration.java +++ /dev/null @@ -1,53 +0,0 @@ -/* - * Copyright 2012-2015 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.boot.autoconfigure.web; - -import org.eclipse.jetty.servlets.GzipFilter; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.boot.autoconfigure.EnableAutoConfiguration; -import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; -import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; -import org.springframework.boot.context.embedded.FilterRegistrationBean; -import org.springframework.boot.context.properties.EnableConfigurationProperties; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; - -/** - * {@link EnableAutoConfiguration Auto-configuration} for {@link GzipFilter}. - * - * @author Andy Wilkinson - * @since 1.2.2 - */ -@Configuration -@ConditionalOnClass(GzipFilter.class) -@EnableConfigurationProperties(GzipFilterProperties.class) -public class GzipFilterAutoConfiguration { - - @Autowired - private GzipFilterProperties properties; - - @Bean - @ConditionalOnProperty(prefix = "spring.http.gzip", name = "enabled", matchIfMissing = true) - public FilterRegistrationBean gzipFilter() { - FilterRegistrationBean registration = new FilterRegistrationBean(new GzipFilter()); - registration.addUrlPatterns("/*"); - registration.setInitParameters(this.properties.getAsInitParameters()); - - return registration; - } - -} diff --git a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/GzipFilterProperties.java b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/GzipFilterProperties.java deleted file mode 100644 index a180db2707..0000000000 --- a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/GzipFilterProperties.java +++ /dev/null @@ -1,242 +0,0 @@ -/* - * Copyright 2012-2015 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.boot.autoconfigure.web; - -import java.util.HashMap; -import java.util.List; -import java.util.Map; - -import org.eclipse.jetty.servlets.GzipFilter; -import org.springframework.boot.context.properties.ConfigurationProperties; -import org.springframework.http.HttpMethod; -import org.springframework.util.MimeType; -import org.springframework.util.StringUtils; - -/** - * Properties for configuring {@link GzipFilter}. - * - * @author Andy Wilkinson - * @author Stephane Nicoll - * @since 1.2.2 - */ -@ConfigurationProperties(prefix = "spring.http.gzip") -public class GzipFilterProperties { - - private final Map initParameters = new HashMap(); - - /** - * Size of the output buffer in bytes. - */ - private Integer bufferSize; - - /** - * Minimum content length required for compression to occur. - */ - private Integer minGzipSize; - - /** - * Level used for deflate compression (0-9). - */ - private Integer deflateCompressionLevel; - - /** - * noWrap setting for deflate compression. - */ - private Boolean deflateNoWrap; - - /** - * Comma-separated list of HTTP methods for which compression is enabled. - */ - private List methods; - - /** - * Comma-separated list of MIME types which should be compressed. - */ - private List mimeTypes; - - /** - * Comma-separated list of MIME types to exclude from compression. - */ - private List excludedMimeTypes; - - /** - * Comma-separated list of user agents to exclude from compression. String.contains is - * used to determine a match against the request's User-Agent header. - */ - private String excludedAgents; - - /** - * Comma-separated list of regular expression patterns to control user agents excluded - * from compression. - */ - private String excludeAgentPatterns; - - /** - * Comma-separated list of paths to exclude from compression. Uses String.startsWith - * to determine a match against the request's path. - */ - private String excludePaths; - - /** - * Comma-separated list of regular expression patterns to control the paths that are - * excluded from compression. - */ - private String excludePathPatterns; - - /** - * Vary header sent on responses that may be compressed. - */ - private String vary; - - public GzipFilterProperties() { - this.addInitParameter("checkGzExists", false); - } - - public Integer getBufferSize() { - return this.bufferSize; - } - - public void setBufferSize(Integer bufferSize) { - this.addInitParameter("bufferSize", bufferSize); - this.bufferSize = bufferSize; - } - - public Integer getMinGzipSize() { - return this.minGzipSize; - } - - public void setMinGzipSize(Integer minGzipSize) { - this.addInitParameter("minGzipSize", minGzipSize); - this.minGzipSize = minGzipSize; - } - - public Integer getDeflateCompressionLevel() { - return this.deflateCompressionLevel; - } - - public void setDeflateCompressionLevel(Integer deflateCompressionLevel) { - this.addInitParameter("deflateCompressionLevel", deflateCompressionLevel); - this.deflateCompressionLevel = deflateCompressionLevel; - } - - public Boolean getDeflateNoWrap() { - return this.deflateNoWrap; - } - - public void setDeflateNoWrap(Boolean deflateNoWrap) { - this.addInitParameter("deflateNoWrap", deflateNoWrap); - this.deflateNoWrap = deflateNoWrap; - } - - public List getMethods() { - return this.methods; - } - - public void setMethods(List methods) { - this.addInitParameter("methods", - StringUtils.collectionToCommaDelimitedString(methods)); - this.methods = methods; - } - - public List getMimeTypes() { - return this.mimeTypes; - } - - public void setMimeTypes(List mimeTypes) { - this.addInitParameter("mimeTypes", - StringUtils.collectionToCommaDelimitedString(mimeTypes)); - this.mimeTypes = mimeTypes; - } - - public List getExcludedMimeTypes() { - return this.excludedMimeTypes; - } - - public void setExcludedMimeTypes(List excludedMimeTypes) { - this.addInitParameter("excludedMimeTypes", - StringUtils.collectionToCommaDelimitedString(excludedMimeTypes)); - this.excludedMimeTypes = excludedMimeTypes; - } - - public String getExcludedAgents() { - return this.excludedAgents; - } - - public void setExcludedAgents(String excludedAgents) { - this.addInitParameter("excludedAgents", excludedAgents); - this.excludedAgents = excludedAgents; - } - - public String getExcludeAgentPatterns() { - return this.excludeAgentPatterns; - } - - public void setExcludeAgentPatterns(String excludeAgentPatterns) { - this.addInitParameter("excludeAgentPatterns", excludeAgentPatterns); - this.excludeAgentPatterns = excludeAgentPatterns; - } - - public String getExcludePaths() { - return this.excludePaths; - } - - public void setExcludePaths(String excludePaths) { - this.addInitParameter("excludePaths", excludePaths); - this.excludePaths = excludePaths; - } - - public String getExcludePathPatterns() { - return this.excludePathPatterns; - } - - public void setExcludePathPatterns(String excludePathPatterns) { - this.addInitParameter("excludePathPatterns", excludePathPatterns); - this.excludePathPatterns = excludePathPatterns; - } - - public String getVary() { - return this.vary; - } - - public void setVary(String vary) { - this.addInitParameter("vary", vary); - this.vary = vary; - } - - Map getAsInitParameters() { - return this.initParameters; - } - - private void addInitParameter(String name, Integer value) { - if (value != null) { - this.initParameters.put(name, value.toString()); - } - } - - private void addInitParameter(String name, Boolean value) { - if (value != null) { - this.initParameters.put(name, value.toString()); - } - } - - private void addInitParameter(String name, String value) { - if (value != null) { - this.initParameters.put(name, value.toString()); - } - } - -} diff --git a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/ServerProperties.java b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/ServerProperties.java index 38a74dc08b..8af0c07315 100644 --- a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/ServerProperties.java +++ b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/ServerProperties.java @@ -31,8 +31,7 @@ import org.apache.catalina.valves.RemoteIpValve; import org.apache.coyote.AbstractProtocol; import org.apache.coyote.ProtocolHandler; import org.apache.coyote.http11.AbstractHttp11Protocol; -import org.springframework.boot.context.embedded.AbstractConfigurableEmbeddedServletContainer; -import org.springframework.boot.context.embedded.AbstractConfigurableEmbeddedServletContainer.CompressionProperties; +import org.springframework.boot.context.embedded.Compression; import org.springframework.boot.context.embedded.ConfigurableEmbeddedServletContainer; import org.springframework.boot.context.embedded.EmbeddedServletContainerCustomizer; import org.springframework.boot.context.embedded.EmbeddedServletContainerCustomizerBeanPostProcessor; @@ -101,7 +100,8 @@ public class ServerProperties implements EmbeddedServletContainerCustomizer, Ord private final Undertow undertow = new Undertow(); - private CompressionProperties compression = new CompressionProperties(); + @NestedConfigurationProperty + private Compression compression = new Compression(); @NestedConfigurationProperty private JspServlet jspServlet; @@ -124,7 +124,7 @@ public class ServerProperties implements EmbeddedServletContainerCustomizer, Ord return this.undertow; } - public CompressionProperties getCompression() { + public Compression getCompression() { return this.compression; } @@ -247,9 +247,8 @@ public class ServerProperties implements EmbeddedServletContainerCustomizer, Ord if (getJspServlet() != null) { container.setJspServlet(getJspServlet()); } - if (container instanceof AbstractConfigurableEmbeddedServletContainer) { - ((AbstractConfigurableEmbeddedServletContainer) container) - .setCompression(getCompression()); + if (getCompression() != null) { + container.setCompression(getCompression()); } if (container instanceof TomcatEmbeddedServletContainerFactory) { getTomcat() @@ -359,19 +358,6 @@ public class ServerProperties implements EmbeddedServletContainerCustomizer, Ord */ private String uriEncoding; - /** - * Controls response compression. Acceptable values are "off" to disable - * compression, "on" to enable compression of responses over 2048 bytes, "force" - * to force response compression, or an integer value to enable compression of - * responses with content length that is at least that value. - */ - private String compression = "off"; - - /** - * Comma-separated list of MIME types for which compression is used. - */ - private String compressableMimeTypes = "text/html,text/xml,text/plain"; - public int getMaxThreads() { return this.maxThreads; } @@ -420,22 +406,6 @@ public class ServerProperties implements EmbeddedServletContainerCustomizer, Ord this.accessLogPattern = accessLogPattern; } - public String getCompressableMimeTypes() { - return this.compressableMimeTypes; - } - - public void setCompressableMimeTypes(String compressableMimeTypes) { - this.compressableMimeTypes = compressableMimeTypes; - } - - public String getCompression() { - return this.compression; - } - - public void setCompression(String compression) { - this.compression = compression; - } - public String getInternalProxies() { return this.internalProxies; } @@ -496,7 +466,6 @@ public class ServerProperties implements EmbeddedServletContainerCustomizer, Ord if (this.maxHttpHeaderSize > 0) { customizeMaxHttpHeaderSize(factory); } - customizeCompression(factory); if (this.accessLogEnabled) { customizeAccessLog(factory); } @@ -565,33 +534,6 @@ public class ServerProperties implements EmbeddedServletContainerCustomizer, Ord }); } - private void customizeCompression(TomcatEmbeddedServletContainerFactory factory) { - factory.addConnectorCustomizers(new TomcatConnectorCustomizer() { - - @Override - public void customize(Connector connector) { - ProtocolHandler handler = connector.getProtocolHandler(); - if (handler instanceof AbstractHttp11Protocol) { - @SuppressWarnings("rawtypes") - AbstractHttp11Protocol protocol = (AbstractHttp11Protocol) handler; - protocol.setCompression(coerceCompression(Tomcat.this.compression)); - protocol.setCompressableMimeTypes(Tomcat.this.compressableMimeTypes); - } - } - - private String coerceCompression(String compression) { - if ("true".equalsIgnoreCase(compression)) { - return "on"; - } - if ("false".equalsIgnoreCase(compression)) { - return "off"; - } - return compression; - } - - }); - } - private void customizeAccessLog(TomcatEmbeddedServletContainerFactory factory) { AccessLogValve valve = new AccessLogValve(); String accessLogPattern = getAccessLogPattern(); diff --git a/spring-boot-autoconfigure/src/main/resources/META-INF/spring.factories b/spring-boot-autoconfigure/src/main/resources/META-INF/spring.factories index 8fe06c5c4c..5d1895d5a7 100644 --- a/spring-boot-autoconfigure/src/main/resources/META-INF/spring.factories +++ b/spring-boot-autoconfigure/src/main/resources/META-INF/spring.factories @@ -68,7 +68,6 @@ org.springframework.boot.autoconfigure.transaction.jta.JtaAutoConfiguration,\ org.springframework.boot.autoconfigure.web.DispatcherServletAutoConfiguration,\ org.springframework.boot.autoconfigure.web.EmbeddedServletContainerAutoConfiguration,\ org.springframework.boot.autoconfigure.web.ErrorMvcAutoConfiguration,\ -org.springframework.boot.autoconfigure.web.GzipFilterAutoConfiguration,\ org.springframework.boot.autoconfigure.web.HttpEncodingAutoConfiguration,\ org.springframework.boot.autoconfigure.web.HttpMessageConvertersAutoConfiguration,\ org.springframework.boot.autoconfigure.web.MultipartAutoConfiguration,\ diff --git a/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/web/GzipFilterAutoConfigurationTests.java b/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/web/GzipFilterAutoConfigurationTests.java deleted file mode 100644 index 17a5d8306c..0000000000 --- a/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/web/GzipFilterAutoConfigurationTests.java +++ /dev/null @@ -1,123 +0,0 @@ -/* - * Copyright 2012-2015 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.boot.autoconfigure.web; - -import org.junit.After; -import org.junit.Test; -import org.springframework.boot.context.embedded.FilterRegistrationBean; -import org.springframework.boot.test.EnvironmentTestUtils; -import org.springframework.context.annotation.AnnotationConfigApplicationContext; - -import static org.hamcrest.Matchers.contains; -import static org.hamcrest.Matchers.equalTo; -import static org.hamcrest.Matchers.is; -import static org.junit.Assert.assertThat; - -/** - * Tests for {@link GzipFilterAutoConfiguration} - * - * @author Andy Wilkinson - */ -public class GzipFilterAutoConfigurationTests { - - private AnnotationConfigApplicationContext context; - - @After - public void close() { - if (this.context != null) { - this.context.close(); - } - } - - @Test - public void filterIsMappedToSlashStar() { - createAndRefreshContext(); - FilterRegistrationBean registrationBean = this.context.getBean("gzipFilter", - FilterRegistrationBean.class); - assertThat(registrationBean.getUrlPatterns(), contains("/*")); - } - - @Test - public void byDefaultCheckGzExistsIsTheOnlyInitParameter() { - createAndRefreshContext(); - FilterRegistrationBean registrationBean = this.context.getBean("gzipFilter", - FilterRegistrationBean.class); - assertThat(registrationBean.getInitParameters().size(), equalTo(1)); - assertThat(registrationBean.getInitParameters().get("checkGzExists"), - equalTo("false")); - } - - @Test - public void customInitParameterConfiguration() { - createAndRefreshContext("spring.http.gzip.bufferSize:1234", - "spring.http.gzip.minGzipSize:2345", - "spring.http.gzip.deflateCompressionLevel:5", - "spring.http.gzip.deflateNoWrap:false", - "spring.http.gzip.methods:GET,POST", - "spring.http.gzip.mimeTypes:application/foo,application/bar", - "spring.http.gzip.excludedMimeTypes:application/biz", - "spring.http.gzip.excludedAgents:excluded-agent-1,excluded-agent-2", - "spring.http.gzip.excludeAgentPatterns:agent-pattern-1,agent-pattern-2", - "spring.http.gzip.excludePaths:/static/", - "spring.http.gzip.excludePathPatterns:path-pattern", - "spring.http.gzip.vary:vary-header-value"); - FilterRegistrationBean registrationBean = this.context.getBean("gzipFilter", - FilterRegistrationBean.class); - assertThat(registrationBean.getInitParameters().size(), equalTo(13)); - assertThat(registrationBean.getInitParameters().get("checkGzExists"), - equalTo("false")); - assertThat(registrationBean.getInitParameters().get("bufferSize"), - equalTo("1234")); - assertThat(registrationBean.getInitParameters().get("minGzipSize"), - equalTo("2345")); - assertThat(registrationBean.getInitParameters().get("deflateCompressionLevel"), - equalTo("5")); - assertThat(registrationBean.getInitParameters().get("deflateNoWrap"), - equalTo("false")); - assertThat(registrationBean.getInitParameters().get("methods"), - equalTo("GET,POST")); - assertThat(registrationBean.getInitParameters().get("mimeTypes"), - equalTo("application/foo,application/bar")); - assertThat(registrationBean.getInitParameters().get("excludedMimeTypes"), - equalTo("application/biz")); - assertThat(registrationBean.getInitParameters().get("excludedAgents"), - equalTo("excluded-agent-1,excluded-agent-2")); - assertThat(registrationBean.getInitParameters().get("excludeAgentPatterns"), - equalTo("agent-pattern-1,agent-pattern-2")); - assertThat(registrationBean.getInitParameters().get("excludePaths"), - equalTo("/static/")); - assertThat(registrationBean.getInitParameters().get("excludePathPatterns"), - equalTo("path-pattern")); - assertThat(registrationBean.getInitParameters().get("vary"), - equalTo("vary-header-value")); - } - - @Test - public void filterCanBeDisabled() { - createAndRefreshContext("spring.http.gzip.enabled:false"); - assertThat(this.context.getBeanNamesForType(FilterRegistrationBean.class).length, - is(equalTo(0))); - } - - private void createAndRefreshContext(String... pairs) { - this.context = new AnnotationConfigApplicationContext(); - EnvironmentTestUtils.addEnvironment(this.context, pairs); - this.context.register(GzipFilterAutoConfiguration.class); - this.context.refresh(); - } - -} diff --git a/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/web/ServerPropertiesTests.java b/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/web/ServerPropertiesTests.java index 962eb74f52..154e6b8b65 100644 --- a/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/web/ServerPropertiesTests.java +++ b/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/web/ServerPropertiesTests.java @@ -23,21 +23,16 @@ import java.util.Map; import org.apache.catalina.Valve; import org.apache.catalina.valves.RemoteIpValve; -import org.apache.coyote.http11.AbstractHttp11Protocol; import org.junit.Test; import org.springframework.beans.MutablePropertyValues; import org.springframework.boot.bind.RelaxedDataBinder; import org.springframework.boot.context.embedded.ConfigurableEmbeddedServletContainer; -import org.springframework.boot.context.embedded.tomcat.TomcatEmbeddedServletContainer; import org.springframework.boot.context.embedded.tomcat.TomcatEmbeddedServletContainerFactory; -import static org.hamcrest.Matchers.equalTo; -import static org.hamcrest.Matchers.is; import static org.hamcrest.core.IsInstanceOf.instanceOf; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertThat; -import static org.junit.Assert.assertTrue; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.never; import static org.mockito.Mockito.verify; @@ -106,18 +101,6 @@ public class ServerPropertiesTests { .getInternalProxies()); } - @Test - public void testCompressionBinding() throws Exception { - Map map = new HashMap(); - map.put("server.compression.enabled", "true"); - map.put("server.compression.mimeTypes", "foo/bar"); - map.put("server.compression.minSize", "228"); - bindProperties(map); - assertTrue(this.properties.getCompression().isEnabled()); - assertEquals("foo/bar", this.properties.getCompression().getMimeTypes()); - assertEquals(228, this.properties.getCompression().getMinSize()); - } - @Test public void testCustomizeTomcat() throws Exception { ConfigurableEmbeddedServletContainer factory = mock(ConfigurableEmbeddedServletContainer.class); @@ -243,72 +226,9 @@ public class ServerPropertiesTests { assertEquals("192.168.0.1", remoteIpValve.getInternalProxies()); } - @Test - public void customTomcatCompression() throws Exception { - assertThat("on", is(equalTo(configureCompression("on")))); - } - - @Test - public void disableTomcatCompressionWithYaml() throws Exception { - // YAML interprets "off" as false, check that it's mapped back to off - assertThat("off", is(equalTo(configureCompression("faLSe")))); - } - - @Test - public void enableTomcatCompressionWithYaml() throws Exception { - // YAML interprets "on" as true, check that it's mapped back to on - assertThat("on", is(equalTo(configureCompression("trUE")))); - } - - @Test - public void customTomcatCompressableMimeTypes() throws Exception { - Map map = new HashMap(); - map.put("server.port", "0"); - map.put("server.tomcat.compressableMimeTypes", "application/foo"); - bindProperties(map); - - TomcatEmbeddedServletContainerFactory factory = new TomcatEmbeddedServletContainerFactory(); - this.properties.customize(factory); - - TomcatEmbeddedServletContainer container = (TomcatEmbeddedServletContainer) factory - .getEmbeddedServletContainer(); - - try { - AbstractHttp11Protocol protocol = (AbstractHttp11Protocol) container - .getTomcat().getConnector().getProtocolHandler(); - assertEquals("application/foo", protocol.getCompressableMimeTypes()); - } - finally { - container.stop(); - } - } - private void bindProperties(Map map) { new RelaxedDataBinder(this.properties, "server").bind(new MutablePropertyValues( map)); } - private String configureCompression(String compression) { - Map map = new HashMap(); - map.put("server.port", "0"); - // YAML interprets "on" as true - map.put("server.tomcat.compression", compression); - bindProperties(map); - - TomcatEmbeddedServletContainerFactory factory = new TomcatEmbeddedServletContainerFactory(); - this.properties.customize(factory); - - TomcatEmbeddedServletContainer container = (TomcatEmbeddedServletContainer) factory - .getEmbeddedServletContainer(); - - try { - AbstractHttp11Protocol protocol = (AbstractHttp11Protocol) container - .getTomcat().getConnector().getProtocolHandler(); - return protocol.getCompression(); - } - finally { - container.stop(); - } - } - } diff --git a/spring-boot-docs/src/main/asciidoc/appendix-application-properties.adoc b/spring-boot-docs/src/main/asciidoc/appendix-application-properties.adoc index 580a4e3e45..cf780de4d4 100644 --- a/spring-boot-docs/src/main/asciidoc/appendix-application-properties.adoc +++ b/spring-boot-docs/src/main/asciidoc/appendix-application-properties.adoc @@ -66,6 +66,9 @@ content into your application; rather pick only the properties that you need. server.port=8080 server.address= # bind to a specific NIC server.session-timeout= # session timeout in seconds + server.compression.enabled=false # if response compression is enabled + server.compression.mime-types=text/html,text/xml,text/plain,text/css # comma-separated list of MIME types that should be compressed + server.compression.min-response-size=2048 # minimum response size that is required for compression to be performed server.context-parameters.*= # Servlet context init parameters, e.g. server.context-parameters.a=alpha server.context-path= # the context path, defaults to '/' server.jsp-servlet.class-name=org.apache.jasper.servlet.JspServlet # The class name of the JSP servlet @@ -89,8 +92,6 @@ content into your application; rather pick only the properties that you need. server.ssl.trust-store-type= server.tomcat.access-log-pattern= # log pattern of the access log server.tomcat.access-log-enabled=false # is access logging enabled - server.tomcat.compression=off # is compression enabled (off, on, or an integer content length limit) - server.tomcat.compressable-mime-types=text/html,text/xml,text/plain # comma-separated list of mime types that Tomcat will compress server.tomcat.internal-proxies=10\\.\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}|\\ 192\\.168\\.\\d{1,3}\\.\\d{1,3}|\\ 169\\.254\\.\\d{1,3}\\.\\d{1,3}|\\ @@ -156,21 +157,6 @@ content into your application; rather pick only the properties that you need. # HTTP message conversion spring.http.converters.preferred-json-mapper= # the preferred JSON mapper to use for HTTP message conversion. Set to "gson" to force the use of Gson when both it and Jackson are on the classpath. - # HTTP response compression ({sc-spring-boot-autoconfigure}/web/GzipFilterProperties.{sc-ext}[GzipFilterProperties]) - spring.http.gzip.buffer-size= # size of the output buffer in bytes - spring.http.gzip.deflate-compression-level= # the level used for deflate compression (0-9) - spring.http.gzip.deflate-no-wrap= # noWrap setting for deflate compression (true or false) - spring.http.gzip.enabled=true # enable gzip filter support - spring.http.gzip.excluded-agents= # comma-separated list of user agents to exclude from compression - spring.http.gzip.exclude-agent-patterns= # comma-separated list of regular expression patterns to control user agents excluded from compression - spring.http.gzip.exclude-paths= # comma-separated list of paths to exclude from compression - spring.http.gzip.exclude-path-patterns= # comma-separated list of regular expression patterns to control the paths that are excluded from compression - spring.http.gzip.methods= # comma-separated list of HTTP methods for which compression is enabled - spring.http.gzip.mime-types= # comma-separated list of MIME types which should be compressed - spring.http.gzip.excluded-mime-types= # comma-separated list of MIME types to exclude from compression - spring.http.gzip.min-gzip-size= # minimum content length required for compression to occur - spring.http.gzip.vary= # Vary header to be sent on responses that may be compressed - # JACKSON ({sc-spring-boot-autoconfigure}/jackson/JacksonProperties.{sc-ext}[JacksonProperties]) spring.jackson.date-format= # Date format string (e.g. yyyy-MM-dd HH:mm:ss), or a fully-qualified date format class name (e.g. com.fasterxml.jackson.databind.util.ISO8601DateFormat) spring.jackson.property-naming-strategy= # One of the constants on Jackson's PropertyNamingStrategy (e.g. CAMEL_CASE_TO_LOWER_CASE_WITH_UNDERSCORES) or the fully-qualified class name of a PropertyNamingStrategy subclass diff --git a/spring-boot-docs/src/main/asciidoc/howto.adoc b/spring-boot-docs/src/main/asciidoc/howto.adoc index 0f1c4357c2..fd3f141bbb 100644 --- a/spring-boot-docs/src/main/asciidoc/howto.adoc +++ b/spring-boot-docs/src/main/asciidoc/howto.adoc @@ -834,53 +834,27 @@ not required. [[how-to-enable-http-response-compression]] === Enable HTTP response compression -Spring Boot provides two mechanisms for enabling compression of HTTP compression; one -that is Tomcat-specific and another that uses a filter and works with Jetty, Tomcat, -and Undertow. - - - -[[how-to-enable-http-response-compression-tomcat]] -==== Enable Tomcat's HTTP response compression -Tomcat provides built-in support for HTTP response compression. It is disabled by -default, but can easily be enabled via `application.properties`: +HTTP response compression is supported by Jetty, Tomcat, and Undertow. It can be enabled +via `application.properties`: [source,properties,indent=0,subs="verbatim,quotes,attributes"] ---- - server.tomcat.compression=on + server.compression.enabled=true ---- -When set to `on` Tomcat will compress responses with a length that is at least 2048 -bytes. This limit can be configured by specifying an integer value rather than `on`, -e.g.: +By default, responses must be at least 2048 bytes in length for compression to be +performed. This can be configured using the `server.compression.min-response-size` +property. -[source,properties,indent=0,subs="verbatim,quotes,attributes"] ----- - server.tomcat.compression=4096 ----- +By default, responses will only be compressed if their content type is one of the +following: -By default Tomcat will only compress responses with certain MIME types -(`text/html`, `text/xml`, and `text/plain`). You can customize this using the -`server.tomcat.compressableMimeTypes` property, e.g.: + - `text/html` + - `text/xml` + - `text/plain` + - `text/css` -[source,properties,indent=0,subs="verbatim,quotes,attributes"] ----- - server.tomcat.compressableMimeTypes=application/json,application/xml ----- - - - -[[how-to-enable-http-compression-gzip-filter]] -==== Enable HTTP response compression using GzipFilter -If you're using Jetty or Undertow, or you want more sophisticated control over -HTTP response compression, Spring Boot provides auto-configuration for Jetty's -`GzipFilter`. While this filter is part of Jetty, it's compatible with Tomcat -and Undertow as well. To enable the filter, simply add a dependency on -`org.eclipse.jetty:jetty-servlets` to your application. - -`GzipFilter` can be configured using the `spring.http.gzip.*` properties. See -{sc-spring-boot-autoconfigure}/web/GzipFilterProperties.{sc-ext}[`GzipFilterProperties`] -for more details. +This can be configured using the `server.compression.mime-types` property. diff --git a/spring-boot-integration-tests/spring-boot-gradle-tests/src/test/java/org/springframework/boot/gradle/WarPackagingTests.java b/spring-boot-integration-tests/spring-boot-gradle-tests/src/test/java/org/springframework/boot/gradle/WarPackagingTests.java index 4a3383c59f..f9c247ab1a 100644 --- a/spring-boot-integration-tests/spring-boot-gradle-tests/src/test/java/org/springframework/boot/gradle/WarPackagingTests.java +++ b/spring-boot-integration-tests/spring-boot-gradle-tests/src/test/java/org/springframework/boot/gradle/WarPackagingTests.java @@ -51,9 +51,10 @@ public class WarPackagingTests { "tomcat-embed-websocket-")); private static final Set JETTY_EXPECTED_IN_WEB_INF_LIB_PROVIDED = new HashSet( - Arrays.asList("spring-boot-starter-jetty-", "jetty-util-", "javax.servlet-", - "jetty-io-", "jetty-http-", "jetty-server-", "jetty-security-", - "jetty-servlet-", "jetty-webapp-", "websocket-api", + Arrays.asList("spring-boot-starter-jetty-", "jetty-continuation", + "jetty-util-", "javax.servlet-", "jetty-io-", "jetty-http-", + "jetty-server-", "jetty-security-", "jetty-servlet-", + "jetty-servlets", "jetty-webapp-", "websocket-api", "javax.annotation-api", "jetty-plus", "javax-websocket-server-impl-", "asm-", "javax.websocket-api-", "asm-tree-", "asm-commons-", "websocket-common-", "jetty-annotations-", diff --git a/spring-boot-samples/spring-boot-sample-jetty/src/main/resources/application.properties b/spring-boot-samples/spring-boot-sample-jetty/src/main/resources/application.properties new file mode 100644 index 0000000000..f0cf321720 --- /dev/null +++ b/spring-boot-samples/spring-boot-sample-jetty/src/main/resources/application.properties @@ -0,0 +1,2 @@ +server.compression.enabled: true +server.compression.min-response-size: 1 \ No newline at end of file diff --git a/spring-boot-samples/spring-boot-sample-jetty/src/test/java/sample/jetty/SampleJettyApplicationTests.java b/spring-boot-samples/spring-boot-sample-jetty/src/test/java/sample/jetty/SampleJettyApplicationTests.java index bbc5ffb73f..25a17d3bbb 100644 --- a/spring-boot-samples/spring-boot-sample-jetty/src/test/java/sample/jetty/SampleJettyApplicationTests.java +++ b/spring-boot-samples/spring-boot-sample-jetty/src/test/java/sample/jetty/SampleJettyApplicationTests.java @@ -16,17 +16,26 @@ package sample.jetty; +import java.io.ByteArrayInputStream; +import java.nio.charset.Charset; +import java.util.zip.GZIPInputStream; + import org.junit.Test; import org.junit.runner.RunWith; import org.springframework.beans.factory.annotation.Value; import org.springframework.boot.test.IntegrationTest; import org.springframework.boot.test.SpringApplicationConfiguration; import org.springframework.boot.test.TestRestTemplate; +import org.springframework.http.HttpEntity; +import org.springframework.http.HttpHeaders; +import org.springframework.http.HttpMethod; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; import org.springframework.test.annotation.DirtiesContext; import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; import org.springframework.test.context.web.WebAppConfiguration; +import org.springframework.util.StreamUtils; +import org.springframework.web.client.RestTemplate; import static org.junit.Assert.assertEquals; @@ -34,6 +43,7 @@ import static org.junit.Assert.assertEquals; * Basic integration tests for demo application. * * @author Dave Syer + * @author Andy Wilkinson */ @RunWith(SpringJUnit4ClassRunner.class) @SpringApplicationConfiguration(classes = SampleJettyApplication.class) @@ -53,4 +63,28 @@ public class SampleJettyApplicationTests { assertEquals("Hello World", entity.getBody()); } + @Test + public void testCompression() throws Exception { + HttpHeaders requestHeaders = new HttpHeaders(); + requestHeaders.set("Accept-Encoding", "gzip"); + HttpEntity requestEntity = new HttpEntity(requestHeaders); + + RestTemplate restTemplate = new TestRestTemplate(); + + ResponseEntity entity = restTemplate.exchange("http://localhost:" + + this.port, HttpMethod.GET, requestEntity, byte[].class); + + assertEquals(HttpStatus.OK, entity.getStatusCode()); + + GZIPInputStream inflater = new GZIPInputStream(new ByteArrayInputStream( + entity.getBody())); + try { + assertEquals("Hello World", + StreamUtils.copyToString(inflater, Charset.forName("UTF-8"))); + } + finally { + inflater.close(); + } + } + } diff --git a/spring-boot-samples/spring-boot-sample-jetty8/src/main/resources/application.properties b/spring-boot-samples/spring-boot-sample-jetty8/src/main/resources/application.properties new file mode 100644 index 0000000000..f0cf321720 --- /dev/null +++ b/spring-boot-samples/spring-boot-sample-jetty8/src/main/resources/application.properties @@ -0,0 +1,2 @@ +server.compression.enabled: true +server.compression.min-response-size: 1 \ No newline at end of file diff --git a/spring-boot-samples/spring-boot-sample-jetty8/src/test/java/sample/jetty8/SampleJetty8ApplicationTests.java b/spring-boot-samples/spring-boot-sample-jetty8/src/test/java/sample/jetty8/SampleJetty8ApplicationTests.java index bd0f800438..d10349a2c7 100644 --- a/spring-boot-samples/spring-boot-sample-jetty8/src/test/java/sample/jetty8/SampleJetty8ApplicationTests.java +++ b/spring-boot-samples/spring-boot-sample-jetty8/src/test/java/sample/jetty8/SampleJetty8ApplicationTests.java @@ -16,25 +16,34 @@ package sample.jetty8; +import java.io.ByteArrayInputStream; +import java.nio.charset.Charset; +import java.util.zip.GZIPInputStream; + import org.junit.Test; import org.junit.runner.RunWith; import org.springframework.beans.factory.annotation.Value; import org.springframework.boot.test.IntegrationTest; import org.springframework.boot.test.SpringApplicationConfiguration; import org.springframework.boot.test.TestRestTemplate; +import org.springframework.http.HttpEntity; +import org.springframework.http.HttpHeaders; +import org.springframework.http.HttpMethod; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; import org.springframework.test.annotation.DirtiesContext; import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; import org.springframework.test.context.web.WebAppConfiguration; +import org.springframework.util.StreamUtils; +import org.springframework.web.client.RestTemplate; -import sample.jetty8.SampleJetty8Application; import static org.junit.Assert.assertEquals; /** * Basic integration tests for demo application. * * @author Dave Syer + * @author Andy Wilkinson */ @RunWith(SpringJUnit4ClassRunner.class) @SpringApplicationConfiguration(classes = SampleJetty8Application.class) @@ -54,4 +63,28 @@ public class SampleJetty8ApplicationTests { assertEquals("Hello World", entity.getBody()); } + @Test + public void testCompression() throws Exception { + HttpHeaders requestHeaders = new HttpHeaders(); + requestHeaders.set("Accept-Encoding", "gzip"); + HttpEntity requestEntity = new HttpEntity(requestHeaders); + + RestTemplate restTemplate = new TestRestTemplate(); + + ResponseEntity entity = restTemplate.exchange("http://localhost:" + + this.port, HttpMethod.GET, requestEntity, byte[].class); + + assertEquals(HttpStatus.OK, entity.getStatusCode()); + + GZIPInputStream inflater = new GZIPInputStream(new ByteArrayInputStream( + entity.getBody())); + try { + assertEquals("Hello World", + StreamUtils.copyToString(inflater, Charset.forName("UTF-8"))); + } + finally { + inflater.close(); + } + } + } diff --git a/spring-boot-samples/spring-boot-sample-tomcat/src/main/resources/application.properties b/spring-boot-samples/spring-boot-sample-tomcat/src/main/resources/application.properties new file mode 100644 index 0000000000..f0cf321720 --- /dev/null +++ b/spring-boot-samples/spring-boot-sample-tomcat/src/main/resources/application.properties @@ -0,0 +1,2 @@ +server.compression.enabled: true +server.compression.min-response-size: 1 \ No newline at end of file diff --git a/spring-boot-samples/spring-boot-sample-tomcat/src/test/java/sample/tomcat/SampleTomcatApplicationTests.java b/spring-boot-samples/spring-boot-sample-tomcat/src/test/java/sample/tomcat/SampleTomcatApplicationTests.java index f24ec1536f..96f38455a4 100644 --- a/spring-boot-samples/spring-boot-sample-tomcat/src/test/java/sample/tomcat/SampleTomcatApplicationTests.java +++ b/spring-boot-samples/spring-boot-sample-tomcat/src/test/java/sample/tomcat/SampleTomcatApplicationTests.java @@ -16,16 +16,25 @@ package sample.tomcat; +import java.io.ByteArrayInputStream; +import java.nio.charset.Charset; +import java.util.zip.GZIPInputStream; + import org.junit.Test; import org.junit.runner.RunWith; import org.springframework.beans.factory.annotation.Value; import org.springframework.boot.test.SpringApplicationConfiguration; import org.springframework.boot.test.TestRestTemplate; import org.springframework.boot.test.WebIntegrationTest; +import org.springframework.http.HttpEntity; +import org.springframework.http.HttpHeaders; +import org.springframework.http.HttpMethod; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; import org.springframework.test.annotation.DirtiesContext; import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; +import org.springframework.util.StreamUtils; +import org.springframework.web.client.RestTemplate; import static org.junit.Assert.assertEquals; @@ -33,6 +42,7 @@ import static org.junit.Assert.assertEquals; * Basic integration tests for demo application. * * @author Dave Syer + * @author Andy Wilkinson */ @RunWith(SpringJUnit4ClassRunner.class) @SpringApplicationConfiguration(classes = SampleTomcatApplication.class) @@ -51,4 +61,28 @@ public class SampleTomcatApplicationTests { assertEquals("Hello World", entity.getBody()); } + @Test + public void testCompression() throws Exception { + HttpHeaders requestHeaders = new HttpHeaders(); + requestHeaders.set("Accept-Encoding", "gzip"); + HttpEntity requestEntity = new HttpEntity(requestHeaders); + + RestTemplate restTemplate = new TestRestTemplate(); + + ResponseEntity entity = restTemplate.exchange("http://localhost:" + + this.port, HttpMethod.GET, requestEntity, byte[].class); + + assertEquals(HttpStatus.OK, entity.getStatusCode()); + + GZIPInputStream inflater = new GZIPInputStream(new ByteArrayInputStream( + entity.getBody())); + try { + assertEquals("Hello World", + StreamUtils.copyToString(inflater, Charset.forName("UTF-8"))); + } + finally { + inflater.close(); + } + } + } diff --git a/spring-boot-samples/spring-boot-sample-undertow/src/main/resources/application.properties b/spring-boot-samples/spring-boot-sample-undertow/src/main/resources/application.properties index 9d5b25a84a..5b074bf346 100644 --- a/spring-boot-samples/spring-boot-sample-undertow/src/main/resources/application.properties +++ b/spring-boot-samples/spring-boot-sample-undertow/src/main/resources/application.properties @@ -1,3 +1,5 @@ server.undertow.access-log-enabled=true server.undertow.access-log-dir=target/logs -server.undertow.access-log-pattern=combined \ No newline at end of file +server.undertow.access-log-pattern=combined +server.compression.enabled=true +server.compression.min-response-size=1 \ No newline at end of file diff --git a/spring-boot-samples/spring-boot-sample-undertow/src/test/java/sample/undertow/SampleUndertowApplicationTests.java b/spring-boot-samples/spring-boot-sample-undertow/src/test/java/sample/undertow/SampleUndertowApplicationTests.java index 2e1f835ceb..157e277369 100644 --- a/spring-boot-samples/spring-boot-sample-undertow/src/test/java/sample/undertow/SampleUndertowApplicationTests.java +++ b/spring-boot-samples/spring-boot-sample-undertow/src/test/java/sample/undertow/SampleUndertowApplicationTests.java @@ -16,17 +16,26 @@ package sample.undertow; +import java.io.ByteArrayInputStream; +import java.nio.charset.Charset; +import java.util.zip.GZIPInputStream; + import org.junit.Test; import org.junit.runner.RunWith; import org.springframework.beans.factory.annotation.Value; import org.springframework.boot.test.IntegrationTest; import org.springframework.boot.test.SpringApplicationConfiguration; import org.springframework.boot.test.TestRestTemplate; +import org.springframework.http.HttpEntity; +import org.springframework.http.HttpHeaders; +import org.springframework.http.HttpMethod; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; import org.springframework.test.annotation.DirtiesContext; import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; import org.springframework.test.context.web.WebAppConfiguration; +import org.springframework.util.StreamUtils; +import org.springframework.web.client.RestTemplate; import static org.junit.Assert.assertEquals; @@ -56,6 +65,30 @@ public class SampleUndertowApplicationTests { assertOkResponse("/async", "async: Hello World"); } + @Test + public void testCompression() throws Exception { + HttpHeaders requestHeaders = new HttpHeaders(); + requestHeaders.set("Accept-Encoding", "gzip"); + HttpEntity requestEntity = new HttpEntity(requestHeaders); + + RestTemplate restTemplate = new TestRestTemplate(); + + ResponseEntity entity = restTemplate.exchange("http://localhost:" + + this.port, HttpMethod.GET, requestEntity, byte[].class); + + assertEquals(HttpStatus.OK, entity.getStatusCode()); + + GZIPInputStream inflater = new GZIPInputStream(new ByteArrayInputStream( + entity.getBody())); + try { + assertEquals("Hello World", + StreamUtils.copyToString(inflater, Charset.forName("UTF-8"))); + } + finally { + inflater.close(); + } + } + private void assertOkResponse(String path, String body) { ResponseEntity entity = new TestRestTemplate().getForEntity( "http://localhost:" + this.port + path, String.class); diff --git a/spring-boot-starters/spring-boot-starter-jetty/pom.xml b/spring-boot-starters/spring-boot-starter-jetty/pom.xml index 24a1b7f5a3..a13f6c0a28 100644 --- a/spring-boot-starters/spring-boot-starter-jetty/pom.xml +++ b/spring-boot-starters/spring-boot-starter-jetty/pom.xml @@ -18,6 +18,10 @@ ${basedir}/../.. + + org.eclipse.jetty + jetty-servlets + org.eclipse.jetty jetty-webapp diff --git a/spring-boot/src/main/java/org/springframework/boot/context/embedded/AbstractConfigurableEmbeddedServletContainer.java b/spring-boot/src/main/java/org/springframework/boot/context/embedded/AbstractConfigurableEmbeddedServletContainer.java index db8b75893b..f1ed20b9cc 100644 --- a/spring-boot/src/main/java/org/springframework/boot/context/embedded/AbstractConfigurableEmbeddedServletContainer.java +++ b/spring-boot/src/main/java/org/springframework/boot/context/embedded/AbstractConfigurableEmbeddedServletContainer.java @@ -23,7 +23,6 @@ import java.util.Arrays; import java.util.LinkedHashSet; import java.util.List; import java.util.Set; -import java.util.StringTokenizer; import java.util.concurrent.TimeUnit; import org.springframework.util.Assert; @@ -36,6 +35,7 @@ import org.springframework.util.ClassUtils; * @author Dave Syer * @author Andy Wilkinson * @author Stephane Nicoll + * @author Ivan Sopov * @see AbstractEmbeddedServletContainerFactory */ public abstract class AbstractConfigurableEmbeddedServletContainer implements @@ -68,7 +68,7 @@ public abstract class AbstractConfigurableEmbeddedServletContainer implements private JspServlet jspServlet = new JspServlet(); - private CompressionProperties compression; + private Compression compression; /** * Create a new {@link AbstractConfigurableEmbeddedServletContainer} instance. @@ -281,11 +281,11 @@ public abstract class AbstractConfigurableEmbeddedServletContainer implements return this.jspServlet; } - public CompressionProperties getCompression() { + public Compression getCompression() { return this.compression; } - public void setCompression(CompressionProperties compression) { + public void setCompression(Compression compression) { this.compression = compression; } @@ -317,44 +317,4 @@ public abstract class AbstractConfigurableEmbeddedServletContainer implements .getClassLoader()); } - public static class CompressionProperties { - private boolean enabled = false; - private String mimeTypes = "text/html,text/xml,text/plain,text/css"; - private int minSize = 2048; - - public boolean isEnabled() { - return enabled; - } - - public void setEnabled(boolean enabled) { - this.enabled = enabled; - } - - public String getMimeTypes() { - return mimeTypes; - } - - public void setMimeTypes(String mimeTypes) { - this.mimeTypes = mimeTypes; - } - - public int getMinSize() { - return minSize; - } - - public void setMinSize(int minSize) { - this.minSize = minSize; - } - - public List getMimeTypesList() { - List mimeTypesList = new ArrayList(); - StringTokenizer tok = new StringTokenizer(mimeTypes, ",", false); - while (tok.hasMoreTokens()) { - mimeTypesList.add(tok.nextToken()); - } - return mimeTypesList; - } - - } - } diff --git a/spring-boot/src/main/java/org/springframework/boot/context/embedded/Compression.java b/spring-boot/src/main/java/org/springframework/boot/context/embedded/Compression.java new file mode 100644 index 0000000000..43985a4ffa --- /dev/null +++ b/spring-boot/src/main/java/org/springframework/boot/context/embedded/Compression.java @@ -0,0 +1,68 @@ +/* + * Copyright 2012-2015 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.context.embedded; + +/** + * Simple container-independent abstraction for compression configuration. + * + * @author Ivan Sopov + * @author Andy Wilkinson + * @since 1.3.0 + */ +public class Compression { + + /** + * If response compression is enabled. + */ + private boolean enabled = false; + + /** + * Comma-separated list of MIME types that should be compressed. + */ + private String[] mimeTypes = new String[] { "text/html", "text/xml", "text/plain", + "text/css" }; + + /** + * Minimum response size that is required for compression to be performed + */ + private int minResponseSize = 2048; + + public boolean getEnabled() { + return this.enabled; + } + + public void setEnabled(boolean enabled) { + this.enabled = enabled; + } + + public String[] getMimeTypes() { + return this.mimeTypes; + } + + public void setMimeTypes(String[] mimeTypes) { + this.mimeTypes = mimeTypes; + } + + public int getMinResponseSize() { + return this.minResponseSize; + } + + public void setMinResponseSize(int minSize) { + this.minResponseSize = minSize; + } + +} \ No newline at end of file diff --git a/spring-boot/src/main/java/org/springframework/boot/context/embedded/ConfigurableEmbeddedServletContainer.java b/spring-boot/src/main/java/org/springframework/boot/context/embedded/ConfigurableEmbeddedServletContainer.java index e5074b182c..fc2732edf8 100644 --- a/spring-boot/src/main/java/org/springframework/boot/context/embedded/ConfigurableEmbeddedServletContainer.java +++ b/spring-boot/src/main/java/org/springframework/boot/context/embedded/ConfigurableEmbeddedServletContainer.java @@ -170,4 +170,11 @@ public interface ConfigurableEmbeddedServletContainer { */ void setJspServlet(JspServlet jspServlet); + /** + * Sets the compression configuration that will be applied to the container's default + * connector. + * @param compression the compression configuration + */ + void setCompression(Compression compression); + } diff --git a/spring-boot/src/main/java/org/springframework/boot/context/embedded/jetty/JettyEmbeddedServletContainerFactory.java b/spring-boot/src/main/java/org/springframework/boot/context/embedded/jetty/JettyEmbeddedServletContainerFactory.java index c40655a331..930c78f23d 100644 --- a/spring-boot/src/main/java/org/springframework/boot/context/embedded/jetty/JettyEmbeddedServletContainerFactory.java +++ b/spring-boot/src/main/java/org/springframework/boot/context/embedded/jetty/JettyEmbeddedServletContainerFactory.java @@ -18,13 +18,14 @@ package org.springframework.boot.context.embedded.jetty; import java.io.File; import java.io.IOException; -import java.lang.reflect.Method; import java.net.InetSocketAddress; import java.net.URL; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; +import java.util.HashSet; import java.util.List; +import java.util.Set; import org.eclipse.jetty.http.HttpVersion; import org.eclipse.jetty.http.MimeTypes; @@ -42,6 +43,7 @@ import org.eclipse.jetty.server.handler.HandlerWrapper; import org.eclipse.jetty.servlet.ErrorPageErrorHandler; import org.eclipse.jetty.servlet.ServletHolder; import org.eclipse.jetty.servlet.ServletMapping; +import org.eclipse.jetty.servlets.gzip.GzipHandler; import org.eclipse.jetty.util.resource.JarResource; import org.eclipse.jetty.util.resource.Resource; import org.eclipse.jetty.util.ssl.SslContextFactory; @@ -49,6 +51,7 @@ import org.eclipse.jetty.webapp.AbstractConfiguration; import org.eclipse.jetty.webapp.Configuration; import org.eclipse.jetty.webapp.WebAppContext; import org.springframework.boot.context.embedded.AbstractEmbeddedServletContainerFactory; +import org.springframework.boot.context.embedded.Compression; import org.springframework.boot.context.embedded.EmbeddedServletContainer; import org.springframework.boot.context.embedded.EmbeddedServletContainerException; import org.springframework.boot.context.embedded.EmbeddedServletContainerFactory; @@ -84,6 +87,12 @@ import org.springframework.util.StringUtils; public class JettyEmbeddedServletContainerFactory extends AbstractEmbeddedServletContainerFactory implements ResourceLoaderAware { + private static final String GZIP_HANDLER_JETTY_9_2 = "org.eclipse.jetty.servlets.gzip.GzipHandler"; + + private static final String GZIP_HANDLER_JETTY_8 = "org.eclipse.jetty.server.handler.GzipHandler"; + + private static final String GZIP_HANDLER_JETTY_9_3 = "org.eclipse.jetty.server.handler.gzip.GzipHandler"; + private List configurations = new ArrayList(); private List jettyServerCustomizers = new ArrayList(); @@ -116,12 +125,6 @@ public class JettyEmbeddedServletContainerFactory extends super(contextPath, port); } - private static final String[] GZIP_HANDLER_CLASSNAMES = new String[] { - "org.eclipse.jetty.servlets.gzip.GzipHandler", // Jetty 9.2 - "org.eclipse.jetty.server.handler.gzip.GzipHandler", // Jetty 9.3 - "org.eclipse.jetty.server.handler.GzipHandler" // Jetty 8 - }; - @Override public EmbeddedServletContainer getEmbeddedServletContainer( ServletContextInitializer... initializers) { @@ -129,8 +132,10 @@ public class JettyEmbeddedServletContainerFactory extends int port = (getPort() >= 0 ? getPort() : 0); Server server = new Server(new InetSocketAddress(getAddress(), port)); configureWebAppContext(context, initializers); - if (getCompression() != null && getCompression().isEnabled()) { - setupGzipHandler(context, server); + if (getCompression() != null && getCompression().getEnabled()) { + HandlerWrapper gzipHandler = createGzipHandler(); + gzipHandler.setHandler(context); + server.setHandler(gzipHandler); } else { server.setHandler(context); @@ -149,50 +154,19 @@ public class JettyEmbeddedServletContainerFactory extends return getJettyEmbeddedServletContainer(server); } - private void setupGzipHandler(JettyEmbeddedWebAppContext context, Server server) - throws LinkageError { - boolean done = false; - for (String gzipHandlerClassName : GZIP_HANDLER_CLASSNAMES) { - if (ClassUtils.isPresent(gzipHandlerClassName, null)) { - try { - Class gzipHandlerClass = ClassUtils.forName(gzipHandlerClassName, - null); - - HandlerWrapper gzipHandler = (HandlerWrapper) gzipHandlerClass - .newInstance(); - gzipHandler.setHandler(context); - - Method minGzipSizeMethod = ReflectionUtils.findMethod( - gzipHandlerClass, "setMinGzipSize", int.class); - minGzipSizeMethod.invoke(gzipHandler, getCompression().getMinSize()); - - Method mimeTypesMethod = ReflectionUtils.findMethod(gzipHandlerClass, - "setMimeTypes", String.class); - if (mimeTypesMethod != null) { - // Jetty 8 & Jety 9.2 - mimeTypesMethod.invoke(gzipHandler, getCompression() - .getMimeTypes()); - } - else { - // Jetty 9.3 - mimeTypesMethod = ReflectionUtils.findMethod(gzipHandlerClass, - "setIncludedMimeTypes", String[].class); - List mimeTypes = getCompression().getMimeTypesList(); - mimeTypesMethod.invoke(gzipHandler, - (Object) mimeTypes.toArray(new String[mimeTypes.size()])); - } - server.setHandler(gzipHandler); - done = true; - break; - } - catch (Exception e) { - throw new RuntimeException(e); - } - } + private HandlerWrapper createGzipHandler() { + if (ClassUtils.isPresent(GZIP_HANDLER_JETTY_9_2, getClass().getClassLoader())) { + return new Jetty92GzipHandlerFactory().createGzipHandler(getCompression()); } - if (!done) { - throw new IllegalStateException("Jetty GzipHandler is not in classpath"); + else if (ClassUtils.isPresent(GZIP_HANDLER_JETTY_8, getClass().getClassLoader())) { + return new Jetty8GzipHandlerFactory().createGzipHandler(getCompression()); } + else if (ClassUtils + .isPresent(GZIP_HANDLER_JETTY_9_3, getClass().getClassLoader())) { + return new Jetty93GzipHandlerFactory().createGzipHandler(getCompression()); + } + throw new IllegalStateException( + "Compression is enabled, but GzipHandler is not on the classpath"); } private SslServerConnectorFactory getSslServerConnectorFactory() { @@ -594,4 +568,73 @@ public class JettyEmbeddedServletContainerFactory extends } + private interface GzipHandlerFactory { + + HandlerWrapper createGzipHandler(Compression compression); + + } + + private static class Jetty8GzipHandlerFactory implements GzipHandlerFactory { + + @Override + public HandlerWrapper createGzipHandler(Compression compression) { + try { + Class gzipHandlerClass = ClassUtils.forName(GZIP_HANDLER_JETTY_8, + getClass().getClassLoader()); + HandlerWrapper gzipHandler = (HandlerWrapper) gzipHandlerClass + .newInstance(); + ReflectionUtils.findMethod(gzipHandlerClass, "setMinGzipSize", int.class) + .invoke(gzipHandler, compression.getMinResponseSize()); + ReflectionUtils.findMethod(gzipHandlerClass, "setMimeTypes", Set.class) + .invoke(gzipHandler, + new HashSet(Arrays.asList(compression + .getMimeTypes()))); + return gzipHandler; + } + + catch (Exception ex) { + throw new RuntimeException("Failed to configure Jetty 8 gzip handler", ex); + } + } + + } + + private static class Jetty92GzipHandlerFactory implements GzipHandlerFactory { + + @Override + public HandlerWrapper createGzipHandler(Compression compression) { + GzipHandler gzipHandler = new GzipHandler(); + gzipHandler.setMinGzipSize(compression.getMinResponseSize()); + gzipHandler.setMimeTypes(new HashSet(Arrays.asList(compression + .getMimeTypes()))); + return gzipHandler; + + } + + } + + private static class Jetty93GzipHandlerFactory implements GzipHandlerFactory { + + @Override + public HandlerWrapper createGzipHandler(Compression compression) { + try { + Class gzipHandlerClass = ClassUtils.forName(GZIP_HANDLER_JETTY_9_3, + getClass().getClassLoader()); + HandlerWrapper gzipHandler = (HandlerWrapper) gzipHandlerClass + .newInstance(); + ReflectionUtils.findMethod(gzipHandlerClass, "setMinGzipSize", int.class) + .invoke(gzipHandler, compression.getMinResponseSize()); + ReflectionUtils.findMethod(gzipHandlerClass, "setIncludedMimeTypes", + String[].class).invoke(gzipHandler, + new Object[] { compression.getMimeTypes() }); + return gzipHandler; + } + + catch (Exception ex) { + throw new RuntimeException("Failed to configure Jetty 9.3 gzip handler", + ex); + } + } + + } } diff --git a/spring-boot/src/main/java/org/springframework/boot/context/embedded/tomcat/TomcatEmbeddedServletContainerFactory.java b/spring-boot/src/main/java/org/springframework/boot/context/embedded/tomcat/TomcatEmbeddedServletContainerFactory.java index 9045648a15..9e256d5181 100644 --- a/spring-boot/src/main/java/org/springframework/boot/context/embedded/tomcat/TomcatEmbeddedServletContainerFactory.java +++ b/spring-boot/src/main/java/org/springframework/boot/context/embedded/tomcat/TomcatEmbeddedServletContainerFactory.java @@ -257,14 +257,15 @@ public class TomcatEmbeddedServletContainerFactory extends connector.setSecure(true); } - if (getCompression() != null && getCompression().isEnabled()) { + if (getCompression() != null && getCompression().getEnabled()) { ProtocolHandler handler = connector.getProtocolHandler(); if (handler instanceof AbstractHttp11Protocol) { @SuppressWarnings("rawtypes") AbstractHttp11Protocol protocol = (AbstractHttp11Protocol) handler; protocol.setCompression("on"); - protocol.setCompressionMinSize(getCompression().getMinSize()); - protocol.setCompressableMimeTypes(getCompression().getMimeTypes()); + protocol.setCompressionMinSize(getCompression().getMinResponseSize()); + protocol.setCompressableMimeTypes(StringUtils + .arrayToCommaDelimitedString(getCompression().getMimeTypes())); } } diff --git a/spring-boot/src/main/java/org/springframework/boot/context/embedded/undertow/UndertowEmbeddedServletContainer.java b/spring-boot/src/main/java/org/springframework/boot/context/embedded/undertow/UndertowEmbeddedServletContainer.java index 82dfe4583a..b9e1a1c409 100644 --- a/spring-boot/src/main/java/org/springframework/boot/context/embedded/undertow/UndertowEmbeddedServletContainer.java +++ b/spring-boot/src/main/java/org/springframework/boot/context/embedded/undertow/UndertowEmbeddedServletContainer.java @@ -19,17 +19,14 @@ package org.springframework.boot.context.embedded.undertow; import io.undertow.Handlers; import io.undertow.Undertow; import io.undertow.Undertow.Builder; -import io.undertow.attribute.ConstantExchangeAttribute; -import io.undertow.attribute.ExchangeAttribute; -import io.undertow.attribute.ResponseHeaderAttribute; import io.undertow.predicate.Predicate; import io.undertow.predicate.Predicates; import io.undertow.server.HttpHandler; +import io.undertow.server.HttpServerExchange; import io.undertow.server.handlers.encoding.ContentEncodingRepository; import io.undertow.server.handlers.encoding.EncodingHandler; import io.undertow.server.handlers.encoding.GzipEncodingProvider; import io.undertow.servlet.api.DeploymentManager; -import io.undertow.util.HttpString; import java.lang.reflect.Field; import java.net.ServerSocket; @@ -40,10 +37,12 @@ import javax.servlet.ServletException; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; -import org.springframework.boot.context.embedded.AbstractConfigurableEmbeddedServletContainer.CompressionProperties; +import org.springframework.boot.context.embedded.Compression; import org.springframework.boot.context.embedded.EmbeddedServletContainer; import org.springframework.boot.context.embedded.EmbeddedServletContainerException; import org.springframework.http.HttpHeaders; +import org.springframework.util.MimeType; +import org.springframework.util.MimeTypeUtils; import org.springframework.util.ReflectionUtils; import org.springframework.util.StringUtils; @@ -70,15 +69,14 @@ public class UndertowEmbeddedServletContainer implements EmbeddedServletContaine private final boolean autoStart; - private final CompressionProperties compression; + private final Compression compression; private Undertow undertow; private boolean started = false; public UndertowEmbeddedServletContainer(Builder builder, DeploymentManager manager, - String contextPath, int port, boolean autoStart, - CompressionProperties compression) { + String contextPath, int port, boolean autoStart, Compression compression) { this.builder = builder; this.manager = manager; this.contextPath = contextPath; @@ -113,33 +111,26 @@ public class UndertowEmbeddedServletContainer implements EmbeddedServletContaine } private HttpHandler getContextHandler(HttpHandler servletHandler) { - if (compression != null && compression.isEnabled()) { - ContentEncodingRepository encodingRepository = new ContentEncodingRepository(); - - List mimeTypes = compression.getMimeTypesList(); - Predicate[] mimePredicates = new Predicate[mimeTypes.size()]; - ResponseHeaderAttribute mimeHeader = new ResponseHeaderAttribute( - new HttpString(HttpHeaders.CONTENT_TYPE)); - for (int i = 0; i < mimeTypes.size(); i++) { - mimePredicates[i] = Predicates.equals(new ExchangeAttribute[] { - mimeHeader, new ConstantExchangeAttribute(mimeTypes.get(i)) }); - - } - - Predicate mimeAndSizePredicate = Predicates.and( - Predicates.maxContentSize(compression.getMinSize()), - Predicates.or(mimePredicates)); - - encodingRepository.addEncodingHandler("gzip", new GzipEncodingProvider(), 50, - mimeAndSizePredicate); - servletHandler = new EncodingHandler(encodingRepository) - .setNext(servletHandler); - } - + HttpHandler contextHandler = configurationCompressionIfNecessary(servletHandler); if (StringUtils.isEmpty(this.contextPath)) { + return contextHandler; + } + return Handlers.path().addPrefixPath(this.contextPath, contextHandler); + } + + private HttpHandler configurationCompressionIfNecessary(HttpHandler servletHandler) { + if (this.compression == null || !this.compression.getEnabled()) { return servletHandler; } - return Handlers.path().addPrefixPath(this.contextPath, servletHandler); + ContentEncodingRepository encodingRepository = new ContentEncodingRepository(); + + Predicate mimeAndSizePredicate = Predicates.and(Predicates + .maxContentSize(this.compression.getMinResponseSize()), Predicates + .or(new CompressibleMimeTypePredicate(this.compression.getMimeTypes()))); + + encodingRepository.addEncodingHandler("gzip", new GzipEncodingProvider(), 50, + mimeAndSizePredicate); + return new EncodingHandler(encodingRepository).setNext(servletHandler); } private String getPortsDescription() { @@ -250,4 +241,32 @@ public class UndertowEmbeddedServletContainer implements EmbeddedServletContaine } + private static class CompressibleMimeTypePredicate implements Predicate { + + private final List mimeTypes; + + CompressibleMimeTypePredicate(String[] mimeTypes) { + this.mimeTypes = new ArrayList(mimeTypes.length); + for (String mimeTypeString : mimeTypes) { + this.mimeTypes.add(MimeTypeUtils.parseMimeType(mimeTypeString)); + } + } + + @Override + public boolean resolve(HttpServerExchange value) { + String contentType = value.getResponseHeaders().getFirst( + HttpHeaders.CONTENT_TYPE); + if (contentType != null) { + for (MimeType mimeType : this.mimeTypes) { + if (mimeType.isCompatibleWith(MimeTypeUtils + .parseMimeType(contentType))) { + return true; + } + } + } + return false; + } + + } + } diff --git a/spring-boot/src/test/java/org/springframework/boot/context/embedded/AbstractEmbeddedServletContainerFactoryTests.java b/spring-boot/src/test/java/org/springframework/boot/context/embedded/AbstractEmbeddedServletContainerFactoryTests.java index 9ff051ace4..da9641e235 100644 --- a/spring-boot/src/test/java/org/springframework/boot/context/embedded/AbstractEmbeddedServletContainerFactoryTests.java +++ b/spring-boot/src/test/java/org/springframework/boot/context/embedded/AbstractEmbeddedServletContainerFactoryTests.java @@ -51,7 +51,6 @@ import org.junit.Test; import org.junit.rules.ExpectedException; import org.junit.rules.TemporaryFolder; import org.mockito.InOrder; -import org.springframework.boot.context.embedded.AbstractConfigurableEmbeddedServletContainer.CompressionProperties; import org.springframework.boot.context.embedded.Ssl.ClientAuth; import org.springframework.http.HttpMethod; import org.springframework.http.HttpStatus; @@ -541,10 +540,11 @@ public abstract class AbstractEmbeddedServletContainerFactoryTests { @Test public void noCompressionForMimeType() throws Exception { - assertFalse(internalTestCompression(10000, "text/html,text/xml,text/css")); + assertFalse(internalTestCompression(10000, new String[] { "text/html", + "text/xml", "text/css" })); } - protected String setupFactoryForCompression(int contentSize, String mimeTypes) + protected String setUpFactoryForCompression(int contentSize, String[] mimeTypes) throws Exception { char[] chars = new char[contentSize]; Arrays.fill(chars, 'F'); @@ -555,7 +555,7 @@ public abstract class AbstractEmbeddedServletContainerFactoryTests { FileCopyUtils.copy(testContent, new FileWriter(this.temporaryFolder.newFile("test.txt"))); factory.setDocumentRoot(this.temporaryFolder.getRoot()); - CompressionProperties compression = new CompressionProperties(); + Compression compression = new Compression(); compression.setEnabled(true); if (mimeTypes != null) { compression.setMimeTypes(mimeTypes); @@ -567,9 +567,9 @@ public abstract class AbstractEmbeddedServletContainerFactoryTests { return testContent; } - private boolean internalTestCompression(int contentSize, String mimeTypes) + private boolean internalTestCompression(int contentSize, String[] mimeTypes) throws Exception { - String testContent = setupFactoryForCompression(contentSize, mimeTypes); + String testContent = setUpFactoryForCompression(contentSize, mimeTypes); class TestGzipInputStreamFactory implements InputStreamFactory { @@ -577,11 +577,11 @@ public abstract class AbstractEmbeddedServletContainerFactoryTests { @Override public InputStream create(InputStream instream) throws IOException { - if (requested.get()) { + if (this.requested.get()) { throw new IllegalStateException( "On deflated InputStream already requested"); } - requested.set(true); + this.requested.set(true); return new GZIPInputStream(instream); } diff --git a/spring-boot/src/test/java/org/springframework/boot/context/embedded/jetty/JettyEmbeddedServletContainerFactoryTests.java b/spring-boot/src/test/java/org/springframework/boot/context/embedded/jetty/JettyEmbeddedServletContainerFactoryTests.java index 270dc0a22b..e17baee851 100644 --- a/spring-boot/src/test/java/org/springframework/boot/context/embedded/jetty/JettyEmbeddedServletContainerFactoryTests.java +++ b/spring-boot/src/test/java/org/springframework/boot/context/embedded/jetty/JettyEmbeddedServletContainerFactoryTests.java @@ -40,9 +40,9 @@ import org.junit.Test; import org.mockito.InOrder; import org.springframework.boot.context.embedded.AbstractEmbeddedServletContainerFactory; import org.springframework.boot.context.embedded.AbstractEmbeddedServletContainerFactoryTests; +import org.springframework.boot.context.embedded.Compression; import org.springframework.boot.context.embedded.ServletRegistrationBean; import org.springframework.boot.context.embedded.Ssl; -import org.springframework.boot.context.embedded.AbstractConfigurableEmbeddedServletContainer.CompressionProperties; import org.springframework.http.HttpHeaders; import static org.hamcrest.Matchers.equalTo; @@ -185,8 +185,8 @@ public class JettyEmbeddedServletContainerFactoryTests extends @Override @SuppressWarnings("serial") - // work-around for Jetty issue - https://bugs.eclipse.org/bugs/show_bug.cgi?id=470646 - protected String setupFactoryForCompression(final int contentSize, String mimeTypes) + // Workaround for Jetty issue - https://bugs.eclipse.org/bugs/show_bug.cgi?id=470646 + protected String setUpFactoryForCompression(final int contentSize, String[] mimeTypes) throws Exception { char[] chars = new char[contentSize]; Arrays.fill(chars, 'F'); @@ -194,7 +194,7 @@ public class JettyEmbeddedServletContainerFactoryTests extends AbstractEmbeddedServletContainerFactory factory = getFactory(); - CompressionProperties compression = new CompressionProperties(); + Compression compression = new Compression(); compression.setEnabled(true); if (mimeTypes != null) { compression.setMimeTypes(mimeTypes);