Skip to content

Commit fe1ad5b

Browse files
author
tinexw
committed
Set CacheControl for static resources from properties file
Fixes gh-9432
1 parent 1e2220b commit fe1ad5b

File tree

4 files changed

+387
-14
lines changed

4 files changed

+387
-14
lines changed

spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/ResourceProperties.java

Lines changed: 242 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,16 @@
1616

1717
package org.springframework.boot.autoconfigure.web;
1818

19+
import java.util.concurrent.TimeUnit;
20+
import java.util.function.BiFunction;
21+
import java.util.function.Supplier;
22+
23+
import javax.annotation.PostConstruct;
24+
1925
import org.springframework.boot.context.properties.ConfigurationProperties;
26+
import org.springframework.boot.context.properties.NestedConfigurationProperty;
27+
import org.springframework.http.CacheControl;
28+
import org.springframework.util.Assert;
2029

2130
/**
2231
* Properties used to configure resource handling.
@@ -25,14 +34,15 @@
2534
* @author Brian Clozel
2635
* @author Dave Syer
2736
* @author Venil Noronha
37+
* @author Kristine Jetzke
2838
* @since 1.1.0
2939
*/
3040
@ConfigurationProperties(prefix = "spring.resources", ignoreUnknownFields = false)
3141
public class ResourceProperties {
3242

3343
private static final String[] CLASSPATH_RESOURCE_LOCATIONS = {
3444
"classpath:/META-INF/resources/", "classpath:/resources/",
35-
"classpath:/static/", "classpath:/public/" };
45+
"classpath:/static/", "classpath:/public/"};
3646

3747
/**
3848
* Locations of static resources. Defaults to classpath:[/META-INF/resources/,
@@ -41,10 +51,17 @@ public class ResourceProperties {
4151
private String[] staticLocations = CLASSPATH_RESOURCE_LOCATIONS;
4252

4353
/**
44-
* Cache period for the resources served by the resource handler, in seconds.
54+
* Cache period for the resources served by the resource handler, in seconds. Either
55+
* {@link #cachePeriod} or {@link #cacheControl} can be set.
4556
*/
4657
private Integer cachePeriod;
4758

59+
/**
60+
* Cache control headers. Either {@link #cachePeriod} or {@link #cacheControl} can be set.
61+
*/
62+
@NestedConfigurationProperty
63+
private CacheControlProperties cacheControl;
64+
4865
/**
4966
* Enable default resource handling.
5067
*/
@@ -77,6 +94,15 @@ public void setCachePeriod(Integer cachePeriod) {
7794
this.cachePeriod = cachePeriod;
7895
}
7996

97+
public CacheControlProperties getCacheControl() {
98+
return this.cacheControl;
99+
}
100+
101+
public void setCacheControl(CacheControlProperties cacheControl) {
102+
this.cacheControl = cacheControl;
103+
}
104+
105+
80106
public boolean isAddMappings() {
81107
return this.addMappings;
82108
}
@@ -89,6 +115,36 @@ public Chain getChain() {
89115
return this.chain;
90116
}
91117

118+
public CacheControl createCacheControl() {
119+
if (this.cachePeriod != null) {
120+
return CacheControl.maxAge(this.cachePeriod, TimeUnit.SECONDS);
121+
}
122+
if (this.cacheControl != null) {
123+
return this.cacheControl.transformToHttpSpringCacheControl();
124+
}
125+
return null;
126+
}
127+
128+
@PostConstruct
129+
public void checkIncompatibleCacheOptions() {
130+
Assert.state(this.cachePeriod == null || this.cacheControl == null,
131+
"Only one of cache-period or cache-control may be set.");
132+
if (this.cacheControl != null) {
133+
if (this.cacheControl.getMaxAge() != null) {
134+
Assert.state(!Boolean.TRUE.equals(this.cacheControl.getNoCache()), "no-cache may not be set if max-age is set.");
135+
Assert.state(!Boolean.TRUE.equals(this.cacheControl.getNoStore()), "no-store may not be set if max-age is set.");
136+
}
137+
if (this.cacheControl.getNoCache() != null) {
138+
Assert.state(this.cacheControl.getMaxAge() == null, "max-age may not be set if no-cache is set.");
139+
Assert.state(!Boolean.TRUE.equals(this.cacheControl.getNoStore()), "no-store may not be set if no-cache is set.");
140+
}
141+
if (this.cacheControl.getNoStore() != null) {
142+
Assert.state(this.cacheControl.getMaxAge() == null, "max-age may not be set if no-store is set.");
143+
Assert.state(!Boolean.TRUE.equals(this.cacheControl.getNoCache()), "no-cache may not be set if no-store is set.");
144+
}
145+
}
146+
}
147+
92148
/**
93149
* Configuration for the Spring Resource Handling chain.
94150
*/
@@ -121,8 +177,9 @@ public static class Chain {
121177
/**
122178
* Return whether the resource chain is enabled. Return {@code null} if no
123179
* specific settings are present.
124-
* @return whether the resource chain is enabled or {@code null} if no specified
125-
* settings are present.
180+
*
181+
* @return whether the resource chain is enabled or {@code null} if no specified settings are
182+
* present.
126183
*/
127184
public Boolean getEnabled() {
128185
return getEnabled(getStrategy().getFixed().isEnabled(),
@@ -200,7 +257,7 @@ public static class Content {
200257
/**
201258
* Comma-separated list of patterns to apply to the Version Strategy.
202259
*/
203-
private String[] paths = new String[] { "/**" };
260+
private String[] paths = new String[]{"/**"};
204261

205262
public boolean isEnabled() {
206263
return this.enabled;
@@ -233,7 +290,7 @@ public static class Fixed {
233290
/**
234291
* Comma-separated list of patterns to apply to the Version Strategy.
235292
*/
236-
private String[] paths = new String[] { "/**" };
293+
private String[] paths = new String[]{"/**"};
237294

238295
/**
239296
* Version string to use for the Version Strategy.
@@ -266,4 +323,183 @@ public void setVersion(String version) {
266323

267324
}
268325

326+
/**
327+
* Configuration for the Cache Control header.
328+
*/
329+
330+
public static class CacheControlProperties {
331+
332+
private Long maxAge;
333+
334+
private Boolean noCache;
335+
336+
private Boolean noStore;
337+
338+
private Boolean mustRevalidate;
339+
340+
private Boolean noTransform;
341+
342+
private Boolean cachePublic;
343+
344+
private Boolean cachePrivate;
345+
346+
private Boolean proxyRevalidate;
347+
348+
private Long staleWhileRevalidate;
349+
350+
private Long staleIfError;
351+
352+
private Long sMaxAge;
353+
354+
public Long getMaxAge() {
355+
return this.maxAge;
356+
}
357+
358+
public void setMaxAge(Long maxAge) {
359+
this.maxAge = maxAge;
360+
}
361+
362+
public Boolean getNoCache() {
363+
return this.noCache;
364+
}
365+
366+
public void setNoCache(Boolean noCache) {
367+
this.noCache = noCache;
368+
}
369+
370+
public Boolean getNoStore() {
371+
return this.noStore;
372+
}
373+
374+
public void setNoStore(Boolean noStore) {
375+
this.noStore = noStore;
376+
}
377+
378+
public Boolean getMustRevalidate() {
379+
return this.mustRevalidate;
380+
}
381+
382+
public void setMustRevalidate(Boolean mustRevalidate) {
383+
this.mustRevalidate = mustRevalidate;
384+
}
385+
386+
public Boolean getNoTransform() {
387+
return this.noTransform;
388+
}
389+
390+
public void setNoTransform(Boolean noTransform) {
391+
this.noTransform = noTransform;
392+
}
393+
394+
public Boolean getCachePublic() {
395+
return this.cachePublic;
396+
}
397+
398+
public void setCachePublic(Boolean cachePublic) {
399+
this.cachePublic = cachePublic;
400+
}
401+
402+
public Boolean getCachePrivate() {
403+
return this.cachePrivate;
404+
}
405+
406+
public void setCachePrivate(Boolean cachePrivate) {
407+
this.cachePrivate = cachePrivate;
408+
}
409+
410+
public Boolean getProxyRevalidate() {
411+
return this.proxyRevalidate;
412+
}
413+
414+
public void setProxyRevalidate(Boolean proxyRevalidate) {
415+
this.proxyRevalidate = proxyRevalidate;
416+
}
417+
418+
public Long getStaleWhileRevalidate() {
419+
return this.staleWhileRevalidate;
420+
}
421+
422+
public void setStaleWhileRevalidate(Long staleWhileRevalidate) {
423+
this.staleWhileRevalidate = staleWhileRevalidate;
424+
}
425+
426+
public Long getStaleIfError() {
427+
return this.staleIfError;
428+
}
429+
430+
public void setStaleIfError(Long staleIfError) {
431+
this.staleIfError = staleIfError;
432+
}
433+
434+
public Long getsMaxAge() {
435+
return this.sMaxAge;
436+
}
437+
438+
public void setsMaxAge(Long sMaxAge) {
439+
this.sMaxAge = sMaxAge;
440+
}
441+
442+
CacheControl transformToHttpSpringCacheControl() {
443+
CacheControl httpSpringCacheControl = initCacheControl();
444+
httpSpringCacheControl = setFlags(httpSpringCacheControl);
445+
httpSpringCacheControl = setTimes(httpSpringCacheControl);
446+
return httpSpringCacheControl;
447+
}
448+
449+
private CacheControl initCacheControl() {
450+
if (this.maxAge != null) {
451+
return CacheControl.maxAge(this.maxAge, TimeUnit.SECONDS);
452+
}
453+
if (Boolean.TRUE.equals(this.noCache)) {
454+
return CacheControl.noCache();
455+
}
456+
if (Boolean.TRUE.equals(this.noStore)) {
457+
return CacheControl.noStore();
458+
}
459+
return CacheControl.empty();
460+
}
461+
462+
private CacheControl setFlags(CacheControl cacheControl) {
463+
cacheControl = setBoolean(this.mustRevalidate, cacheControl::mustRevalidate,
464+
cacheControl);
465+
cacheControl = setBoolean(this.noTransform, cacheControl::noTransform,
466+
cacheControl);
467+
cacheControl = setBoolean(this.cachePublic, cacheControl::cachePublic,
468+
cacheControl);
469+
cacheControl = setBoolean(this.cachePrivate, cacheControl::cachePrivate,
470+
cacheControl);
471+
cacheControl = setBoolean(this.proxyRevalidate, cacheControl::proxyRevalidate,
472+
cacheControl);
473+
return cacheControl;
474+
}
475+
476+
private static CacheControl setBoolean(Boolean value,
477+
Supplier<CacheControl> setter, CacheControl cacheControl) {
478+
if (Boolean.TRUE.equals(value)) {
479+
return setter.get();
480+
}
481+
return cacheControl;
482+
}
483+
484+
private CacheControl setTimes(CacheControl cacheControl) {
485+
cacheControl = setLong(this.staleWhileRevalidate,
486+
cacheControl::staleWhileRevalidate, cacheControl);
487+
cacheControl = setLong(this.staleIfError, cacheControl::staleIfError,
488+
cacheControl);
489+
cacheControl = setLong(this.sMaxAge, cacheControl::sMaxAge, cacheControl);
490+
return cacheControl;
491+
}
492+
493+
private static CacheControl setLong(Long value,
494+
BiFunction<Long, TimeUnit, CacheControl> setter,
495+
CacheControl cacheControl) {
496+
if (value != null) {
497+
return setter.apply(value, TimeUnit.SECONDS);
498+
}
499+
return cacheControl;
500+
}
501+
502+
}
503+
504+
269505
}

spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/servlet/WebMvcAutoConfiguration.java

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,7 @@
7272
import org.springframework.format.Formatter;
7373
import org.springframework.format.FormatterRegistry;
7474
import org.springframework.format.datetime.DateFormatter;
75+
import org.springframework.http.CacheControl;
7576
import org.springframework.http.HttpHeaders;
7677
import org.springframework.http.MediaType;
7778
import org.springframework.http.converter.HttpMessageConverter;
@@ -132,6 +133,7 @@
132133
* @author Sébastien Deleuze
133134
* @author Eddú Meléndez
134135
* @author Stephane Nicoll
136+
* @author Kristine Jetzke
135137
*/
136138
@Configuration
137139
@ConditionalOnWebApplication(type = Type.SERVLET)
@@ -305,21 +307,21 @@ public void addResourceHandlers(ResourceHandlerRegistry registry) {
305307
logger.debug("Default resource handling disabled");
306308
return;
307309
}
308-
Integer cachePeriod = this.resourceProperties.getCachePeriod();
310+
CacheControl cacheControl = this.resourceProperties.createCacheControl();
309311
if (!registry.hasMappingForPattern("/webjars/**")) {
310312
customizeResourceHandlerRegistration(
311313
registry.addResourceHandler("/webjars/**")
312314
.addResourceLocations(
313315
"classpath:/META-INF/resources/webjars/")
314-
.setCachePeriod(cachePeriod));
316+
.setCacheControl(cacheControl));
315317
}
316318
String staticPathPattern = this.mvcProperties.getStaticPathPattern();
317319
if (!registry.hasMappingForPattern(staticPathPattern)) {
318320
customizeResourceHandlerRegistration(
319321
registry.addResourceHandler(staticPathPattern)
320322
.addResourceLocations(getResourceLocations(
321323
this.resourceProperties.getStaticLocations()))
322-
.setCachePeriod(cachePeriod));
324+
.setCacheControl(cacheControl));
323325
}
324326
}
325327

0 commit comments

Comments
 (0)