Polish + minor refactoring of ResourceUrlProvider
This commit is contained in:
parent
118d1470fd
commit
378c72e9b6
|
|
@ -64,7 +64,7 @@ public interface ServerHttpResponse extends ReactiveHttpOutputMessage {
|
|||
* HTML template libraries to use consistently for all URLs emitted by
|
||||
* the application. Doing so enables the registration of URL encoders via
|
||||
* {@link #registerUrlEncoder} that can insert an id for authentication,
|
||||
* a nonce for CSRF protection, a version for a static resource, etc.
|
||||
* a nonce for CSRF protection, or other.
|
||||
* @param url the URL to encode
|
||||
* @return the encoded URL or the same
|
||||
*/
|
||||
|
|
|
|||
|
|
@ -76,7 +76,7 @@ public abstract class ResourceTransformerSupport implements ResourceTransformer
|
|||
if (resourcePath.startsWith("/")) {
|
||||
// full resource path
|
||||
ResourceUrlProvider urlProvider = getResourceUrlProvider();
|
||||
return (urlProvider != null ? urlProvider.getForRequestUrl(exchange, resourcePath) : Mono.empty());
|
||||
return (urlProvider != null ? urlProvider.getForUriString(resourcePath, exchange) : Mono.empty());
|
||||
}
|
||||
else {
|
||||
// try resolving as relative path
|
||||
|
|
|
|||
|
|
@ -33,8 +33,6 @@ import org.springframework.context.event.ContextRefreshedEvent;
|
|||
import org.springframework.core.annotation.AnnotationAwareOrderComparator;
|
||||
import org.springframework.http.server.reactive.PathContainer;
|
||||
import org.springframework.http.server.reactive.ServerHttpRequest;
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.StringUtils;
|
||||
import org.springframework.web.reactive.handler.SimpleUrlHandlerMapping;
|
||||
import org.springframework.web.server.ServerWebExchange;
|
||||
|
|
@ -57,50 +55,49 @@ public class ResourceUrlProvider implements ApplicationListener<ContextRefreshed
|
|||
private static final Log logger = LogFactory.getLog(ResourceUrlProvider.class);
|
||||
|
||||
|
||||
private final PathPatternParser patternParser;
|
||||
private final PathPatternParser patternParser = new PathPatternParser();
|
||||
|
||||
private final Map<PathPattern, ResourceWebHandler> handlerMap = new LinkedHashMap<>();
|
||||
|
||||
private boolean autodetect = true;
|
||||
|
||||
|
||||
public ResourceUrlProvider() {
|
||||
this(new PathPatternParser());
|
||||
}
|
||||
|
||||
public ResourceUrlProvider(PathPatternParser patternParser) {
|
||||
Assert.notNull(patternParser, "'patternParser' is required.");
|
||||
this.patternParser = patternParser;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Return a read-only view of the resource handler mappings either manually
|
||||
* configured or auto-detected when the Spring {@code ApplicationContext}
|
||||
* is refreshed.
|
||||
* configured or auto-detected from Spring configuration.
|
||||
*/
|
||||
public Map<PathPattern, ResourceWebHandler> getHandlerMap() {
|
||||
return Collections.unmodifiableMap(this.handlerMap);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Return {@code false} if resource mappings were manually configured,
|
||||
* {@code true} otherwise.
|
||||
* Manually configure resource handler mappings.
|
||||
* <p><strong>Note:</strong> by default resource mappings are auto-detected
|
||||
* from the Spring {@code ApplicationContext}. If this property is used,
|
||||
* auto-detection is turned off.
|
||||
*/
|
||||
public boolean isAutodetect() {
|
||||
return this.autodetect;
|
||||
public void registerHandlers(Map<String, ResourceWebHandler> handlerMap) {
|
||||
this.handlerMap.clear();
|
||||
handlerMap.forEach((rawPattern, resourceWebHandler) -> {
|
||||
rawPattern = prependLeadingSlash(rawPattern);
|
||||
PathPattern pattern = this.patternParser.parse(rawPattern);
|
||||
this.handlerMap.put(pattern, resourceWebHandler);
|
||||
});
|
||||
}
|
||||
|
||||
private static String prependLeadingSlash(String pattern) {
|
||||
if (StringUtils.hasLength(pattern) && !pattern.startsWith("/")) {
|
||||
return "/" + pattern;
|
||||
}
|
||||
else {
|
||||
return pattern;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onApplicationEvent(ContextRefreshedEvent event) {
|
||||
if (isAutodetect()) {
|
||||
this.handlerMap.clear();
|
||||
if (this.handlerMap.isEmpty()) {
|
||||
detectResourceHandlers(event.getApplicationContext());
|
||||
if (!this.handlerMap.isEmpty()) {
|
||||
this.autodetect = false;
|
||||
}
|
||||
else if(logger.isDebugEnabled()) {
|
||||
if(logger.isDebugEnabled()) {
|
||||
logger.debug("No resource handling mappings found");
|
||||
}
|
||||
}
|
||||
|
|
@ -110,10 +107,10 @@ public class ResourceUrlProvider implements ApplicationListener<ContextRefreshed
|
|||
logger.debug("Looking for resource handler mappings");
|
||||
|
||||
Map<String, SimpleUrlHandlerMapping> map = context.getBeansOfType(SimpleUrlHandlerMapping.class);
|
||||
List<SimpleUrlHandlerMapping> handlerMappings = new ArrayList<>(map.values());
|
||||
AnnotationAwareOrderComparator.sort(handlerMappings);
|
||||
List<SimpleUrlHandlerMapping> mappings = new ArrayList<>(map.values());
|
||||
AnnotationAwareOrderComparator.sort(mappings);
|
||||
|
||||
handlerMappings.forEach(mapping -> {
|
||||
mappings.forEach(mapping -> {
|
||||
mapping.getHandlerMap().forEach((pattern, handler) -> {
|
||||
if (handler instanceof ResourceWebHandler) {
|
||||
ResourceWebHandler resourceHandler = (ResourceWebHandler) handler;
|
||||
|
|
@ -128,85 +125,45 @@ public class ResourceUrlProvider implements ApplicationListener<ContextRefreshed
|
|||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Manually configure the resource mappings.
|
||||
* <p><strong>Note:</strong> by default resource mappings are auto-detected
|
||||
* from the Spring {@code ApplicationContext}. However if this property is
|
||||
* used, the auto-detection is turned off.
|
||||
*/
|
||||
public void registerHandlers(@Nullable Map<String, ResourceWebHandler> handlerMap) {
|
||||
if (handlerMap == null) {
|
||||
return;
|
||||
}
|
||||
this.handlerMap.clear();
|
||||
handlerMap.forEach((rawPattern, resourceWebHandler) -> {
|
||||
rawPattern = prependLeadingSlash(rawPattern);
|
||||
PathPattern pattern = this.patternParser.parse(rawPattern);
|
||||
this.handlerMap.put(pattern, resourceWebHandler);
|
||||
});
|
||||
this.autodetect = false;
|
||||
}
|
||||
|
||||
private static String prependLeadingSlash(String pattern) {
|
||||
if (StringUtils.hasLength(pattern) && !pattern.startsWith("/")) {
|
||||
return "/" + pattern;
|
||||
}
|
||||
else {
|
||||
return pattern;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* A variation on {@link #getForLookupPath(PathContainer)} that accepts a
|
||||
* full request URL path and returns the full request URL path to expose
|
||||
* for public use.
|
||||
* Get the public resource URL for the given URI string.
|
||||
* <p>The URI string is expected to be a path and if it contains a query or
|
||||
* fragment those will be preserved in the resulting public resource URL.
|
||||
* @param uriString the URI string to transform
|
||||
* @param exchange the current exchange
|
||||
* @param requestUrl the request URL path to resolve
|
||||
* @return the resolved public URL path, or empty if unresolved
|
||||
* @return the resolved public resource URL path, or empty if unresolved
|
||||
*/
|
||||
public final Mono<String> getForRequestUrl(ServerWebExchange exchange, String requestUrl) {
|
||||
public final Mono<String> getForUriString(String uriString, ServerWebExchange exchange) {
|
||||
if (logger.isTraceEnabled()) {
|
||||
logger.trace("Getting resource URL for request URL \"" + requestUrl + "\"");
|
||||
logger.trace("Getting resource URL for request URL \"" + uriString + "\"");
|
||||
}
|
||||
ServerHttpRequest request = exchange.getRequest();
|
||||
int queryIndex = getQueryIndex(requestUrl);
|
||||
String lookupPath = requestUrl.substring(0, queryIndex);
|
||||
String query = requestUrl.substring(queryIndex);
|
||||
int queryIndex = getQueryIndex(uriString);
|
||||
String lookupPath = uriString.substring(0, queryIndex);
|
||||
String query = uriString.substring(queryIndex);
|
||||
PathContainer parsedLookupPath = PathContainer.parseUrlPath(lookupPath);
|
||||
return getForLookupPath(parsedLookupPath).map(resolvedPath ->
|
||||
if (logger.isTraceEnabled()) {
|
||||
logger.trace("Getting resource URL for lookup path \"" + lookupPath + "\"");
|
||||
}
|
||||
return resolveResourceUrl(parsedLookupPath).map(resolvedPath ->
|
||||
request.getPath().contextPath().value() + resolvedPath + query);
|
||||
}
|
||||
|
||||
private int getQueryIndex(String lookupPath) {
|
||||
int suffixIndex = lookupPath.length();
|
||||
int queryIndex = lookupPath.indexOf("?");
|
||||
private int getQueryIndex(String path) {
|
||||
int suffixIndex = path.length();
|
||||
int queryIndex = path.indexOf("?");
|
||||
if (queryIndex > 0) {
|
||||
suffixIndex = queryIndex;
|
||||
}
|
||||
int hashIndex = lookupPath.indexOf("#");
|
||||
int hashIndex = path.indexOf("#");
|
||||
if (hashIndex > 0) {
|
||||
suffixIndex = Math.min(suffixIndex, hashIndex);
|
||||
}
|
||||
return suffixIndex;
|
||||
}
|
||||
|
||||
/**
|
||||
* Compare the given path against configured resource handler mappings and
|
||||
* if a match is found use the {@code ResourceResolver} chain of the matched
|
||||
* {@code ResourceHttpRequestHandler} to resolve the URL path to expose for
|
||||
* public use.
|
||||
* <p>It is expected that the given path is what Spring uses for
|
||||
* request mapping purposes.
|
||||
* <p>If several handler mappings match, the handler used will be the one
|
||||
* configured with the most specific pattern.
|
||||
* @param lookupPath the lookup path to check
|
||||
* @return the resolved public URL path, or empty if unresolved
|
||||
*/
|
||||
public final Mono<String> getForLookupPath(PathContainer lookupPath) {
|
||||
if (logger.isTraceEnabled()) {
|
||||
logger.trace("Getting resource URL for lookup path \"" + lookupPath + "\"");
|
||||
}
|
||||
private Mono<String> resolveResourceUrl(PathContainer lookupPath) {
|
||||
return this.handlerMap.entrySet().stream()
|
||||
.filter(entry -> entry.getKey().matches(lookupPath))
|
||||
.sorted(Comparator.comparing(Map.Entry::getKey))
|
||||
|
|
|
|||
|
|
@ -32,7 +32,6 @@ import org.springframework.context.annotation.Bean;
|
|||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.core.io.ClassPathResource;
|
||||
import org.springframework.core.io.Resource;
|
||||
import org.springframework.http.server.reactive.PathContainer;
|
||||
import org.springframework.mock.http.server.reactive.test.MockServerHttpRequest;
|
||||
import org.springframework.mock.web.test.MockServletContext;
|
||||
import org.springframework.web.context.support.AnnotationConfigWebApplicationContext;
|
||||
|
|
@ -41,7 +40,6 @@ import org.springframework.web.server.ServerWebExchange;
|
|||
import org.springframework.web.util.pattern.PathPattern;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertFalse;
|
||||
import static org.junit.Assert.assertThat;
|
||||
|
||||
|
||||
|
|
@ -74,9 +72,10 @@ public class ResourceUrlProviderTests {
|
|||
|
||||
@Test
|
||||
public void getStaticResourceUrl() {
|
||||
PathContainer path = PathContainer.parsePath("/resources/foo.css");
|
||||
String url = this.urlProvider.getForLookupPath(path).block(Duration.ofSeconds(5));
|
||||
assertEquals("/resources/foo.css", url);
|
||||
ServerWebExchange exchange = MockServerHttpRequest.get("/").toExchange();
|
||||
String uriString = "/resources/foo.css";
|
||||
String actual = this.urlProvider.getForUriString(uriString, exchange).block(Duration.ofSeconds(5));
|
||||
assertEquals(uriString, actual);
|
||||
}
|
||||
|
||||
@Test // SPR-13374
|
||||
|
|
@ -84,11 +83,11 @@ public class ResourceUrlProviderTests {
|
|||
ServerWebExchange exchange = MockServerHttpRequest.get("/").toExchange();
|
||||
|
||||
String url = "/resources/foo.css?foo=bar&url=http://example.org";
|
||||
String resolvedUrl = this.urlProvider.getForRequestUrl(exchange, url).block(Duration.ofSeconds(5));
|
||||
String resolvedUrl = this.urlProvider.getForUriString(url, exchange).block(Duration.ofSeconds(5));
|
||||
assertEquals(url, resolvedUrl);
|
||||
|
||||
url = "/resources/foo.css#hash";
|
||||
resolvedUrl = this.urlProvider.getForRequestUrl(exchange, url).block(Duration.ofSeconds(5));
|
||||
resolvedUrl = this.urlProvider.getForUriString(url, exchange).block(Duration.ofSeconds(5));
|
||||
assertEquals(url, resolvedUrl);
|
||||
}
|
||||
|
||||
|
|
@ -104,8 +103,9 @@ public class ResourceUrlProviderTests {
|
|||
resolvers.add(new PathResourceResolver());
|
||||
this.handler.setResourceResolvers(resolvers);
|
||||
|
||||
PathContainer path = PathContainer.parsePath("/resources/foo.css");
|
||||
String url = this.urlProvider.getForLookupPath(path).block(Duration.ofSeconds(5));
|
||||
ServerWebExchange exchange = MockServerHttpRequest.get("/").toExchange();
|
||||
String path = "/resources/foo.css";
|
||||
String url = this.urlProvider.getForUriString(path, exchange).block(Duration.ofSeconds(5));
|
||||
assertEquals("/resources/foo-e36d2e05253c6c7085a91522ce43a0b4.css", url);
|
||||
}
|
||||
|
||||
|
|
@ -126,8 +126,9 @@ public class ResourceUrlProviderTests {
|
|||
this.handlerMap.put("/resources/*.css", otherHandler);
|
||||
this.urlProvider.registerHandlers(this.handlerMap);
|
||||
|
||||
PathContainer path = PathContainer.parsePath("/resources/foo.css");
|
||||
String url = this.urlProvider.getForLookupPath(path).block(Duration.ofSeconds(5));
|
||||
ServerWebExchange exchange = MockServerHttpRequest.get("/").toExchange();
|
||||
String path = "/resources/foo.css";
|
||||
String url = this.urlProvider.getForUriString(path, exchange).block(Duration.ofSeconds(5));
|
||||
assertEquals("/resources/foo-e36d2e05253c6c7085a91522ce43a0b4.css", url);
|
||||
}
|
||||
|
||||
|
|
@ -140,7 +141,6 @@ public class ResourceUrlProviderTests {
|
|||
|
||||
ResourceUrlProvider urlProviderBean = context.getBean(ResourceUrlProvider.class);
|
||||
assertThat(urlProviderBean.getHandlerMap(), Matchers.hasKey(pattern("/resources/**")));
|
||||
assertFalse(urlProviderBean.isAutodetect());
|
||||
}
|
||||
|
||||
|
||||
|
|
|
|||
Loading…
Reference in New Issue