mirror of
https://github.com/spring-projects/spring-boot.git
synced 2024-08-29 03:06:45 +08:00
Polish
This commit is contained in:
parent
cc3aea2b69
commit
d213cc05d5
@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -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);
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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.
|
||||
|
@ -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 {
|
||||
|
@ -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 {
|
||||
|
@ -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 {
|
||||
|
@ -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 {
|
||||
|
@ -285,6 +285,7 @@ public abstract class AbstractConfigurableEmbeddedServletContainer implements
|
||||
return this.compression;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setCompression(Compression compression) {
|
||||
this.compression = compression;
|
||||
}
|
||||
|
@ -65,4 +65,4 @@ public class Compression {
|
||||
this.minResponseSize = minSize;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
@ -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
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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);
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
}
|
||||
|
@ -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
|
||||
|
Loading…
Reference in New Issue
Block a user