This commit is contained in:
Phillip Webb 2015-06-29 16:48:59 -07:00
parent cc3aea2b69
commit d213cc05d5
16 changed files with 239 additions and 213 deletions

View File

@ -16,8 +16,6 @@
package org.springframework.boot.autoconfigure.web; package org.springframework.boot.autoconfigure.web;
import javax.annotation.PostConstruct;
import org.springframework.boot.context.properties.ConfigurationProperties; import org.springframework.boot.context.properties.ConfigurationProperties;
/** /**
@ -42,15 +40,6 @@ public class ResourceProperties {
private final Chain chain = new Chain(); 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() { public Integer getCachePeriod() {
return this.cachePeriod; return this.cachePeriod;
} }
@ -68,7 +57,7 @@ public class ResourceProperties {
} }
public Chain getChain() { public Chain getChain() {
return chain; return this.chain;
} }
/** /**
@ -77,8 +66,8 @@ public class ResourceProperties {
public static class Chain { public static class Chain {
/** /**
* Enable the Spring Resource Handling chain. Disabled by default unless * Enable the Spring Resource Handling chain. Disabled by default unless at least
* at least one strategy has been enabled. * one strategy has been enabled.
*/ */
private Boolean enabled; private Boolean enabled;
@ -95,7 +84,7 @@ public class ResourceProperties {
private final Strategy strategy = new Strategy(); private final Strategy strategy = new Strategy();
public Boolean getEnabled() { public Boolean getEnabled() {
return enabled; return this.enabled;
} }
public void setEnabled(boolean enabled) { public void setEnabled(boolean enabled) {
@ -103,7 +92,7 @@ public class ResourceProperties {
} }
public boolean isCache() { public boolean isCache() {
return cache; return this.cache;
} }
public void setCache(boolean cache) { public void setCache(boolean cache) {
@ -111,16 +100,17 @@ public class ResourceProperties {
} }
public Strategy getStrategy() { public Strategy getStrategy() {
return strategy; return this.strategy;
} }
public boolean isHtml5AppCache() { public boolean isHtml5AppCache() {
return html5AppCache; return this.html5AppCache;
} }
public void setHtml5AppCache(boolean html5AppCache) { public void setHtml5AppCache(boolean html5AppCache) {
this.html5AppCache = html5AppCache; this.html5AppCache = html5AppCache;
} }
} }
/** /**
@ -133,12 +123,13 @@ public class ResourceProperties {
private final Content content = new Content(); private final Content content = new Content();
public Fixed getFixed() { public Fixed getFixed() {
return fixed; return this.fixed;
} }
public Content getContent() { 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. * Comma-separated list of patterns to apply to the Version Strategy.
*/ */
private String[] paths = new String[]{"/**"}; private String[] paths = new String[] { "/**" };
public boolean isEnabled() { public boolean isEnabled() {
return enabled; return this.enabled;
} }
public void setEnabled(boolean enabled) { public void setEnabled(boolean enabled) {
@ -165,12 +156,13 @@ public class ResourceProperties {
} }
public String[] getPaths() { public String[] getPaths() {
return paths; return this.paths;
} }
public void setPaths(String[] paths) { public void setPaths(String[] paths) {
this.paths = paths; this.paths = paths;
} }
} }
/** /**
@ -194,7 +186,7 @@ public class ResourceProperties {
private String version; private String version;
public boolean isEnabled() { public boolean isEnabled() {
return enabled; return this.enabled;
} }
public void setEnabled(boolean enabled) { public void setEnabled(boolean enabled) {
@ -202,7 +194,7 @@ public class ResourceProperties {
} }
public String[] getPaths() { public String[] getPaths() {
return paths; return this.paths;
} }
public void setPaths(String[] paths) { public void setPaths(String[] paths) {
@ -210,11 +202,13 @@ public class ResourceProperties {
} }
public String getVersion() { public String getVersion() {
return version; return this.version;
} }
public void setVersion(String version) { public void setVersion(String version) {
this.version = version; this.version = version;
} }
} }
} }

View File

@ -38,6 +38,7 @@ import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication; 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.properties.EnableConfigurationProperties;
import org.springframework.boot.context.web.OrderedHiddenHttpMethodFilter; import org.springframework.boot.context.web.OrderedHiddenHttpMethodFilter;
import org.springframework.context.ResourceLoaderAware; import org.springframework.context.ResourceLoaderAware;
@ -55,7 +56,6 @@ import org.springframework.format.Formatter;
import org.springframework.format.FormatterRegistry; import org.springframework.format.FormatterRegistry;
import org.springframework.format.datetime.DateFormatter; import org.springframework.format.datetime.DateFormatter;
import org.springframework.http.converter.HttpMessageConverter; import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.util.ObjectUtils;
import org.springframework.util.StringUtils; import org.springframework.util.StringUtils;
import org.springframework.validation.DefaultMessageCodesResolver; import org.springframework.validation.DefaultMessageCodesResolver;
import org.springframework.validation.MessageCodesResolver; 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.mvc.method.annotation.RequestMappingHandlerMapping;
import org.springframework.web.servlet.resource.AppCacheManifestTransformer; import org.springframework.web.servlet.resource.AppCacheManifestTransformer;
import org.springframework.web.servlet.resource.ResourceHttpRequestHandler; 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.resource.VersionResourceResolver;
import org.springframework.web.servlet.view.BeanNameViewResolver; import org.springframework.web.servlet.view.BeanNameViewResolver;
import org.springframework.web.servlet.view.ContentNegotiatingViewResolver; import org.springframework.web.servlet.view.ContentNegotiatingViewResolver;
@ -260,42 +261,51 @@ public class WebMvcAutoConfiguration {
} }
Integer cachePeriod = this.resourceProperties.getCachePeriod(); Integer cachePeriod = this.resourceProperties.getCachePeriod();
if (!registry.hasMappingForPattern("/webjars/**")) { if (!registry.hasMappingForPattern("/webjars/**")) {
ResourceHandlerRegistration registration = registry.addResourceHandler("/webjars/**") registerResourceChain(registry.addResourceHandler("/webjars/**")
.addResourceLocations("classpath:/META-INF/resources/webjars/") .addResourceLocations("classpath:/META-INF/resources/webjars/")
.setCachePeriod(cachePeriod); .setCachePeriod(cachePeriod));
registerResourceChain(registration);
} }
if (!registry.hasMappingForPattern("/**")) { if (!registry.hasMappingForPattern("/**")) {
ResourceHandlerRegistration registration = registry.addResourceHandler("/**") registerResourceChain(registry.addResourceHandler("/**")
.addResourceLocations(RESOURCE_LOCATIONS) .addResourceLocations(RESOURCE_LOCATIONS)
.setCachePeriod(cachePeriod); .setCachePeriod(cachePeriod));
registerResourceChain(registration);
} }
} }
private void registerResourceChain(ResourceHandlerRegistration registration) { private void registerResourceChain(ResourceHandlerRegistration registration) {
ResourceProperties.Chain chainProperties = this.resourceProperties.getChain(); ResourceProperties.Chain properties = this.resourceProperties.getChain();
if (ObjectUtils.nullSafeEquals(chainProperties.getEnabled(), Boolean.TRUE)) { if (Boolean.TRUE.equals(properties.getEnabled())
ResourceChainRegistration chain = registration.resourceChain(chainProperties.isCache()); || properties.getStrategy().getFixed().isEnabled()
boolean hasFixedVersionConfigured = chainProperties.getStrategy().getFixed().isEnabled(); || properties.getStrategy().getContent().isEnabled()) {
boolean hasContentVersionConfigured = chainProperties.getStrategy().getContent().isEnabled(); configureResourceChain(properties,
if (hasFixedVersionConfigured || hasContentVersionConfigured) { registration.resourceChain(properties.isCache()));
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);
private void configureResourceChain(ResourceProperties.Chain properties,
ResourceChainRegistration chain) {
Strategy strategy = properties.getStrategy();
if (strategy.getFixed().isEnabled() || strategy.getContent().isEnabled()) {
chain.addResolver(getVersionResourceResolver(strategy));
} }
if (chainProperties.isHtml5AppCache()) { if (properties.isHtml5AppCache()) {
chain.addTransformer(new AppCacheManifestTransformer()); 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 @Override

View File

@ -28,6 +28,7 @@ import java.util.Map;
import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse; import javax.servlet.http.HttpServletResponse;
import org.hamcrest.Matcher;
import org.joda.time.DateTime; import org.joda.time.DateTime;
import org.junit.After; import org.junit.After;
import org.junit.Rule; import org.junit.Rule;
@ -170,15 +171,16 @@ public class WebMvcAutoConfigurationTests {
@Test @Test
public void resourceHandlerChainEnabled() throws Exception { public void resourceHandlerChainEnabled() throws Exception {
load("spring.resources.chain.enabled:true"); load("spring.resources.chain.enabled:true");
assertThat(getResourceResolvers("/webjars/**").size(), equalTo(2)); assertThat(getResourceResolvers("/webjars/**").size(), equalTo(2));
assertThat(getResourceTransformers("/webjars/**").size(), equalTo(1)); assertThat(getResourceTransformers("/webjars/**").size(), equalTo(1));
assertThat(getResourceResolvers("/**").size(), equalTo(2)); assertThat(getResourceResolvers("/**").size(), equalTo(2));
assertThat(getResourceTransformers("/**").size(), equalTo(1)); assertThat(getResourceTransformers("/**").size(), equalTo(1));
assertThat(
assertThat(getResourceResolvers("/**"), contains(instanceOf(CachingResourceResolver.class), getResourceResolvers("/**"),
instanceOf(PathResourceResolver.class))); containsInstances(CachingResourceResolver.class,
assertThat(getResourceTransformers("/**"), contains(instanceOf(CachingResourceTransformer.class))); PathResourceResolver.class));
assertThat(getResourceTransformers("/**"),
contains(instanceOf(CachingResourceTransformer.class)));
} }
@Test @Test
@ -186,38 +188,44 @@ public class WebMvcAutoConfigurationTests {
load("spring.resources.chain.strategy.fixed.enabled:true", load("spring.resources.chain.strategy.fixed.enabled:true",
"spring.resources.chain.strategy.fixed.version:test", "spring.resources.chain.strategy.fixed.version:test",
"spring.resources.chain.strategy.fixed.paths:/**/*.js"); "spring.resources.chain.strategy.fixed.paths:/**/*.js");
assertThat(getResourceResolvers("/webjars/**").size(), equalTo(3)); assertThat(getResourceResolvers("/webjars/**").size(), equalTo(3));
assertThat(getResourceTransformers("/webjars/**").size(), equalTo(2)); assertThat(getResourceTransformers("/webjars/**").size(), equalTo(2));
assertThat(getResourceResolvers("/**").size(), equalTo(3)); assertThat(getResourceResolvers("/**").size(), equalTo(3));
assertThat(getResourceTransformers("/**").size(), equalTo(2)); assertThat(getResourceTransformers("/**").size(), equalTo(2));
assertThat(
assertThat(getResourceResolvers("/**"), contains(instanceOf(CachingResourceResolver.class), getResourceResolvers("/**"),
instanceOf(VersionResourceResolver.class), containsInstances(CachingResourceResolver.class,
instanceOf(PathResourceResolver.class))); VersionResourceResolver.class, PathResourceResolver.class));
assertThat(getResourceTransformers("/**"), contains(instanceOf(CachingResourceTransformer.class), assertThat(
instanceOf(CssLinkResourceTransformer.class))); getResourceTransformers("/**"),
VersionResourceResolver resolver = (VersionResourceResolver) getResourceResolvers("/**").get(1); containsInstances(CachingResourceTransformer.class,
assertThat(resolver.getStrategyMap().get("/**/*.js"), instanceOf(FixedVersionStrategy.class)); CssLinkResourceTransformer.class));
VersionResourceResolver resolver = (VersionResourceResolver) getResourceResolvers(
"/**").get(1);
assertThat(resolver.getStrategyMap().get("/**/*.js"),
instanceOf(FixedVersionStrategy.class));
} }
@Test @Test
public void resourceHandlerContentStrategyEnabled() throws Exception { public void resourceHandlerContentStrategyEnabled() throws Exception {
load("spring.resources.chain.strategy.content.enabled:true", load("spring.resources.chain.strategy.content.enabled:true",
"spring.resources.chain.strategy.content.paths:/**,/*.png"); "spring.resources.chain.strategy.content.paths:/**,/*.png");
assertThat(getResourceResolvers("/webjars/**").size(), equalTo(3)); assertThat(getResourceResolvers("/webjars/**").size(), equalTo(3));
assertThat(getResourceTransformers("/webjars/**").size(), equalTo(2)); assertThat(getResourceTransformers("/webjars/**").size(), equalTo(2));
assertThat(getResourceResolvers("/**").size(), equalTo(3)); assertThat(getResourceResolvers("/**").size(), equalTo(3));
assertThat(getResourceTransformers("/**").size(), equalTo(2)); assertThat(getResourceTransformers("/**").size(), equalTo(2));
assertThat(
assertThat(getResourceResolvers("/**"), contains(instanceOf(CachingResourceResolver.class), getResourceResolvers("/**"),
instanceOf(VersionResourceResolver.class), containsInstances(CachingResourceResolver.class,
instanceOf(PathResourceResolver.class))); VersionResourceResolver.class, PathResourceResolver.class));
assertThat(getResourceTransformers("/**"), contains(instanceOf(CachingResourceTransformer.class), assertThat(
instanceOf(CssLinkResourceTransformer.class))); getResourceTransformers("/**"),
VersionResourceResolver resolver = (VersionResourceResolver) getResourceResolvers("/**").get(1); containsInstances(CachingResourceTransformer.class,
assertThat(resolver.getStrategyMap().get("/*.png"), instanceOf(ContentVersionStrategy.class)); CssLinkResourceTransformer.class));
VersionResourceResolver resolver = (VersionResourceResolver) getResourceResolvers(
"/**").get(1);
assertThat(resolver.getStrategyMap().get("/*.png"),
instanceOf(ContentVersionStrategy.class));
} }
@Test @Test
@ -229,20 +237,24 @@ public class WebMvcAutoConfigurationTests {
"spring.resources.chain.strategy.fixed.version:test", "spring.resources.chain.strategy.fixed.version:test",
"spring.resources.chain.strategy.fixed.paths:/**/*.js", "spring.resources.chain.strategy.fixed.paths:/**/*.js",
"spring.resources.chain.html5AppCache:true"); "spring.resources.chain.html5AppCache:true");
assertThat(getResourceResolvers("/webjars/**").size(), equalTo(2)); assertThat(getResourceResolvers("/webjars/**").size(), equalTo(2));
assertThat(getResourceTransformers("/webjars/**").size(), equalTo(2)); assertThat(getResourceTransformers("/webjars/**").size(), equalTo(2));
assertThat(getResourceResolvers("/**").size(), equalTo(2)); assertThat(getResourceResolvers("/**").size(), equalTo(2));
assertThat(getResourceTransformers("/**").size(), equalTo(2)); assertThat(getResourceTransformers("/**").size(), equalTo(2));
assertThat(
assertThat(getResourceResolvers("/**"), contains( getResourceResolvers("/**"),
instanceOf(VersionResourceResolver.class), instanceOf(PathResourceResolver.class))); containsInstances(VersionResourceResolver.class,
assertThat(getResourceTransformers("/**"), contains(instanceOf(CssLinkResourceTransformer.class), PathResourceResolver.class));
instanceOf(AppCacheManifestTransformer.class))); assertThat(
getResourceTransformers("/**"),
VersionResourceResolver resolver = (VersionResourceResolver) getResourceResolvers("/**").get(0); containsInstances(CssLinkResourceTransformer.class,
assertThat(resolver.getStrategyMap().get("/*.png"), instanceOf(ContentVersionStrategy.class)); AppCacheManifestTransformer.class));
assertThat(resolver.getStrategyMap().get("/**/*.js"), instanceOf(FixedVersionStrategy.class)); VersionResourceResolver resolver = (VersionResourceResolver) getResourceResolvers(
"/**").get(0);
assertThat(resolver.getStrategyMap().get("/*.png"),
instanceOf(ContentVersionStrategy.class));
assertThat(resolver.getStrategyMap().get("/**/*.js"),
instanceOf(FixedVersionStrategy.class));
} }
@Test @Test
@ -315,14 +327,18 @@ public class WebMvcAutoConfigurationTests {
} }
protected List<ResourceResolver> getResourceResolvers(String mapping) { protected List<ResourceResolver> getResourceResolvers(String mapping) {
SimpleUrlHandlerMapping handler = (SimpleUrlHandlerMapping) this.context.getBean("resourceHandlerMapping"); SimpleUrlHandlerMapping handler = (SimpleUrlHandlerMapping) this.context
ResourceHttpRequestHandler resourceHandler = (ResourceHttpRequestHandler) handler.getHandlerMap().get(mapping); .getBean("resourceHandlerMapping");
ResourceHttpRequestHandler resourceHandler = (ResourceHttpRequestHandler) handler
.getHandlerMap().get(mapping);
return resourceHandler.getResourceResolvers(); return resourceHandler.getResourceResolvers();
} }
protected List<ResourceTransformer> getResourceTransformers(String mapping) { protected List<ResourceTransformer> getResourceTransformers(String mapping) {
SimpleUrlHandlerMapping handler = (SimpleUrlHandlerMapping) this.context.getBean("resourceHandlerMapping"); SimpleUrlHandlerMapping handler = (SimpleUrlHandlerMapping) this.context
ResourceHttpRequestHandler resourceHandler = (ResourceHttpRequestHandler) handler.getHandlerMap().get(mapping); .getBean("resourceHandlerMapping");
ResourceHttpRequestHandler resourceHandler = (ResourceHttpRequestHandler) handler
.getHandlerMap().get(mapping);
return resourceHandler.getResourceTransformers(); return resourceHandler.getResourceTransformers();
} }
@ -440,6 +456,15 @@ public class WebMvcAutoConfigurationTests {
this.context.refresh(); this.context.refresh();
} }
@SuppressWarnings({ "unchecked", "rawtypes" })
private <E> Matcher<E> 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) { private void load(String... environment) {
load(null, environment); load(null, environment);
} }

View File

@ -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 The `endpoints.health.time-to-live` property is used to configure the caching period in
milliseconds. It defaults to 1000, i.e. one second. milliseconds. It defaults to 1000, i.e. one second.
The above-described restrictions can be enhanced, thereby allowing only authenticated users full The above-described restrictions can be enhanced, thereby allowing only authenticated
access to the health endpoint in a secure application. To do so, set `endpoints.health.sensitive` to `true`. users full access to the health endpoint in a secure application. To do so, set
Here's a summary of behaviour (with default `sensitive` flag value "false" indicated in bold): `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
|**false**
| true | true | No content | Full content |Status only
|Full content
|true
|true
|No content
|Full content
|==== |====
[[production-ready-jmx]] [[production-ready-jmx]]
== Monitoring and management over JMX == Monitoring and management over JMX
Java Management Extensions (JMX) provide a standard mechanism to monitor and manage Java Management Extensions (JMX) provide a standard mechanism to monitor and manage

View File

@ -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 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. 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"] [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]] [[boot-features-spring-mvc-template-engines]]
==== Template engines ==== Template engines
As well as REST web services, you can also use Spring MVC to serve dynamic HTML content. As well as REST web services, you can also use Spring MVC to serve dynamic HTML content.

View File

@ -68,14 +68,10 @@ public class SampleJetty8ApplicationTests {
HttpHeaders requestHeaders = new HttpHeaders(); HttpHeaders requestHeaders = new HttpHeaders();
requestHeaders.set("Accept-Encoding", "gzip"); requestHeaders.set("Accept-Encoding", "gzip");
HttpEntity<?> requestEntity = new HttpEntity<Object>(requestHeaders); HttpEntity<?> requestEntity = new HttpEntity<Object>(requestHeaders);
RestTemplate restTemplate = new TestRestTemplate(); RestTemplate restTemplate = new TestRestTemplate();
ResponseEntity<byte[]> entity = restTemplate.exchange("http://localhost:" ResponseEntity<byte[]> entity = restTemplate.exchange("http://localhost:"
+ this.port, HttpMethod.GET, requestEntity, byte[].class); + this.port, HttpMethod.GET, requestEntity, byte[].class);
assertEquals(HttpStatus.OK, entity.getStatusCode()); assertEquals(HttpStatus.OK, entity.getStatusCode());
GZIPInputStream inflater = new GZIPInputStream(new ByteArrayInputStream( GZIPInputStream inflater = new GZIPInputStream(new ByteArrayInputStream(
entity.getBody())); entity.getBody()));
try { try {

View File

@ -68,14 +68,10 @@ public class SampleJetty93ApplicationTests {
HttpHeaders requestHeaders = new HttpHeaders(); HttpHeaders requestHeaders = new HttpHeaders();
requestHeaders.set("Accept-Encoding", "gzip"); requestHeaders.set("Accept-Encoding", "gzip");
HttpEntity<?> requestEntity = new HttpEntity<Object>(requestHeaders); HttpEntity<?> requestEntity = new HttpEntity<Object>(requestHeaders);
RestTemplate restTemplate = new TestRestTemplate(); RestTemplate restTemplate = new TestRestTemplate();
ResponseEntity<byte[]> entity = restTemplate.exchange("http://localhost:" ResponseEntity<byte[]> entity = restTemplate.exchange("http://localhost:"
+ this.port, HttpMethod.GET, requestEntity, byte[].class); + this.port, HttpMethod.GET, requestEntity, byte[].class);
assertEquals(HttpStatus.OK, entity.getStatusCode()); assertEquals(HttpStatus.OK, entity.getStatusCode());
GZIPInputStream inflater = new GZIPInputStream(new ByteArrayInputStream( GZIPInputStream inflater = new GZIPInputStream(new ByteArrayInputStream(
entity.getBody())); entity.getBody()));
try { try {

View File

@ -66,14 +66,10 @@ public class SampleTomcatApplicationTests {
HttpHeaders requestHeaders = new HttpHeaders(); HttpHeaders requestHeaders = new HttpHeaders();
requestHeaders.set("Accept-Encoding", "gzip"); requestHeaders.set("Accept-Encoding", "gzip");
HttpEntity<?> requestEntity = new HttpEntity<Object>(requestHeaders); HttpEntity<?> requestEntity = new HttpEntity<Object>(requestHeaders);
RestTemplate restTemplate = new TestRestTemplate(); RestTemplate restTemplate = new TestRestTemplate();
ResponseEntity<byte[]> entity = restTemplate.exchange("http://localhost:" ResponseEntity<byte[]> entity = restTemplate.exchange("http://localhost:"
+ this.port, HttpMethod.GET, requestEntity, byte[].class); + this.port, HttpMethod.GET, requestEntity, byte[].class);
assertEquals(HttpStatus.OK, entity.getStatusCode()); assertEquals(HttpStatus.OK, entity.getStatusCode());
GZIPInputStream inflater = new GZIPInputStream(new ByteArrayInputStream( GZIPInputStream inflater = new GZIPInputStream(new ByteArrayInputStream(
entity.getBody())); entity.getBody()));
try { try {

View File

@ -70,14 +70,10 @@ public class SampleUndertowApplicationTests {
HttpHeaders requestHeaders = new HttpHeaders(); HttpHeaders requestHeaders = new HttpHeaders();
requestHeaders.set("Accept-Encoding", "gzip"); requestHeaders.set("Accept-Encoding", "gzip");
HttpEntity<?> requestEntity = new HttpEntity<Object>(requestHeaders); HttpEntity<?> requestEntity = new HttpEntity<Object>(requestHeaders);
RestTemplate restTemplate = new TestRestTemplate(); RestTemplate restTemplate = new TestRestTemplate();
ResponseEntity<byte[]> entity = restTemplate.exchange("http://localhost:" ResponseEntity<byte[]> entity = restTemplate.exchange("http://localhost:"
+ this.port, HttpMethod.GET, requestEntity, byte[].class); + this.port, HttpMethod.GET, requestEntity, byte[].class);
assertEquals(HttpStatus.OK, entity.getStatusCode()); assertEquals(HttpStatus.OK, entity.getStatusCode());
GZIPInputStream inflater = new GZIPInputStream(new ByteArrayInputStream( GZIPInputStream inflater = new GZIPInputStream(new ByteArrayInputStream(
entity.getBody())); entity.getBody()));
try { try {

View File

@ -285,6 +285,7 @@ public abstract class AbstractConfigurableEmbeddedServletContainer implements
return this.compression; return this.compression;
} }
@Override
public void setCompression(Compression compression) { public void setCompression(Compression compression) {
this.compression = compression; this.compression = compression;
} }

View File

@ -155,14 +155,14 @@ public class JettyEmbeddedServletContainerFactory extends
} }
private HandlerWrapper createGzipHandler() { 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()); 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()); return new Jetty8GzipHandlerFactory().createGzipHandler(getCompression());
} }
else if (ClassUtils if (ClassUtils.isPresent(GZIP_HANDLER_JETTY_9_3, getClass().getClassLoader())) {
.isPresent(GZIP_HANDLER_JETTY_9_3, getClass().getClassLoader())) {
return new Jetty93GzipHandlerFactory().createGzipHandler(getCompression()); return new Jetty93GzipHandlerFactory().createGzipHandler(getCompression());
} }
throw new IllegalStateException( throw new IllegalStateException(
@ -579,19 +579,17 @@ public class JettyEmbeddedServletContainerFactory extends
@Override @Override
public HandlerWrapper createGzipHandler(Compression compression) { public HandlerWrapper createGzipHandler(Compression compression) {
try { try {
Class<?> gzipHandlerClass = ClassUtils.forName(GZIP_HANDLER_JETTY_8, Class<?> handlerClass = ClassUtils.forName(GZIP_HANDLER_JETTY_8,
getClass().getClassLoader()); getClass().getClassLoader());
HandlerWrapper gzipHandler = (HandlerWrapper) gzipHandlerClass HandlerWrapper handler = (HandlerWrapper) handlerClass.newInstance();
.newInstance(); ReflectionUtils.findMethod(handlerClass, "setMinGzipSize", int.class)
ReflectionUtils.findMethod(gzipHandlerClass, "setMinGzipSize", int.class) .invoke(handler, compression.getMinResponseSize());
.invoke(gzipHandler, compression.getMinResponseSize()); ReflectionUtils.findMethod(handlerClass, "setMimeTypes", Set.class)
ReflectionUtils.findMethod(gzipHandlerClass, "setMimeTypes", Set.class) .invoke(handler,
.invoke(gzipHandler,
new HashSet<String>(Arrays.asList(compression new HashSet<String>(Arrays.asList(compression
.getMimeTypes()))); .getMimeTypes())));
return gzipHandler; return handler;
} }
catch (Exception ex) { catch (Exception ex) {
throw new RuntimeException("Failed to configure Jetty 8 gzip handler", ex); throw new RuntimeException("Failed to configure Jetty 8 gzip handler", ex);
} }
@ -608,7 +606,6 @@ public class JettyEmbeddedServletContainerFactory extends
gzipHandler.setMimeTypes(new HashSet<String>(Arrays.asList(compression gzipHandler.setMimeTypes(new HashSet<String>(Arrays.asList(compression
.getMimeTypes()))); .getMimeTypes())));
return gzipHandler; return gzipHandler;
} }
} }
@ -618,18 +615,16 @@ public class JettyEmbeddedServletContainerFactory extends
@Override @Override
public HandlerWrapper createGzipHandler(Compression compression) { public HandlerWrapper createGzipHandler(Compression compression) {
try { try {
Class<?> gzipHandlerClass = ClassUtils.forName(GZIP_HANDLER_JETTY_9_3, Class<?> handlerClass = ClassUtils.forName(GZIP_HANDLER_JETTY_9_3,
getClass().getClassLoader()); getClass().getClassLoader());
HandlerWrapper gzipHandler = (HandlerWrapper) gzipHandlerClass HandlerWrapper handler = (HandlerWrapper) handlerClass.newInstance();
.newInstance(); ReflectionUtils.findMethod(handlerClass, "setMinGzipSize", int.class)
ReflectionUtils.findMethod(gzipHandlerClass, "setMinGzipSize", int.class) .invoke(handler, compression.getMinResponseSize());
.invoke(gzipHandler, compression.getMinResponseSize()); ReflectionUtils.findMethod(handlerClass, "setIncludedMimeTypes",
ReflectionUtils.findMethod(gzipHandlerClass, "setIncludedMimeTypes", String[].class).invoke(handler,
String[].class).invoke(gzipHandler,
new Object[] { compression.getMimeTypes() }); new Object[] { compression.getMimeTypes() });
return gzipHandler; return handler;
} }
catch (Exception ex) { catch (Exception ex) {
throw new RuntimeException("Failed to configure Jetty 9.3 gzip handler", throw new RuntimeException("Failed to configure Jetty 9.3 gzip handler",
ex); ex);
@ -637,4 +632,5 @@ public class JettyEmbeddedServletContainerFactory extends
} }
} }
} }

View File

@ -51,6 +51,7 @@ import org.apache.coyote.http11.AbstractHttp11JsseProtocol;
import org.apache.coyote.http11.AbstractHttp11Protocol; import org.apache.coyote.http11.AbstractHttp11Protocol;
import org.springframework.beans.BeanUtils; import org.springframework.beans.BeanUtils;
import org.springframework.boot.context.embedded.AbstractEmbeddedServletContainerFactory; 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.EmbeddedServletContainer;
import org.springframework.boot.context.embedded.EmbeddedServletContainerException; import org.springframework.boot.context.embedded.EmbeddedServletContainerException;
import org.springframework.boot.context.embedded.EmbeddedServletContainerFactory; import org.springframework.boot.context.embedded.EmbeddedServletContainerFactory;
@ -233,10 +234,7 @@ public class TomcatEmbeddedServletContainerFactory extends
int port = (getPort() >= 0 ? getPort() : 0); int port = (getPort() >= 0 ? getPort() : 0);
connector.setPort(port); connector.setPort(port);
if (connector.getProtocolHandler() instanceof AbstractProtocol) { if (connector.getProtocolHandler() instanceof AbstractProtocol) {
if (getAddress() != null) { customizeProtocol((AbstractProtocol<?>) connector.getProtocolHandler());
((AbstractProtocol<?>) connector.getProtocolHandler())
.setAddress(getAddress());
}
} }
if (getUriEncoding() != null) { if (getUriEncoding() != null) {
connector.setURIEncoding(getUriEncoding()); connector.setURIEncoding(getUriEncoding());
@ -247,30 +245,41 @@ public class TomcatEmbeddedServletContainerFactory extends
connector.setProperty("bindOnInit", "false"); connector.setProperty("bindOnInit", "false");
if (getSsl() != null && getSsl().isEnabled()) { if (getSsl() != null && getSsl().isEnabled()) {
Assert.state( customizeSsl(connector);
connector.getProtocolHandler() instanceof AbstractHttp11JsseProtocol, }
if (getCompression() != null && getCompression().getEnabled()) {
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 " "To use SSL, the connector's protocol handler must be an "
+ "AbstractHttp11JsseProtocol subclass"); + "AbstractHttp11JsseProtocol subclass");
configureSsl((AbstractHttp11JsseProtocol<?>) connector.getProtocolHandler(), configureSsl((AbstractHttp11JsseProtocol<?>) handler, getSsl());
getSsl());
connector.setScheme("https"); connector.setScheme("https");
connector.setSecure(true); connector.setSecure(true);
} }
if (getCompression() != null && getCompression().getEnabled()) { private void customizeCompression(Connector connector) {
ProtocolHandler handler = connector.getProtocolHandler(); ProtocolHandler handler = connector.getProtocolHandler();
if (handler instanceof AbstractHttp11Protocol) { if (handler instanceof AbstractHttp11Protocol) {
@SuppressWarnings("rawtypes") AbstractHttp11Protocol<?> protocol = (AbstractHttp11Protocol<?>) handler;
AbstractHttp11Protocol protocol = (AbstractHttp11Protocol) handler; Compression compression = getCompression();
protocol.setCompression("on"); protocol.setCompression("on");
protocol.setCompressionMinSize(getCompression().getMinResponseSize()); protocol.setCompressionMinSize(compression.getMinResponseSize());
protocol.setCompressableMimeTypes(StringUtils protocol.setCompressableMimeTypes(StringUtils
.arrayToCommaDelimitedString(getCompression().getMimeTypes())); .arrayToCommaDelimitedString(compression.getMimeTypes()));
}
}
for (TomcatConnectorCustomizer customizer : this.tomcatConnectorCustomizers) {
customizer.customize(connector);
} }
} }

View File

@ -123,11 +123,9 @@ public class UndertowEmbeddedServletContainer implements EmbeddedServletContaine
return servletHandler; return servletHandler;
} }
ContentEncodingRepository encodingRepository = new ContentEncodingRepository(); ContentEncodingRepository encodingRepository = new ContentEncodingRepository();
Predicate mimeAndSizePredicate = Predicates.and(Predicates Predicate mimeAndSizePredicate = Predicates.and(Predicates
.maxContentSize(this.compression.getMinResponseSize()), Predicates .maxContentSize(this.compression.getMinResponseSize()), Predicates
.or(new CompressibleMimeTypePredicate(this.compression.getMimeTypes()))); .or(new CompressibleMimeTypePredicate(this.compression.getMimeTypes())));
encodingRepository.addEncodingHandler("gzip", new GzipEncodingProvider(), 50, encodingRepository.addEncodingHandler("gzip", new GzipEncodingProvider(), 50,
mimeAndSizePredicate); mimeAndSizePredicate);
return new EncodingHandler(encodingRepository).setNext(servletHandler); return new EncodingHandler(encodingRepository).setNext(servletHandler);

View File

@ -27,6 +27,7 @@ import java.nio.charset.Charset;
import java.security.KeyStore; import java.security.KeyStore;
import java.util.Arrays; import java.util.Arrays;
import java.util.Date; import java.util.Date;
import java.util.Map;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicBoolean;
import java.util.zip.GZIPInputStream; import java.util.zip.GZIPInputStream;
@ -530,18 +531,31 @@ public abstract class AbstractEmbeddedServletContainerFactoryTests {
@Test @Test
public void compression() throws Exception { public void compression() throws Exception {
assertTrue(internalTestCompression(10000, null)); assertTrue(doTestCompression(10000, null));
} }
@Test @Test
public void noCompressionForSmallResponse() throws Exception { public void noCompressionForSmallResponse() throws Exception {
assertFalse(internalTestCompression(100, null)); assertFalse(doTestCompression(100, null));
} }
@Test @Test
public void noCompressionForMimeType() throws Exception { public void noCompressionForMimeType() throws Exception {
assertFalse(internalTestCompression(10000, new String[] { "text/html", String[] mimeTypes = new String[] { "text/html", "text/xml", "text/css" };
"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<String, InputStreamFactory> 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) protected String setUpFactoryForCompression(int contentSize, String[] mimeTypes)
@ -549,9 +563,7 @@ public abstract class AbstractEmbeddedServletContainerFactoryTests {
char[] chars = new char[contentSize]; char[] chars = new char[contentSize];
Arrays.fill(chars, 'F'); Arrays.fill(chars, 'F');
String testContent = new String(chars); String testContent = new String(chars);
AbstractEmbeddedServletContainerFactory factory = getFactory(); AbstractEmbeddedServletContainerFactory factory = getFactory();
FileCopyUtils.copy(testContent, FileCopyUtils.copy(testContent,
new FileWriter(this.temporaryFolder.newFile("test.txt"))); new FileWriter(this.temporaryFolder.newFile("test.txt")));
factory.setDocumentRoot(this.temporaryFolder.getRoot()); factory.setDocumentRoot(this.temporaryFolder.getRoot());
@ -561,47 +573,11 @@ public abstract class AbstractEmbeddedServletContainerFactoryTests {
compression.setMimeTypes(mimeTypes); compression.setMimeTypes(mimeTypes);
} }
factory.setCompression(compression); factory.setCompression(compression);
this.container = factory.getEmbeddedServletContainer(); this.container = factory.getEmbeddedServletContainer();
this.container.start(); this.container.start();
return testContent; 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) private void addTestTxtFile(AbstractEmbeddedServletContainerFactory factory)
throws IOException { throws IOException {
FileCopyUtils.copy("test", FileCopyUtils.copy("test",
@ -678,6 +654,26 @@ public abstract class AbstractEmbeddedServletContainerFactoryTests {
return bean; 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") @SuppressWarnings("serial")
private static class InitCountingServlet extends GenericServlet { private static class InitCountingServlet extends GenericServlet {
@ -696,6 +692,7 @@ public abstract class AbstractEmbeddedServletContainerFactoryTests {
public int getInitCount() { public int getInitCount() {
return this.initCount; return this.initCount;
} }
}; };
} }

View File

@ -191,16 +191,13 @@ public class JettyEmbeddedServletContainerFactoryTests extends
char[] chars = new char[contentSize]; char[] chars = new char[contentSize];
Arrays.fill(chars, 'F'); Arrays.fill(chars, 'F');
final String testContent = new String(chars); final String testContent = new String(chars);
AbstractEmbeddedServletContainerFactory factory = getFactory(); AbstractEmbeddedServletContainerFactory factory = getFactory();
Compression compression = new Compression(); Compression compression = new Compression();
compression.setEnabled(true); compression.setEnabled(true);
if (mimeTypes != null) { if (mimeTypes != null) {
compression.setMimeTypes(mimeTypes); compression.setMimeTypes(mimeTypes);
} }
factory.setCompression(compression); factory.setCompression(compression);
this.container = factory.getEmbeddedServletContainer(new ServletRegistrationBean( this.container = factory.getEmbeddedServletContainer(new ServletRegistrationBean(
new HttpServlet() { new HttpServlet() {
@Override @Override