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

@ -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
<<production-ready-health-access-restrictions>> 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

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

@ -65,4 +65,4 @@ public class Compression {
this.minResponseSize = minSize;
}
}
}

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