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;
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;
}
}
}

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.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);

View File

@ -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<ResourceResolver> 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<ResourceTransformer> 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 <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) {
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
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

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
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.

View File

@ -68,14 +68,10 @@ public class SampleJetty8ApplicationTests {
HttpHeaders requestHeaders = new HttpHeaders();
requestHeaders.set("Accept-Encoding", "gzip");
HttpEntity<?> requestEntity = new HttpEntity<Object>(requestHeaders);
RestTemplate restTemplate = new TestRestTemplate();
ResponseEntity<byte[]> 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 {

View File

@ -68,14 +68,10 @@ public class SampleJetty93ApplicationTests {
HttpHeaders requestHeaders = new HttpHeaders();
requestHeaders.set("Accept-Encoding", "gzip");
HttpEntity<?> requestEntity = new HttpEntity<Object>(requestHeaders);
RestTemplate restTemplate = new TestRestTemplate();
ResponseEntity<byte[]> 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 {

View File

@ -66,14 +66,10 @@ public class SampleTomcatApplicationTests {
HttpHeaders requestHeaders = new HttpHeaders();
requestHeaders.set("Accept-Encoding", "gzip");
HttpEntity<?> requestEntity = new HttpEntity<Object>(requestHeaders);
RestTemplate restTemplate = new TestRestTemplate();
ResponseEntity<byte[]> 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 {

View File

@ -70,14 +70,10 @@ public class SampleUndertowApplicationTests {
HttpHeaders requestHeaders = new HttpHeaders();
requestHeaders.set("Accept-Encoding", "gzip");
HttpEntity<?> requestEntity = new HttpEntity<Object>(requestHeaders);
RestTemplate restTemplate = new TestRestTemplate();
ResponseEntity<byte[]> 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 {

View File

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

View File

@ -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<String>(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<String>(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
}
}
}

View File

@ -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

View File

@ -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);

View File

@ -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<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)
@ -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;
}
};
}

View File

@ -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