diff --git a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/ResourceProperties.java b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/ResourceProperties.java index 98a30adfdeb..ab79cd15184 100644 --- a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/ResourceProperties.java +++ b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/ResourceProperties.java @@ -16,8 +16,6 @@ package org.springframework.boot.autoconfigure.web; -import javax.annotation.PostConstruct; - import org.springframework.boot.context.properties.ConfigurationProperties; /** @@ -42,15 +40,6 @@ public class ResourceProperties { private final Chain chain = new Chain(); - - @PostConstruct - public void setUpDefaults() { - if (this.chain.enabled == null && (this.chain.strategy.content.enabled - || this.chain.strategy.fixed.enabled)) { - this.chain.enabled = true; - } - } - public Integer getCachePeriod() { return this.cachePeriod; } @@ -68,7 +57,7 @@ public class ResourceProperties { } public Chain getChain() { - return chain; + return this.chain; } /** @@ -77,8 +66,8 @@ public class ResourceProperties { public static class Chain { /** - * Enable the Spring Resource Handling chain. Disabled by default unless - * at least one strategy has been enabled. + * Enable the Spring Resource Handling chain. Disabled by default unless at least + * one strategy has been enabled. */ private Boolean enabled; @@ -95,7 +84,7 @@ public class ResourceProperties { private final Strategy strategy = new Strategy(); public Boolean getEnabled() { - return enabled; + return this.enabled; } public void setEnabled(boolean enabled) { @@ -103,7 +92,7 @@ public class ResourceProperties { } public boolean isCache() { - return cache; + return this.cache; } public void setCache(boolean cache) { @@ -111,16 +100,17 @@ public class ResourceProperties { } public Strategy getStrategy() { - return strategy; + return this.strategy; } public boolean isHtml5AppCache() { - return html5AppCache; + return this.html5AppCache; } public void setHtml5AppCache(boolean html5AppCache) { this.html5AppCache = html5AppCache; } + } /** @@ -133,12 +123,13 @@ public class ResourceProperties { private final Content content = new Content(); public Fixed getFixed() { - return fixed; + return this.fixed; } public Content getContent() { - return content; + return this.content; } + } /** @@ -154,10 +145,10 @@ public class ResourceProperties { /** * Comma-separated list of patterns to apply to the Version Strategy. */ - private String[] paths = new String[]{"/**"}; + private String[] paths = new String[] { "/**" }; public boolean isEnabled() { - return enabled; + return this.enabled; } public void setEnabled(boolean enabled) { @@ -165,12 +156,13 @@ public class ResourceProperties { } public String[] getPaths() { - return paths; + return this.paths; } public void setPaths(String[] paths) { this.paths = paths; } + } /** @@ -194,7 +186,7 @@ public class ResourceProperties { private String version; public boolean isEnabled() { - return enabled; + return this.enabled; } public void setEnabled(boolean enabled) { @@ -202,7 +194,7 @@ public class ResourceProperties { } public String[] getPaths() { - return paths; + return this.paths; } public void setPaths(String[] paths) { @@ -210,11 +202,13 @@ public class ResourceProperties { } public String getVersion() { - return version; + return this.version; } public void setVersion(String version) { this.version = version; } + } + } diff --git a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/WebMvcAutoConfiguration.java b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/WebMvcAutoConfiguration.java index 2b44dd0bccb..33a425059a2 100644 --- a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/WebMvcAutoConfiguration.java +++ b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/WebMvcAutoConfiguration.java @@ -38,6 +38,7 @@ import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication; +import org.springframework.boot.autoconfigure.web.ResourceProperties.Strategy; import org.springframework.boot.context.properties.EnableConfigurationProperties; import org.springframework.boot.context.web.OrderedHiddenHttpMethodFilter; import org.springframework.context.ResourceLoaderAware; @@ -55,7 +56,6 @@ import org.springframework.format.Formatter; import org.springframework.format.FormatterRegistry; import org.springframework.format.datetime.DateFormatter; import org.springframework.http.converter.HttpMessageConverter; -import org.springframework.util.ObjectUtils; import org.springframework.util.StringUtils; import org.springframework.validation.DefaultMessageCodesResolver; import org.springframework.validation.MessageCodesResolver; @@ -81,6 +81,7 @@ import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandl import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping; import org.springframework.web.servlet.resource.AppCacheManifestTransformer; import org.springframework.web.servlet.resource.ResourceHttpRequestHandler; +import org.springframework.web.servlet.resource.ResourceResolver; import org.springframework.web.servlet.resource.VersionResourceResolver; import org.springframework.web.servlet.view.BeanNameViewResolver; import org.springframework.web.servlet.view.ContentNegotiatingViewResolver; @@ -260,44 +261,53 @@ public class WebMvcAutoConfiguration { } Integer cachePeriod = this.resourceProperties.getCachePeriod(); if (!registry.hasMappingForPattern("/webjars/**")) { - ResourceHandlerRegistration registration = registry.addResourceHandler("/webjars/**") + registerResourceChain(registry.addResourceHandler("/webjars/**") .addResourceLocations("classpath:/META-INF/resources/webjars/") - .setCachePeriod(cachePeriod); - registerResourceChain(registration); + .setCachePeriod(cachePeriod)); } if (!registry.hasMappingForPattern("/**")) { - ResourceHandlerRegistration registration = registry.addResourceHandler("/**") + registerResourceChain(registry.addResourceHandler("/**") .addResourceLocations(RESOURCE_LOCATIONS) - .setCachePeriod(cachePeriod); - registerResourceChain(registration); + .setCachePeriod(cachePeriod)); } } private void registerResourceChain(ResourceHandlerRegistration registration) { - ResourceProperties.Chain chainProperties = this.resourceProperties.getChain(); - if (ObjectUtils.nullSafeEquals(chainProperties.getEnabled(), Boolean.TRUE)) { - ResourceChainRegistration chain = registration.resourceChain(chainProperties.isCache()); - boolean hasFixedVersionConfigured = chainProperties.getStrategy().getFixed().isEnabled(); - boolean hasContentVersionConfigured = chainProperties.getStrategy().getContent().isEnabled(); - if (hasFixedVersionConfigured || hasContentVersionConfigured) { - VersionResourceResolver versionResourceResolver = new VersionResourceResolver(); - if (hasFixedVersionConfigured) { - versionResourceResolver.addFixedVersionStrategy( - chainProperties.getStrategy().getFixed().getVersion(), - chainProperties.getStrategy().getFixed().getPaths()); - } - if (hasContentVersionConfigured) { - versionResourceResolver. - addContentVersionStrategy(chainProperties.getStrategy().getContent().getPaths()); - } - chain.addResolver(versionResourceResolver); - } - if (chainProperties.isHtml5AppCache()) { - chain.addTransformer(new AppCacheManifestTransformer()); - } + ResourceProperties.Chain properties = this.resourceProperties.getChain(); + if (Boolean.TRUE.equals(properties.getEnabled()) + || properties.getStrategy().getFixed().isEnabled() + || properties.getStrategy().getContent().isEnabled()) { + configureResourceChain(properties, + registration.resourceChain(properties.isCache())); } } + private void configureResourceChain(ResourceProperties.Chain properties, + ResourceChainRegistration chain) { + Strategy strategy = properties.getStrategy(); + if (strategy.getFixed().isEnabled() || strategy.getContent().isEnabled()) { + chain.addResolver(getVersionResourceResolver(strategy)); + } + if (properties.isHtml5AppCache()) { + chain.addTransformer(new AppCacheManifestTransformer()); + } + } + + private ResourceResolver getVersionResourceResolver( + ResourceProperties.Strategy properties) { + VersionResourceResolver resolver = new VersionResourceResolver(); + if (properties.getFixed().isEnabled()) { + String version = properties.getFixed().getVersion(); + String[] paths = properties.getFixed().getPaths(); + resolver.addFixedVersionStrategy(version, paths); + } + if (properties.getContent().isEnabled()) { + String[] paths = properties.getContent().getPaths(); + resolver.addContentVersionStrategy(paths); + } + return resolver; + } + @Override public void addViewControllers(ViewControllerRegistry registry) { addStaticIndexHtmlViewControllers(registry); diff --git a/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/web/WebMvcAutoConfigurationTests.java b/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/web/WebMvcAutoConfigurationTests.java index 55735958350..20728edb223 100644 --- a/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/web/WebMvcAutoConfigurationTests.java +++ b/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/web/WebMvcAutoConfigurationTests.java @@ -28,6 +28,7 @@ import java.util.Map; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; +import org.hamcrest.Matcher; import org.joda.time.DateTime; import org.junit.After; import org.junit.Rule; @@ -170,15 +171,16 @@ public class WebMvcAutoConfigurationTests { @Test public void resourceHandlerChainEnabled() throws Exception { load("spring.resources.chain.enabled:true"); - assertThat(getResourceResolvers("/webjars/**").size(), equalTo(2)); assertThat(getResourceTransformers("/webjars/**").size(), equalTo(1)); assertThat(getResourceResolvers("/**").size(), equalTo(2)); assertThat(getResourceTransformers("/**").size(), equalTo(1)); - - assertThat(getResourceResolvers("/**"), contains(instanceOf(CachingResourceResolver.class), - instanceOf(PathResourceResolver.class))); - assertThat(getResourceTransformers("/**"), contains(instanceOf(CachingResourceTransformer.class))); + assertThat( + getResourceResolvers("/**"), + containsInstances(CachingResourceResolver.class, + PathResourceResolver.class)); + assertThat(getResourceTransformers("/**"), + contains(instanceOf(CachingResourceTransformer.class))); } @Test @@ -186,38 +188,44 @@ public class WebMvcAutoConfigurationTests { load("spring.resources.chain.strategy.fixed.enabled:true", "spring.resources.chain.strategy.fixed.version:test", "spring.resources.chain.strategy.fixed.paths:/**/*.js"); - assertThat(getResourceResolvers("/webjars/**").size(), equalTo(3)); assertThat(getResourceTransformers("/webjars/**").size(), equalTo(2)); assertThat(getResourceResolvers("/**").size(), equalTo(3)); assertThat(getResourceTransformers("/**").size(), equalTo(2)); - - assertThat(getResourceResolvers("/**"), contains(instanceOf(CachingResourceResolver.class), - instanceOf(VersionResourceResolver.class), - instanceOf(PathResourceResolver.class))); - assertThat(getResourceTransformers("/**"), contains(instanceOf(CachingResourceTransformer.class), - instanceOf(CssLinkResourceTransformer.class))); - VersionResourceResolver resolver = (VersionResourceResolver) getResourceResolvers("/**").get(1); - assertThat(resolver.getStrategyMap().get("/**/*.js"), instanceOf(FixedVersionStrategy.class)); + assertThat( + getResourceResolvers("/**"), + containsInstances(CachingResourceResolver.class, + VersionResourceResolver.class, PathResourceResolver.class)); + assertThat( + getResourceTransformers("/**"), + containsInstances(CachingResourceTransformer.class, + CssLinkResourceTransformer.class)); + VersionResourceResolver resolver = (VersionResourceResolver) getResourceResolvers( + "/**").get(1); + assertThat(resolver.getStrategyMap().get("/**/*.js"), + instanceOf(FixedVersionStrategy.class)); } @Test public void resourceHandlerContentStrategyEnabled() throws Exception { load("spring.resources.chain.strategy.content.enabled:true", "spring.resources.chain.strategy.content.paths:/**,/*.png"); - assertThat(getResourceResolvers("/webjars/**").size(), equalTo(3)); assertThat(getResourceTransformers("/webjars/**").size(), equalTo(2)); assertThat(getResourceResolvers("/**").size(), equalTo(3)); assertThat(getResourceTransformers("/**").size(), equalTo(2)); - - assertThat(getResourceResolvers("/**"), contains(instanceOf(CachingResourceResolver.class), - instanceOf(VersionResourceResolver.class), - instanceOf(PathResourceResolver.class))); - assertThat(getResourceTransformers("/**"), contains(instanceOf(CachingResourceTransformer.class), - instanceOf(CssLinkResourceTransformer.class))); - VersionResourceResolver resolver = (VersionResourceResolver) getResourceResolvers("/**").get(1); - assertThat(resolver.getStrategyMap().get("/*.png"), instanceOf(ContentVersionStrategy.class)); + assertThat( + getResourceResolvers("/**"), + containsInstances(CachingResourceResolver.class, + VersionResourceResolver.class, PathResourceResolver.class)); + assertThat( + getResourceTransformers("/**"), + containsInstances(CachingResourceTransformer.class, + CssLinkResourceTransformer.class)); + VersionResourceResolver resolver = (VersionResourceResolver) getResourceResolvers( + "/**").get(1); + assertThat(resolver.getStrategyMap().get("/*.png"), + instanceOf(ContentVersionStrategy.class)); } @Test @@ -229,20 +237,24 @@ public class WebMvcAutoConfigurationTests { "spring.resources.chain.strategy.fixed.version:test", "spring.resources.chain.strategy.fixed.paths:/**/*.js", "spring.resources.chain.html5AppCache:true"); - assertThat(getResourceResolvers("/webjars/**").size(), equalTo(2)); assertThat(getResourceTransformers("/webjars/**").size(), equalTo(2)); assertThat(getResourceResolvers("/**").size(), equalTo(2)); assertThat(getResourceTransformers("/**").size(), equalTo(2)); - - assertThat(getResourceResolvers("/**"), contains( - instanceOf(VersionResourceResolver.class), instanceOf(PathResourceResolver.class))); - assertThat(getResourceTransformers("/**"), contains(instanceOf(CssLinkResourceTransformer.class), - instanceOf(AppCacheManifestTransformer.class))); - - VersionResourceResolver resolver = (VersionResourceResolver) getResourceResolvers("/**").get(0); - assertThat(resolver.getStrategyMap().get("/*.png"), instanceOf(ContentVersionStrategy.class)); - assertThat(resolver.getStrategyMap().get("/**/*.js"), instanceOf(FixedVersionStrategy.class)); + assertThat( + getResourceResolvers("/**"), + containsInstances(VersionResourceResolver.class, + PathResourceResolver.class)); + assertThat( + getResourceTransformers("/**"), + containsInstances(CssLinkResourceTransformer.class, + AppCacheManifestTransformer.class)); + VersionResourceResolver resolver = (VersionResourceResolver) getResourceResolvers( + "/**").get(0); + assertThat(resolver.getStrategyMap().get("/*.png"), + instanceOf(ContentVersionStrategy.class)); + assertThat(resolver.getStrategyMap().get("/**/*.js"), + instanceOf(FixedVersionStrategy.class)); } @Test @@ -315,14 +327,18 @@ public class WebMvcAutoConfigurationTests { } protected List getResourceResolvers(String mapping) { - SimpleUrlHandlerMapping handler = (SimpleUrlHandlerMapping) this.context.getBean("resourceHandlerMapping"); - ResourceHttpRequestHandler resourceHandler = (ResourceHttpRequestHandler) handler.getHandlerMap().get(mapping); + SimpleUrlHandlerMapping handler = (SimpleUrlHandlerMapping) this.context + .getBean("resourceHandlerMapping"); + ResourceHttpRequestHandler resourceHandler = (ResourceHttpRequestHandler) handler + .getHandlerMap().get(mapping); return resourceHandler.getResourceResolvers(); } protected List getResourceTransformers(String mapping) { - SimpleUrlHandlerMapping handler = (SimpleUrlHandlerMapping) this.context.getBean("resourceHandlerMapping"); - ResourceHttpRequestHandler resourceHandler = (ResourceHttpRequestHandler) handler.getHandlerMap().get(mapping); + SimpleUrlHandlerMapping handler = (SimpleUrlHandlerMapping) this.context + .getBean("resourceHandlerMapping"); + ResourceHttpRequestHandler resourceHandler = (ResourceHttpRequestHandler) handler + .getHandlerMap().get(mapping); return resourceHandler.getResourceTransformers(); } @@ -440,6 +456,15 @@ public class WebMvcAutoConfigurationTests { this.context.refresh(); } + @SuppressWarnings({ "unchecked", "rawtypes" }) + private Matcher containsInstances(Class... types) { + Matcher[] instances = new Matcher[types.length]; + for (int i = 0; i < instances.length; i++) { + instances[i] = instanceOf(types[i]); + } + return contains(instances); + } + private void load(String... environment) { load(null, environment); } diff --git a/spring-boot-docs/src/main/asciidoc/production-ready-features.adoc b/spring-boot-docs/src/main/asciidoc/production-ready-features.adoc index 1ea764a53ec..fdd35211fec 100644 --- a/spring-boot-docs/src/main/asciidoc/production-ready-features.adoc +++ b/spring-boot-docs/src/main/asciidoc/production-ready-features.adoc @@ -157,7 +157,7 @@ For example, the following will disable _all_ endpoints except for `info`: Health information can be used to check the status of your running application. It is often used by monitoring software to alert someone if a production system goes down. The default information exposed by the `health` endpoint depends on how it is accessed. -For an unauthenticated connection in a secure application a simple '`status`' message is +For an unauthenticated connection in a secure application a simple '`status`' message is returned, and for an authenticated connection additional details are also displayed (see <> for HTTP details). @@ -507,7 +507,7 @@ If you don't want to expose endpoints over HTTP you can set the management port [[production-ready-health-access-restrictions]] === HTTP health endpoint access restrictions The information exposed by the health endpoint varies depending on whether or not it's -accessed anonymously, and whether or not the enclosing application is secure. +accessed anonymously, and whether or not the enclosing application is secure. By default, when accessed anonymously in a secure application, any details about the server's health are hidden and the endpoint will simply indicate whether or not the server is up or down. Furthermore, when accessed anonymously, the response is cached for a @@ -515,24 +515,37 @@ configurable period to prevent the endpoint being used in a denial of service at The `endpoints.health.time-to-live` property is used to configure the caching period in milliseconds. It defaults to 1000, i.e. one second. -The above-described restrictions can be enhanced, thereby allowing only authenticated users full -access to the health endpoint in a secure application. To do so, set `endpoints.health.sensitive` to `true`. -Here's a summary of behaviour (with default `sensitive` flag value "false" indicated in bold): +The above-described restrictions can be enhanced, thereby allowing only authenticated +users full access to the health endpoint in a secure application. To do so, set +`endpoints.health.sensitive` to `true`. Here's a summary of behavior (with default +`sensitive` flag value "`false`" indicated in bold): |==== -|Secure | Sensitive | Unauthenticated behaviour | Authenticated behaviour +|Secure |Sensitive |Unauthenticated |Authenticated -| false | **false** | Full content | Full content +|false +|**false** +|Full content +|Full content -| false | true | Status only | Full content +|false +|true +|Status only +|Full content -| true | **false** | Status only | Full content - -| true | true | No content | Full content +|true +|**false** +|Status only +|Full content +|true +|true +|No content +|Full content |==== + [[production-ready-jmx]] == Monitoring and management over JMX Java Management Extensions (JMX) provide a standard mechanism to monitor and manage diff --git a/spring-boot-docs/src/main/asciidoc/spring-boot-features.adoc b/spring-boot-docs/src/main/asciidoc/spring-boot-features.adoc index e53095a3167..9e3dc266b6d 100644 --- a/spring-boot-docs/src/main/asciidoc/spring-boot-features.adoc +++ b/spring-boot-docs/src/main/asciidoc/spring-boot-features.adoc @@ -1205,7 +1205,8 @@ supported right now, but can be with custom template macros/helpers and the use When loading resources dynamically with, for example, a JavaScript module loader, renaming files is not an option. That's why other strategies are also supported and can be combined. -A "fixed" strategy will add a static version string in the URL, without changing the file name: +A "fixed" strategy will add a static version string in the URL, without changing the file +name: [source,properties,indent=0,subs="verbatim,quotes,attributes"] ---- @@ -1231,6 +1232,7 @@ and in Spring Framework's {spring-reference}/#mvc-config-static-resources[refere ==== + [[boot-features-spring-mvc-template-engines]] ==== Template engines As well as REST web services, you can also use Spring MVC to serve dynamic HTML content. 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 d10349a2c73..879fe2db0dd 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 @@ -68,14 +68,10 @@ public class SampleJetty8ApplicationTests { 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 { diff --git a/spring-boot-samples/spring-boot-sample-jetty93/src/test/java/sample/jetty93/SampleJetty93ApplicationTests.java b/spring-boot-samples/spring-boot-sample-jetty93/src/test/java/sample/jetty93/SampleJetty93ApplicationTests.java index 5d12022711a..cd4eca87645 100644 --- a/spring-boot-samples/spring-boot-sample-jetty93/src/test/java/sample/jetty93/SampleJetty93ApplicationTests.java +++ b/spring-boot-samples/spring-boot-sample-jetty93/src/test/java/sample/jetty93/SampleJetty93ApplicationTests.java @@ -68,14 +68,10 @@ public class SampleJetty93ApplicationTests { 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 { 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 96f38455a46..f4584ec46c5 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 @@ -66,14 +66,10 @@ public class SampleTomcatApplicationTests { 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 { 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 157e2773692..cc9d15196cb 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 @@ -70,14 +70,10 @@ public class SampleUndertowApplicationTests { 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 { 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 f1ed20b9cc7..642a6ad0756 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 @@ -285,6 +285,7 @@ public abstract class AbstractConfigurableEmbeddedServletContainer implements return this.compression; } + @Override public void setCompression(Compression compression) { this.compression = compression; } 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 index 43985a4ffa4..10f840ac9ed 100644 --- 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 @@ -65,4 +65,4 @@ public class Compression { this.minResponseSize = minSize; } -} \ No newline at end of file +} 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 930c78f23de..8a789e365b1 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 @@ -155,14 +155,14 @@ public class JettyEmbeddedServletContainerFactory extends } private HandlerWrapper createGzipHandler() { - if (ClassUtils.isPresent(GZIP_HANDLER_JETTY_9_2, getClass().getClassLoader())) { + ClassLoader classLoader = getClass().getClassLoader(); + if (ClassUtils.isPresent(GZIP_HANDLER_JETTY_9_2, classLoader)) { return new Jetty92GzipHandlerFactory().createGzipHandler(getCompression()); } - else if (ClassUtils.isPresent(GZIP_HANDLER_JETTY_8, getClass().getClassLoader())) { + 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())) { + if (ClassUtils.isPresent(GZIP_HANDLER_JETTY_9_3, getClass().getClassLoader())) { return new Jetty93GzipHandlerFactory().createGzipHandler(getCompression()); } throw new IllegalStateException( @@ -579,19 +579,17 @@ public class JettyEmbeddedServletContainerFactory extends @Override public HandlerWrapper createGzipHandler(Compression compression) { try { - Class gzipHandlerClass = ClassUtils.forName(GZIP_HANDLER_JETTY_8, + Class handlerClass = 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, + HandlerWrapper handler = (HandlerWrapper) handlerClass.newInstance(); + ReflectionUtils.findMethod(handlerClass, "setMinGzipSize", int.class) + .invoke(handler, compression.getMinResponseSize()); + ReflectionUtils.findMethod(handlerClass, "setMimeTypes", Set.class) + .invoke(handler, new HashSet(Arrays.asList(compression .getMimeTypes()))); - return gzipHandler; + return handler; } - catch (Exception ex) { throw new RuntimeException("Failed to configure Jetty 8 gzip handler", ex); } @@ -608,7 +606,6 @@ public class JettyEmbeddedServletContainerFactory extends gzipHandler.setMimeTypes(new HashSet(Arrays.asList(compression .getMimeTypes()))); return gzipHandler; - } } @@ -618,18 +615,16 @@ public class JettyEmbeddedServletContainerFactory extends @Override public HandlerWrapper createGzipHandler(Compression compression) { try { - Class gzipHandlerClass = ClassUtils.forName(GZIP_HANDLER_JETTY_9_3, + Class handlerClass = 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, + HandlerWrapper handler = (HandlerWrapper) handlerClass.newInstance(); + ReflectionUtils.findMethod(handlerClass, "setMinGzipSize", int.class) + .invoke(handler, compression.getMinResponseSize()); + ReflectionUtils.findMethod(handlerClass, "setIncludedMimeTypes", + String[].class).invoke(handler, new Object[] { compression.getMimeTypes() }); - return gzipHandler; + return handler; } - catch (Exception ex) { throw new RuntimeException("Failed to configure Jetty 9.3 gzip handler", ex); @@ -637,4 +632,5 @@ public class JettyEmbeddedServletContainerFactory extends } } + } 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 9e256d5181b..4c409fc0a9c 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 @@ -51,6 +51,7 @@ 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.Compression; import org.springframework.boot.context.embedded.EmbeddedServletContainer; import org.springframework.boot.context.embedded.EmbeddedServletContainerException; import org.springframework.boot.context.embedded.EmbeddedServletContainerFactory; @@ -233,10 +234,7 @@ public class TomcatEmbeddedServletContainerFactory extends int port = (getPort() >= 0 ? getPort() : 0); connector.setPort(port); if (connector.getProtocolHandler() instanceof AbstractProtocol) { - if (getAddress() != null) { - ((AbstractProtocol) connector.getProtocolHandler()) - .setAddress(getAddress()); - } + customizeProtocol((AbstractProtocol) connector.getProtocolHandler()); } if (getUriEncoding() != null) { connector.setURIEncoding(getUriEncoding()); @@ -247,33 +245,44 @@ public class TomcatEmbeddedServletContainerFactory extends connector.setProperty("bindOnInit", "false"); if (getSsl() != null && getSsl().isEnabled()) { - Assert.state( - connector.getProtocolHandler() instanceof AbstractHttp11JsseProtocol, - "To use SSL, the connector's protocol handler must be an " - + "AbstractHttp11JsseProtocol subclass"); - configureSsl((AbstractHttp11JsseProtocol) connector.getProtocolHandler(), - getSsl()); - connector.setScheme("https"); - connector.setSecure(true); + customizeSsl(connector); } - 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().getMinResponseSize()); - protocol.setCompressableMimeTypes(StringUtils - .arrayToCommaDelimitedString(getCompression().getMimeTypes())); - } + customizeCompression(connector); } - for (TomcatConnectorCustomizer customizer : this.tomcatConnectorCustomizers) { customizer.customize(connector); } } + private void customizeProtocol(AbstractProtocol protocol) { + if (getAddress() != null) { + protocol.setAddress(getAddress()); + } + } + + private void customizeSsl(Connector connector) { + ProtocolHandler handler = connector.getProtocolHandler(); + Assert.state(handler instanceof AbstractHttp11JsseProtocol, + "To use SSL, the connector's protocol handler must be an " + + "AbstractHttp11JsseProtocol subclass"); + configureSsl((AbstractHttp11JsseProtocol) handler, getSsl()); + connector.setScheme("https"); + connector.setSecure(true); + } + + private void customizeCompression(Connector connector) { + ProtocolHandler handler = connector.getProtocolHandler(); + if (handler instanceof AbstractHttp11Protocol) { + AbstractHttp11Protocol protocol = (AbstractHttp11Protocol) handler; + Compression compression = getCompression(); + protocol.setCompression("on"); + protocol.setCompressionMinSize(compression.getMinResponseSize()); + protocol.setCompressableMimeTypes(StringUtils + .arrayToCommaDelimitedString(compression.getMimeTypes())); + } + } + /** * Configure Tomcat's {@link AbstractHttp11JsseProtocol} for SSL. * @param protocol the protocol 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 b9e1a1c4091..682db2ab07e 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 @@ -123,11 +123,9 @@ public class UndertowEmbeddedServletContainer implements EmbeddedServletContaine return 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); 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 da9641e2359..43e396c0b17 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 @@ -27,6 +27,7 @@ import java.nio.charset.Charset; import java.security.KeyStore; import java.util.Arrays; import java.util.Date; +import java.util.Map; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicBoolean; import java.util.zip.GZIPInputStream; @@ -530,18 +531,31 @@ public abstract class AbstractEmbeddedServletContainerFactoryTests { @Test public void compression() throws Exception { - assertTrue(internalTestCompression(10000, null)); + assertTrue(doTestCompression(10000, null)); } @Test public void noCompressionForSmallResponse() throws Exception { - assertFalse(internalTestCompression(100, null)); + assertFalse(doTestCompression(100, null)); } @Test public void noCompressionForMimeType() throws Exception { - assertFalse(internalTestCompression(10000, new String[] { "text/html", - "text/xml", "text/css" })); + String[] mimeTypes = new String[] { "text/html", "text/xml", "text/css" }; + assertFalse(doTestCompression(10000, mimeTypes)); + } + + private boolean doTestCompression(int contentSize, String[] mimeTypes) + throws Exception { + String testContent = setUpFactoryForCompression(contentSize, mimeTypes); + TestGzipInputStreamFactory inputStreamFactory = new TestGzipInputStreamFactory(); + Map contentDecoderMap = singletonMap("gzip", + (InputStreamFactory) inputStreamFactory); + String response = getResponse(getLocalUrl("/test.txt"), + new HttpComponentsClientHttpRequestFactory(HttpClientBuilder.create() + .setContentDecoderRegistry(contentDecoderMap).build())); + assertThat(response, equalTo(testContent)); + return inputStreamFactory.wasCompressionUsed(); } protected String setUpFactoryForCompression(int contentSize, String[] mimeTypes) @@ -549,9 +563,7 @@ public abstract class AbstractEmbeddedServletContainerFactoryTests { 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()); @@ -561,47 +573,11 @@ public abstract class AbstractEmbeddedServletContainerFactoryTests { 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 (this.requested.get()) { - throw new IllegalStateException( - "On deflated InputStream already requested"); - } - this.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", @@ -678,6 +654,26 @@ public abstract class AbstractEmbeddedServletContainerFactoryTests { return bean; } + private class TestGzipInputStreamFactory implements InputStreamFactory { + + private final AtomicBoolean requested = new AtomicBoolean(false); + + @Override + public InputStream create(InputStream instream) throws IOException { + if (this.requested.get()) { + throw new IllegalStateException( + "On deflated InputStream already requested"); + } + this.requested.set(true); + return new GZIPInputStream(instream); + } + + public boolean wasCompressionUsed() { + return this.requested.get(); + } + + } + @SuppressWarnings("serial") private static class InitCountingServlet extends GenericServlet { @@ -696,6 +692,7 @@ public abstract class AbstractEmbeddedServletContainerFactoryTests { public int getInitCount() { return this.initCount; } + }; } 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 e17baee8512..97b00f90d13 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 @@ -191,16 +191,13 @@ public class JettyEmbeddedServletContainerFactoryTests extends char[] chars = new char[contentSize]; Arrays.fill(chars, 'F'); final String testContent = new String(chars); - AbstractEmbeddedServletContainerFactory factory = getFactory(); - Compression compression = new Compression(); compression.setEnabled(true); if (mimeTypes != null) { compression.setMimeTypes(mimeTypes); } factory.setCompression(compression); - this.container = factory.getEmbeddedServletContainer(new ServletRegistrationBean( new HttpServlet() { @Override