From 4dac8339ffb4705554458bf2c32d81fc16b61f43 Mon Sep 17 00:00:00 2001 From: Rossen Stoyanchev Date: Wed, 13 Oct 2021 14:37:03 +0100 Subject: [PATCH] Filter non-existing static resource locations Same as a2c52a97babdb927d809ad55c696c42d160d4fff, on the WebFlux side. See gh-27538 --- .../reactive/resource/ResourceWebHandler.java | 52 +++++++++++-------- .../resource/ResourceWebHandlerTests.java | 17 ++++++ .../resource/ResourceHttpRequestHandler.java | 4 ++ 3 files changed, 52 insertions(+), 21 deletions(-) diff --git a/spring-webflux/src/main/java/org/springframework/web/reactive/resource/ResourceWebHandler.java b/spring-webflux/src/main/java/org/springframework/web/reactive/resource/ResourceWebHandler.java index be4edf688ce2..2361d1532895 100644 --- a/spring-webflux/src/main/java/org/springframework/web/reactive/resource/ResourceWebHandler.java +++ b/spring-webflux/src/main/java/org/springframework/web/reactive/resource/ResourceWebHandler.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2020 the original author or authors. + * Copyright 2002-2021 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -96,7 +96,9 @@ public class ResourceWebHandler implements WebHandler, InitializingBean { private final List locationValues = new ArrayList<>(4); - private final List locations = new ArrayList<>(4); + private final List locationResources = new ArrayList<>(4); + + private final List locationsToUse = new ArrayList<>(4); private final List resourceResolvers = new ArrayList<>(4); @@ -147,9 +149,9 @@ public List getLocationValues() { * for serving static resources. */ public void setLocations(@Nullable List locations) { - this.locations.clear(); + this.locationResources.clear(); if (locations != null) { - this.locations.addAll(locations); + this.locationResources.addAll(locations); } } @@ -159,11 +161,18 @@ public void setLocations(@Nullable List locations) { *

Note that if {@link #setLocationValues(List) locationValues} are provided, * instead of loaded Resource-based locations, this method will return * empty until after initialization via {@link #afterPropertiesSet()}. + *

Note: As of 5.3.11 the list of locations is filtered + * to exclude those that don't actually exist and therefore the list returned + * from this method may be a subset of all given locations. * @see #setLocationValues * @see #setLocations */ public List getLocations() { - return this.locations; + if (this.locationsToUse.isEmpty()) { + // Possibly not yet initialized, return only what we have so far + return this.locationResources; + } + return this.locationsToUse; } /** @@ -295,7 +304,7 @@ public void setUseLastModified(boolean useLastModified) { public void afterPropertiesSet() throws Exception { resolveResourceLocations(); - if (logger.isWarnEnabled() && CollectionUtils.isEmpty(this.locations)) { + if (logger.isWarnEnabled() && CollectionUtils.isEmpty(getLocations())) { logger.warn("Locations list is empty. No resources will be served unless a " + "custom ResourceResolver is configured as an alternative to PathResourceResolver."); } @@ -316,21 +325,22 @@ public void afterPropertiesSet() throws Exception { } private void resolveResourceLocations() { - if (CollectionUtils.isEmpty(this.locationValues)) { - return; - } - else if (!CollectionUtils.isEmpty(this.locations)) { - throw new IllegalArgumentException("Please set either Resource-based \"locations\" or " + - "String-based \"locationValues\", but not both."); + List result = new ArrayList<>(this.locationResources); + + if (!this.locationValues.isEmpty()) { + Assert.notNull(this.resourceLoader, + "ResourceLoader is required when \"locationValues\" are configured."); + Assert.isTrue(CollectionUtils.isEmpty(this.locationResources), "Please set " + + "either Resource-based \"locations\" or String-based \"locationValues\", but not both."); + for (String location : this.locationValues) { + result.add(this.resourceLoader.getResource(location)); + } } - Assert.notNull(this.resourceLoader, - "ResourceLoader is required when \"locationValues\" are configured."); + result = result.stream().filter(Resource::exists).collect(Collectors.toList()); - for (String location : this.locationValues) { - Resource resource = this.resourceLoader.getResource(location); - this.locations.add(resource); - } + this.locationsToUse.clear(); + this.locationsToUse.addAll(result); } /** @@ -339,7 +349,7 @@ else if (!CollectionUtils.isEmpty(this.locations)) { * match the {@link #setLocations locations} configured on this class. */ protected void initAllowedLocations() { - if (CollectionUtils.isEmpty(this.locations)) { + if (CollectionUtils.isEmpty(getLocations())) { if (logger.isInfoEnabled()) { logger.info("Locations list is empty. No resources will be served unless a " + "custom ResourceResolver is configured as an alternative to PathResourceResolver."); @@ -618,8 +628,8 @@ private Object formatLocations() { if (!this.locationValues.isEmpty()) { return this.locationValues.stream().collect(Collectors.joining("\", \"", "[\"", "\"]")); } - else if (!this.locations.isEmpty()) { - return "[" + this.locations.toString() + if (!getLocations().isEmpty()) { + return "[" + getLocations().toString() .replaceAll("class path resource", "Classpath") .replaceAll("ServletContext resource", "ServletContext") + "]"; } diff --git a/spring-webflux/src/test/java/org/springframework/web/reactive/resource/ResourceWebHandlerTests.java b/spring-webflux/src/test/java/org/springframework/web/reactive/resource/ResourceWebHandlerTests.java index 691afde68ee3..b668172197d6 100644 --- a/spring-webflux/src/test/java/org/springframework/web/reactive/resource/ResourceWebHandlerTests.java +++ b/spring-webflux/src/test/java/org/springframework/web/reactive/resource/ResourceWebHandlerTests.java @@ -253,6 +253,23 @@ public void getResourceFromFileSystem() throws Exception { assertResponseBody(exchange, "h1 { color:red; }"); } + @Test // gh-27538 + public void filterNonExistingLocations() throws Exception { + List inputLocations = Arrays.asList( + new ClassPathResource("test/", getClass()), + new ClassPathResource("testalternatepath/", getClass()), + new ClassPathResource("nosuchpath/", getClass())); + + ResourceWebHandler handler = new ResourceWebHandler(); + handler.setLocations(inputLocations); + handler.afterPropertiesSet(); + + List actual = handler.getLocations(); + assertThat(actual).hasSize(2); + assertThat(actual.get(0).getURL().toString()).endsWith("test/"); + assertThat(actual.get(1).getURL().toString()).endsWith("testalternatepath/"); + } + @Test // SPR-14577 public void getMediaTypeWithFavorPathExtensionOff() throws Exception { List paths = Collections.singletonList(new ClassPathResource("test/", getClass())); diff --git a/spring-webmvc/src/main/java/org/springframework/web/servlet/resource/ResourceHttpRequestHandler.java b/spring-webmvc/src/main/java/org/springframework/web/servlet/resource/ResourceHttpRequestHandler.java index 0831a2a2f29b..6e556e9af5fa 100644 --- a/spring-webmvc/src/main/java/org/springframework/web/servlet/resource/ResourceHttpRequestHandler.java +++ b/spring-webmvc/src/main/java/org/springframework/web/servlet/resource/ResourceHttpRequestHandler.java @@ -189,6 +189,9 @@ public void setLocations(List locations) { * locations provided via {@link #setLocations(List) setLocations}. *

Note that the returned list is fully initialized only after * initialization via {@link #afterPropertiesSet()}. + *

Note: As of 5.3.11 the list of locations is filtered + * to exclude those that don't actually exist and therefore the list returned + * from this method may be a subset of all given locations. * @see #setLocationValues * @see #setLocations */ @@ -467,6 +470,7 @@ private void resolveResourceLocations() { result.addAll(this.locationResources); result = result.stream().filter(Resource::exists).collect(Collectors.toList()); + this.locationsToUse.clear(); this.locationsToUse.addAll(result); }