Skip to content

Commit 2a26870

Browse files
committed
Introduce optimizeLocations flag for resource location filtering on startup
This flag is off by default since it requires jar files with directory entries. Closes gh-27624
1 parent 11a0df3 commit 2a26870

File tree

8 files changed

+188
-80
lines changed

8 files changed

+188
-80
lines changed

spring-webflux/src/main/java/org/springframework/web/reactive/config/ResourceHandlerRegistration.java

Lines changed: 23 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,8 @@ public class ResourceHandlerRegistration {
5454

5555
private boolean useLastModified = true;
5656

57+
private boolean optimizeLocations = false;
58+
5759
@Nullable
5860
private Map<String, MediaType> mediaTypes;
5961

@@ -105,15 +107,33 @@ public ResourceHandlerRegistration setCacheControl(CacheControl cacheControl) {
105107
/**
106108
* Set whether the {@link Resource#lastModified()} information should be used to drive HTTP responses.
107109
* <p>This configuration is set to {@code true} by default.
108-
* @param useLastModified whether the "last modified" resource information should be used.
110+
* @param useLastModified whether the "last modified" resource information should be used
109111
* @return the same {@link ResourceHandlerRegistration} instance, for chained method invocation
110112
* @since 5.3
113+
* @see ResourceWebHandler#setUseLastModified
111114
*/
112115
public ResourceHandlerRegistration setUseLastModified(boolean useLastModified) {
113116
this.useLastModified = useLastModified;
114117
return this;
115118
}
116119

120+
/**
121+
* Set whether to optimize the specified locations through an existence check on startup,
122+
* filtering non-existing directories upfront so that they do not have to be checked
123+
* on every resource access.
124+
* <p>The default is {@code false}, for defensiveness against zip files without directory
125+
* entries which are unable to expose the existence of a directory upfront. Switch this flag to
126+
* {@code true} for optimized access in case of a consistent jar layout with directory entries.
127+
* @param optimizeLocations whether to optimize the locations through an existence check on startup
128+
* @return the same {@link ResourceHandlerRegistration} instance, for chained method invocation
129+
* @since 5.3.13
130+
* @see ResourceWebHandler#setOptimizeLocations
131+
*/
132+
public ResourceHandlerRegistration setOptimizeLocations(boolean optimizeLocations) {
133+
this.optimizeLocations = optimizeLocations;
134+
return this;
135+
}
136+
117137
/**
118138
* Configure a chain of resource resolvers and transformers to use. This
119139
* can be useful, for example, to apply a version strategy to resource URLs.
@@ -181,8 +201,8 @@ protected String[] getPathPatterns() {
181201
*/
182202
protected ResourceWebHandler getRequestHandler() {
183203
ResourceWebHandler handler = new ResourceWebHandler();
184-
handler.setLocationValues(this.locationValues);
185204
handler.setResourceLoader(this.resourceLoader);
205+
handler.setLocationValues(this.locationValues);
186206
if (this.resourceChainRegistration != null) {
187207
handler.setResourceResolvers(this.resourceChainRegistration.getResourceResolvers());
188208
handler.setResourceTransformers(this.resourceChainRegistration.getResourceTransformers());
@@ -191,6 +211,7 @@ protected ResourceWebHandler getRequestHandler() {
191211
handler.setCacheControl(this.cacheControl);
192212
}
193213
handler.setUseLastModified(this.useLastModified);
214+
handler.setOptimizeLocations(this.optimizeLocations);
194215
if (this.mediaTypes != null) {
195216
handler.setMediaTypes(this.mediaTypes);
196217
}

spring-webflux/src/main/java/org/springframework/web/reactive/resource/ResourceWebHandler.java

Lines changed: 75 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -72,8 +72,8 @@
7272
* <p>This request handler may also be configured with a
7373
* {@link #setResourceResolvers(List) resourcesResolver} and
7474
* {@link #setResourceTransformers(List) resourceTransformer} chains to support
75-
* arbitrary resolution and transformation of resources being served. By default a
76-
* {@link PathResourceResolver} simply finds resources based on the configured
75+
* arbitrary resolution and transformation of resources being served. By default
76+
* a {@link PathResourceResolver} simply finds resources based on the configured
7777
* "locations". An application can configure additional resolvers and
7878
* transformers such as the {@link VersionResourceResolver} which can resolve
7979
* and prepare URLs for resources with a version in the URL.
@@ -85,6 +85,7 @@
8585
*
8686
* @author Rossen Stoyanchev
8787
* @author Brian Clozel
88+
* @author Juergen Hoeller
8889
* @since 5.0
8990
*/
9091
public class ResourceWebHandler implements WebHandler, InitializingBean {
@@ -94,6 +95,9 @@ public class ResourceWebHandler implements WebHandler, InitializingBean {
9495
private static final Log logger = LogFactory.getLog(ResourceWebHandler.class);
9596

9697

98+
@Nullable
99+
private ResourceLoader resourceLoader;
100+
97101
private final List<String> locationValues = new ArrayList<>(4);
98102

99103
private final List<Resource> locationResources = new ArrayList<>(4);
@@ -119,11 +123,18 @@ public class ResourceWebHandler implements WebHandler, InitializingBean {
119123
@Nullable
120124
private Map<String, MediaType> mediaTypes;
121125

122-
@Nullable
123-
private ResourceLoader resourceLoader;
124-
125126
private boolean useLastModified = true;
126127

128+
private boolean optimizeLocations = false;
129+
130+
131+
/**
132+
* Provide the ResourceLoader to load {@link #setLocationValues location values} with.
133+
* @since 5.1
134+
*/
135+
public void setResourceLoader(ResourceLoader resourceLoader) {
136+
this.resourceLoader = resourceLoader;
137+
}
127138

128139
/**
129140
* Accepts a list of String-based location values to be resolved into
@@ -161,9 +172,9 @@ public void setLocations(@Nullable List<Resource> locations) {
161172
* <p>Note that if {@link #setLocationValues(List) locationValues} are provided,
162173
* instead of loaded Resource-based locations, this method will return
163174
* empty until after initialization via {@link #afterPropertiesSet()}.
164-
* <p><strong>Note:</strong> As of 5.3.11 the list of locations is filtered
165-
* to exclude those that don't actually exist and therefore the list returned
166-
* from this method may be a subset of all given locations.
175+
* <p><strong>Note:</strong> As of 5.3.11 the list of locations may be filtered to
176+
* exclude those that don't actually exist and therefore the list returned from this
177+
* method may be a subset of all given locations. See {@link #setOptimizeLocations}.
167178
* @see #setLocationValues
168179
* @see #setLocations
169180
*/
@@ -212,6 +223,22 @@ public List<ResourceTransformer> getResourceTransformers() {
212223
return this.resourceTransformers;
213224
}
214225

226+
/**
227+
* Configure the {@link ResourceHttpMessageWriter} to use.
228+
* <p>By default a {@link ResourceHttpMessageWriter} will be configured.
229+
*/
230+
public void setResourceHttpMessageWriter(@Nullable ResourceHttpMessageWriter httpMessageWriter) {
231+
this.resourceHttpMessageWriter = httpMessageWriter;
232+
}
233+
234+
/**
235+
* Return the configured resource message writer.
236+
*/
237+
@Nullable
238+
public ResourceHttpMessageWriter getResourceHttpMessageWriter() {
239+
return this.resourceHttpMessageWriter;
240+
}
241+
215242
/**
216243
* Set the {@link org.springframework.http.CacheControl} instance to build
217244
* the Cache-Control HTTP response header.
@@ -230,19 +257,48 @@ public CacheControl getCacheControl() {
230257
}
231258

232259
/**
233-
* Configure the {@link ResourceHttpMessageWriter} to use.
234-
* <p>By default a {@link ResourceHttpMessageWriter} will be configured.
260+
* Set whether we should look at the {@link Resource#lastModified()}
261+
* when serving resources and use this information to drive {@code "Last-Modified"}
262+
* HTTP response headers.
263+
* <p>This option is enabled by default and should be turned off if the metadata of
264+
* the static files should be ignored.
265+
* @since 5.3
235266
*/
236-
public void setResourceHttpMessageWriter(@Nullable ResourceHttpMessageWriter httpMessageWriter) {
237-
this.resourceHttpMessageWriter = httpMessageWriter;
267+
public void setUseLastModified(boolean useLastModified) {
268+
this.useLastModified = useLastModified;
238269
}
239270

240271
/**
241-
* Return the configured resource message writer.
272+
* Return whether the {@link Resource#lastModified()} information is used
273+
* to drive HTTP responses when serving static resources.
274+
* @since 5.3
242275
*/
243-
@Nullable
244-
public ResourceHttpMessageWriter getResourceHttpMessageWriter() {
245-
return this.resourceHttpMessageWriter;
276+
public boolean isUseLastModified() {
277+
return this.useLastModified;
278+
}
279+
280+
/**
281+
* Set whether to optimize the specified locations through an existence
282+
* check on startup, filtering non-existing directories upfront so that
283+
* they do not have to be checked on every resource access.
284+
* <p>The default is {@code false}, for defensiveness against zip files
285+
* without directory entries which are unable to expose the existence of
286+
* a directory upfront. Switch this flag to {@code true} for optimized
287+
* access in case of a consistent jar layout with directory entries.
288+
* @since 5.3.13
289+
*/
290+
public void setOptimizeLocations(boolean optimizeLocations) {
291+
this.optimizeLocations = optimizeLocations;
292+
}
293+
294+
/**
295+
* Return whether to optimize the specified locations through an existence
296+
* check on startup, filtering non-existing directories upfront so that
297+
* they do not have to be checked on every resource access.
298+
* @since 5.3.13
299+
*/
300+
public boolean isOptimizeLocations() {
301+
return this.optimizeLocations;
246302
}
247303

248304
/**
@@ -269,36 +325,6 @@ public Map<String, MediaType> getMediaTypes() {
269325
return (this.mediaTypes != null ? this.mediaTypes : Collections.emptyMap());
270326
}
271327

272-
/**
273-
* Provide the ResourceLoader to load {@link #setLocationValues(List)
274-
* location values} with.
275-
* @since 5.1
276-
*/
277-
public void setResourceLoader(ResourceLoader resourceLoader) {
278-
this.resourceLoader = resourceLoader;
279-
}
280-
281-
/**
282-
* Return whether the {@link Resource#lastModified()} information is used
283-
* to drive HTTP responses when serving static resources.
284-
* @since 5.3
285-
*/
286-
public boolean isUseLastModified() {
287-
return this.useLastModified;
288-
}
289-
290-
/**
291-
* Set whether we should look at the {@link Resource#lastModified()}
292-
* when serving resources and use this information to drive {@code "Last-Modified"}
293-
* HTTP response headers.
294-
* <p>This option is enabled by default and should be turned off if the metadata of
295-
* the static files should be ignored.
296-
* @param useLastModified whether to use the resource last-modified information.
297-
* @since 5.3
298-
*/
299-
public void setUseLastModified(boolean useLastModified) {
300-
this.useLastModified = useLastModified;
301-
}
302328

303329
@Override
304330
public void afterPropertiesSet() throws Exception {
@@ -332,7 +358,9 @@ private void resolveResourceLocations() {
332358
}
333359
}
334360

335-
result = result.stream().filter(Resource::exists).collect(Collectors.toList());
361+
if (isOptimizeLocations()) {
362+
result = result.stream().filter(Resource::exists).collect(Collectors.toList());
363+
}
336364

337365
this.locationsToUse.clear();
338366
this.locationsToUse.addAll(result);

spring-webflux/src/test/java/org/springframework/web/reactive/resource/ResourceWebHandlerTests.java

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,7 @@ public class ResourceWebHandlerTests {
7474

7575
private ResourceWebHandler handler;
7676

77+
7778
@BeforeEach
7879
public void setup() throws Exception {
7980
List<Resource> locations = new ArrayList<>(2);
@@ -253,7 +254,7 @@ public void getResourceFromFileSystem() throws Exception {
253254
assertResponseBody(exchange, "h1 { color:red; }");
254255
}
255256

256-
@Test // gh-27538
257+
@Test // gh-27538, gh-27624
257258
public void filterNonExistingLocations() throws Exception {
258259
List<Resource> inputLocations = Arrays.asList(
259260
new ClassPathResource("test/", getClass()),
@@ -262,6 +263,7 @@ public void filterNonExistingLocations() throws Exception {
262263

263264
ResourceWebHandler handler = new ResourceWebHandler();
264265
handler.setLocations(inputLocations);
266+
handler.setOptimizeLocations(true);
265267
handler.afterPropertiesSet();
266268

267269
List<Resource> actual = handler.getLocations();

spring-webmvc/src/main/java/org/springframework/web/servlet/config/annotation/ResourceHandlerRegistration.java

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,8 @@ public class ResourceHandlerRegistration {
5555

5656
private boolean useLastModified = true;
5757

58+
private boolean optimizeLocations = false;
59+
5860

5961
/**
6062
* Create a {@link ResourceHandlerRegistration} instance.
@@ -130,15 +132,33 @@ public ResourceHandlerRegistration setCacheControl(CacheControl cacheControl) {
130132
/**
131133
* Set whether the {@link Resource#lastModified()} information should be used to drive HTTP responses.
132134
* <p>This configuration is set to {@code true} by default.
133-
* @param useLastModified whether the "last modified" resource information should be used.
135+
* @param useLastModified whether the "last modified" resource information should be used
134136
* @return the same {@link ResourceHandlerRegistration} instance, for chained method invocation
135137
* @since 5.3
138+
* @see ResourceHttpRequestHandler#setUseLastModified
136139
*/
137140
public ResourceHandlerRegistration setUseLastModified(boolean useLastModified) {
138141
this.useLastModified = useLastModified;
139142
return this;
140143
}
141144

145+
/**
146+
* Set whether to optimize the specified locations through an existence check on startup,
147+
* filtering non-existing directories upfront so that they do not have to be checked
148+
* on every resource access.
149+
* <p>The default is {@code false}, for defensiveness against zip files without directory
150+
* entries which are unable to expose the existence of a directory upfront. Switch this flag to
151+
* {@code true} for optimized access in case of a consistent jar layout with directory entries.
152+
* @param optimizeLocations whether to optimize the locations through an existence check on startup
153+
* @return the same {@link ResourceHandlerRegistration} instance, for chained method invocation
154+
* @since 5.3.13
155+
* @see ResourceHttpRequestHandler#setOptimizeLocations
156+
*/
157+
public ResourceHandlerRegistration setOptimizeLocations(boolean optimizeLocations) {
158+
this.optimizeLocations = optimizeLocations;
159+
return this;
160+
}
161+
142162
/**
143163
* Configure a chain of resource resolvers and transformers to use. This
144164
* can be useful, for example, to apply a version strategy to resource URLs.
@@ -204,6 +224,7 @@ else if (this.cachePeriod != null) {
204224
handler.setCacheSeconds(this.cachePeriod);
205225
}
206226
handler.setUseLastModified(this.useLastModified);
227+
handler.setOptimizeLocations(this.optimizeLocations);
207228
return handler;
208229
}
209230

0 commit comments

Comments
 (0)