spring ResourceUrlProvider 源码

  • 2022-08-08
  • 浏览 (388)

spring ResourceUrlProvider 代码

文件路径:/spring-webflux/src/main/java/org/springframework/web/reactive/resource/ResourceUrlProvider.java

/*
 * 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.
 * You may obtain a copy of the License at
 *
 *      https://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package org.springframework.web.reactive.resource;

import java.util.ArrayList;
import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import reactor.core.publisher.Mono;

import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.context.ApplicationListener;
import org.springframework.context.event.ContextRefreshedEvent;
import org.springframework.core.annotation.AnnotationAwareOrderComparator;
import org.springframework.http.server.PathContainer;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.lang.Nullable;
import org.springframework.util.StringUtils;
import org.springframework.web.reactive.handler.SimpleUrlHandlerMapping;
import org.springframework.web.server.ServerWebExchange;
import org.springframework.web.util.pattern.PathPattern;
import org.springframework.web.util.pattern.PathPatternParser;

/**
 * A central component to use to obtain the public URL path that clients should
 * use to access a static resource.
 *
 * <p>This class is aware of Spring WebFlux handler mappings used to serve static
 * resources and uses the {@code ResourceResolver} chains of the configured
 * {@code ResourceHttpRequestHandler}s to make its decisions.
 *
 * @author Rossen Stoyanchev
 * @author Brian Clozel
 * @since 5.0
 */
public class ResourceUrlProvider implements ApplicationListener<ContextRefreshedEvent>, ApplicationContextAware {

	private static final Log logger = LogFactory.getLog(ResourceUrlProvider.class);

	private final Map<PathPattern, ResourceWebHandler> handlerMap = new LinkedHashMap<>();

	@Nullable
	private ApplicationContext applicationContext;


	@Override
	public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
		this.applicationContext = applicationContext;
	}

	/**
	 * Return a read-only view of the resource handler mappings either manually
	 * configured or auto-detected from Spring configuration.
	 */
	public Map<PathPattern, ResourceWebHandler> getHandlerMap() {
		return Collections.unmodifiableMap(this.handlerMap);
	}


	/**
	 * 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 void registerHandlers(Map<String, ResourceWebHandler> handlerMap) {
		this.handlerMap.clear();
		handlerMap.forEach((rawPattern, resourceWebHandler) -> {
			rawPattern = prependLeadingSlash(rawPattern);
			PathPattern pattern = PathPatternParser.defaultInstance.parse(rawPattern);
			this.handlerMap.put(pattern, resourceWebHandler);
		});
	}

	@Override
	public void onApplicationEvent(ContextRefreshedEvent event) {
		if (this.applicationContext == event.getApplicationContext() && this.handlerMap.isEmpty()) {
			detectResourceHandlers(this.applicationContext);
		}
	}

	private void detectResourceHandlers(ApplicationContext context) {
		Map<String, SimpleUrlHandlerMapping> beans = context.getBeansOfType(SimpleUrlHandlerMapping.class);
		List<SimpleUrlHandlerMapping> mappings = new ArrayList<>(beans.values());
		AnnotationAwareOrderComparator.sort(mappings);

		mappings.forEach(mapping ->
			mapping.getHandlerMap().forEach((pattern, handler) -> {
				if (handler instanceof ResourceWebHandler resourceHandler) {
					this.handlerMap.put(pattern, resourceHandler);
				}
			}));

		if (this.handlerMap.isEmpty()) {
			logger.trace("No resource handling mappings found");
		}
	}


	/**
	 * 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
	 * @return the resolved public resource URL path, or empty if unresolved
	 */
	public final Mono<String> getForUriString(String uriString, ServerWebExchange exchange) {
		ServerHttpRequest request = exchange.getRequest();
		int queryIndex = getQueryIndex(uriString);
		String lookupPath = uriString.substring(0, queryIndex);
		String query = uriString.substring(queryIndex);
		PathContainer parsedLookupPath = PathContainer.parsePath(lookupPath);

		return resolveResourceUrl(exchange, parsedLookupPath).map(resolvedPath ->
				request.getPath().contextPath().value() + resolvedPath + query);
	}

	private int getQueryIndex(String path) {
		int suffixIndex = path.length();
		int queryIndex = path.indexOf('?');
		if (queryIndex > 0) {
			suffixIndex = queryIndex;
		}
		int hashIndex = path.indexOf('#');
		if (hashIndex > 0) {
			suffixIndex = Math.min(suffixIndex, hashIndex);
		}
		return suffixIndex;
	}

	private Mono<String> resolveResourceUrl(ServerWebExchange exchange, PathContainer lookupPath) {
		return this.handlerMap.entrySet().stream()
				.filter(entry -> entry.getKey().matches(lookupPath))
				.min((entry1, entry2) ->
						PathPattern.SPECIFICITY_COMPARATOR.compare(entry1.getKey(), entry2.getKey()))
				.map(entry -> {
					PathContainer path = entry.getKey().extractPathWithinPattern(lookupPath);
					int endIndex = lookupPath.elements().size() - path.elements().size();
					PathContainer mapping = lookupPath.subPath(0, endIndex);
					ResourceWebHandler handler = entry.getValue();
					List<ResourceResolver> resolvers = handler.getResourceResolvers();
					ResourceResolverChain chain = new DefaultResourceResolverChain(resolvers);
					return chain.resolveUrlPath(path.value(), handler.getLocations())
							.map(resolvedPath -> mapping.value() + resolvedPath);
				})
				.orElseGet(() ->{
					if (logger.isTraceEnabled()) {
						logger.trace(exchange.getLogPrefix() + "No match for \"" + lookupPath + "\"");
					}
					return Mono.empty();
				});
	}


	private static String prependLeadingSlash(String pattern) {
		if (StringUtils.hasLength(pattern) && !pattern.startsWith("/")) {
			return "/" + pattern;
		}
		else {
			return pattern;
		}
	}

}

相关信息

spring 源码目录

相关文章

spring AbstractFileNameVersionStrategy 源码

spring AbstractPrefixVersionStrategy 源码

spring AbstractResourceResolver 源码

spring CachingResourceResolver 源码

spring CachingResourceTransformer 源码

spring ContentVersionStrategy 源码

spring CssLinkResourceTransformer 源码

spring DefaultResourceResolverChain 源码

spring DefaultResourceTransformerChain 源码

spring EncodedResourceResolver 源码

0  赞