ResourceWebHandler uses MediaTypeFactory directly
There is no need for ResourceWebHandler to go through the PathExtensionContentTypeResolver when MediaTypeFactory makes it easy to perform such lookups for a given Resource. This does not support any extensions explicitly registered through a WebFluxConfigurer but it would be easy enough to pass those into ResourceWebHandler as a simple Map<String, MediaType>, should the need arise. Issue: SPR-15639
This commit is contained in:
@@ -20,11 +20,9 @@ import java.util.Locale;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
|
||||
import org.springframework.core.io.Resource;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.http.MediaTypeFactory;
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.StringUtils;
|
||||
import org.springframework.web.server.NotAcceptableStatusException;
|
||||
import org.springframework.web.server.ServerWebExchange;
|
||||
@@ -104,26 +102,4 @@ public class PathExtensionContentTypeResolver extends AbstractMappingContentType
|
||||
throw new NotAcceptableStatusException(getAllMediaTypes());
|
||||
}
|
||||
|
||||
/**
|
||||
* A public method exposing the knowledge of the path extension resolver to
|
||||
* determine the media type for a given {@link Resource}. First it checks
|
||||
* the explicitly registered mappings and then falls back on {@link MediaTypeFactory}.
|
||||
* @param resource the resource
|
||||
* @return the MediaType for the extension, or {@code null} if none determined
|
||||
*/
|
||||
@Nullable
|
||||
public MediaType resolveMediaTypeForResource(Resource resource) {
|
||||
Assert.notNull(resource, "Resource must not be null");
|
||||
MediaType mediaType = null;
|
||||
String filename = resource.getFilename();
|
||||
String extension = StringUtils.getFilenameExtension(filename);
|
||||
if (extension != null) {
|
||||
mediaType = getMediaType(extension);
|
||||
}
|
||||
if (mediaType == null) {
|
||||
mediaType = MediaTypeFactory.getMediaType(filename).orElse(null);
|
||||
}
|
||||
return mediaType;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -26,7 +26,6 @@ import org.springframework.beans.factory.BeanInitializationException;
|
||||
import org.springframework.context.ApplicationContext;
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.web.reactive.accept.CompositeContentTypeResolver;
|
||||
import org.springframework.web.reactive.handler.AbstractHandlerMapping;
|
||||
import org.springframework.web.reactive.handler.SimpleUrlHandlerMapping;
|
||||
import org.springframework.web.reactive.resource.ResourceWebHandler;
|
||||
@@ -56,8 +55,6 @@ public class ResourceHandlerRegistry {
|
||||
|
||||
private final ApplicationContext applicationContext;
|
||||
|
||||
private final CompositeContentTypeResolver contentTypeResolver;
|
||||
|
||||
private final List<ResourceHandlerRegistration> registrations = new ArrayList<>();
|
||||
|
||||
private int order = Integer.MAX_VALUE -1;
|
||||
@@ -68,20 +65,9 @@ public class ResourceHandlerRegistry {
|
||||
* @param applicationContext the Spring application context
|
||||
*/
|
||||
public ResourceHandlerRegistry(ApplicationContext applicationContext) {
|
||||
this(applicationContext, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new resource handler registry for the given application context.
|
||||
* @param applicationContext the Spring application context
|
||||
* @param contentTypeResolver the content type resolver to use
|
||||
*/
|
||||
public ResourceHandlerRegistry(ApplicationContext applicationContext,
|
||||
@Nullable CompositeContentTypeResolver contentTypeResolver) {
|
||||
|
||||
Assert.notNull(applicationContext, "ApplicationContext is required");
|
||||
this.applicationContext = applicationContext;
|
||||
this.contentTypeResolver = contentTypeResolver;
|
||||
}
|
||||
|
||||
|
||||
@@ -133,15 +119,12 @@ public class ResourceHandlerRegistry {
|
||||
if (this.registrations.isEmpty()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
Map<String, WebHandler> urlMap = new LinkedHashMap<>();
|
||||
for (ResourceHandlerRegistration registration : this.registrations) {
|
||||
for (String pathPattern : registration.getPathPatterns()) {
|
||||
ResourceWebHandler handler = registration.getRequestHandler();
|
||||
handler.setContentTypeResolver(this.contentTypeResolver);
|
||||
try {
|
||||
handler.afterPropertiesSet();
|
||||
handler.afterSingletonsInstantiated();
|
||||
}
|
||||
catch (Exception ex) {
|
||||
throw new BeanInitializationException("Failed to init ResourceHttpRequestHandler", ex);
|
||||
@@ -149,7 +132,6 @@ public class ResourceHandlerRegistry {
|
||||
urlMap.put(pathPattern, handler);
|
||||
}
|
||||
}
|
||||
|
||||
SimpleUrlHandlerMapping handlerMapping = new SimpleUrlHandlerMapping();
|
||||
handlerMapping.setOrder(this.order);
|
||||
handlerMapping.setUrlMap(urlMap);
|
||||
|
||||
@@ -235,8 +235,7 @@ public class WebFluxConfigurationSupport implements ApplicationContextAware {
|
||||
*/
|
||||
@Bean
|
||||
public HandlerMapping resourceHandlerMapping() {
|
||||
ResourceHandlerRegistry registry =
|
||||
new ResourceHandlerRegistry(this.applicationContext, webFluxContentTypeResolver());
|
||||
ResourceHandlerRegistry registry = new ResourceHandlerRegistry(this.applicationContext);
|
||||
addResourceHandlers(registry);
|
||||
|
||||
AbstractHandlerMapping handlerMapping = registry.getHandlerMapping();
|
||||
|
||||
@@ -23,9 +23,7 @@ import java.time.Instant;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.EnumSet;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.Set;
|
||||
|
||||
@@ -35,7 +33,6 @@ import reactor.core.Exceptions;
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
import org.springframework.beans.factory.InitializingBean;
|
||||
import org.springframework.beans.factory.SmartInitializingSingleton;
|
||||
import org.springframework.core.ResolvableType;
|
||||
import org.springframework.core.io.Resource;
|
||||
import org.springframework.http.CacheControl;
|
||||
@@ -43,6 +40,7 @@ import org.springframework.http.HttpHeaders;
|
||||
import org.springframework.http.HttpMethod;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.http.MediaTypeFactory;
|
||||
import org.springframework.http.codec.ResourceHttpMessageWriter;
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.util.Assert;
|
||||
@@ -51,8 +49,6 @@ import org.springframework.util.ObjectUtils;
|
||||
import org.springframework.util.ResourceUtils;
|
||||
import org.springframework.util.StringUtils;
|
||||
import org.springframework.web.reactive.HandlerMapping;
|
||||
import org.springframework.web.reactive.accept.CompositeContentTypeResolver;
|
||||
import org.springframework.web.reactive.accept.PathExtensionContentTypeResolver;
|
||||
import org.springframework.web.server.MethodNotAllowedException;
|
||||
import org.springframework.web.server.ServerWebExchange;
|
||||
import org.springframework.web.server.WebHandler;
|
||||
@@ -85,7 +81,7 @@ import org.springframework.web.server.WebHandler;
|
||||
* @author Brian Clozel
|
||||
* @since 5.0
|
||||
*/
|
||||
public class ResourceWebHandler implements WebHandler, InitializingBean, SmartInitializingSingleton {
|
||||
public class ResourceWebHandler implements WebHandler, InitializingBean {
|
||||
|
||||
/** Set of supported HTTP methods */
|
||||
private static final Set<HttpMethod> SUPPORTED_METHODS = EnumSet.of(HttpMethod.GET, HttpMethod.HEAD);
|
||||
@@ -103,10 +99,6 @@ public class ResourceWebHandler implements WebHandler, InitializingBean, SmartIn
|
||||
|
||||
private ResourceHttpMessageWriter resourceHttpMessageWriter;
|
||||
|
||||
private CompositeContentTypeResolver contentTypeResolver;
|
||||
|
||||
private PathExtensionContentTypeResolver pathExtensionResolver;
|
||||
|
||||
|
||||
/**
|
||||
* Set the {@code List} of {@code Resource} paths to use as sources
|
||||
@@ -197,23 +189,6 @@ public class ResourceWebHandler implements WebHandler, InitializingBean, SmartIn
|
||||
return this.resourceHttpMessageWriter;
|
||||
}
|
||||
|
||||
/**
|
||||
* Configure a {@link CompositeContentTypeResolver} to help determine the
|
||||
* media types for resources being served. If the manager contains a path
|
||||
* extension resolver it will be checked for registered file extension.
|
||||
* @param contentTypeResolver the resolver in use
|
||||
*/
|
||||
public void setContentTypeResolver(CompositeContentTypeResolver contentTypeResolver) {
|
||||
this.contentTypeResolver = contentTypeResolver;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the configured {@link CompositeContentTypeResolver}.
|
||||
*/
|
||||
@Nullable
|
||||
public CompositeContentTypeResolver getContentTypeResolver() {
|
||||
return this.contentTypeResolver;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void afterPropertiesSet() throws Exception {
|
||||
@@ -250,23 +225,6 @@ public class ResourceWebHandler implements WebHandler, InitializingBean, SmartIn
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void afterSingletonsInstantiated() {
|
||||
this.pathExtensionResolver = initContentNegotiationStrategy();
|
||||
}
|
||||
|
||||
protected PathExtensionContentTypeResolver initContentNegotiationStrategy() {
|
||||
Map<String, MediaType> mediaTypes = null;
|
||||
if (getContentTypeResolver() != null) {
|
||||
PathExtensionContentTypeResolver strategy =
|
||||
getContentTypeResolver().findResolver(PathExtensionContentTypeResolver.class);
|
||||
if (strategy != null) {
|
||||
mediaTypes = new HashMap<>(strategy.getMediaTypes());
|
||||
}
|
||||
}
|
||||
return new PathExtensionContentTypeResolver(mediaTypes);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Processes a resource request.
|
||||
@@ -317,7 +275,7 @@ public class ResourceWebHandler implements WebHandler, InitializingBean, SmartIn
|
||||
}
|
||||
|
||||
// Check the media type for the resource
|
||||
MediaType mediaType = getMediaType(exchange, resource);
|
||||
MediaType mediaType = MediaTypeFactory.getMediaType(resource).orElse(null);
|
||||
if (mediaType != null) {
|
||||
if (logger.isTraceEnabled()) {
|
||||
logger.trace("Determined media type '" + mediaType + "' for " + resource);
|
||||
@@ -474,20 +432,6 @@ public class ResourceWebHandler implements WebHandler, InitializingBean, SmartIn
|
||||
return new DefaultResourceTransformerChain(resolverChain, getResourceTransformers());
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine the media type for the given request and the resource matched
|
||||
* to it. This implementation tries to determine the MediaType based on the
|
||||
* file extension of the Resource via
|
||||
* {@link PathExtensionContentTypeResolver#resolveMediaTypeForResource(Resource)}.
|
||||
* @param exchange the current exchange
|
||||
* @param resource the resource to check
|
||||
* @return the corresponding media type, or {@code null} if none found
|
||||
*/
|
||||
@Nullable
|
||||
protected MediaType getMediaType(ServerWebExchange exchange, Resource resource) {
|
||||
return this.pathExtensionResolver.resolveMediaTypeForResource(resource);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set headers on the response. Called for both GET and HEAD requests.
|
||||
* @param exchange current exchange
|
||||
|
||||
@@ -49,8 +49,6 @@ import org.springframework.mock.http.server.reactive.test.MockServerHttpResponse
|
||||
import org.springframework.mock.http.server.reactive.test.MockServerWebExchange;
|
||||
import org.springframework.util.StringUtils;
|
||||
import org.springframework.web.reactive.HandlerMapping;
|
||||
import org.springframework.web.reactive.accept.CompositeContentTypeResolver;
|
||||
import org.springframework.web.reactive.accept.RequestedContentTypeResolverBuilder;
|
||||
import org.springframework.web.server.MethodNotAllowedException;
|
||||
import org.springframework.web.server.ServerWebExchange;
|
||||
|
||||
@@ -86,7 +84,6 @@ public class ResourceWebHandlerTests {
|
||||
this.handler.setLocations(paths);
|
||||
this.handler.setCacheControl(CacheControl.maxAge(3600, TimeUnit.SECONDS));
|
||||
this.handler.afterPropertiesSet();
|
||||
this.handler.afterSingletonsInstantiated();
|
||||
}
|
||||
|
||||
|
||||
@@ -159,7 +156,6 @@ public class ResourceWebHandlerTests {
|
||||
versionResolver.addFixedVersionStrategy("versionString", "/**");
|
||||
this.handler.setResourceResolvers(Arrays.asList(versionResolver, new PathResourceResolver()));
|
||||
this.handler.afterPropertiesSet();
|
||||
this.handler.afterSingletonsInstantiated();
|
||||
|
||||
MockServerWebExchange exchange = MockServerHttpRequest.get("").toExchange();
|
||||
setPathWithinHandlerMapping(exchange, "versionString/foo.css");
|
||||
@@ -222,39 +218,12 @@ public class ResourceWebHandlerTests {
|
||||
assertResponseBody(exchange, "function foo() { console.log(\"hello world\"); }");
|
||||
}
|
||||
|
||||
@Test // SPR-13658
|
||||
public void getResourceWithRegisteredMediaType() throws Exception {
|
||||
CompositeContentTypeResolver contentTypeResolver = new RequestedContentTypeResolverBuilder()
|
||||
.mediaType("css", new MediaType("foo", "bar"))
|
||||
.build();
|
||||
|
||||
List<Resource> paths = Collections.singletonList(new ClassPathResource("test/", getClass()));
|
||||
ResourceWebHandler handler = new ResourceWebHandler();
|
||||
handler.setLocations(paths);
|
||||
handler.setContentTypeResolver(contentTypeResolver);
|
||||
handler.afterPropertiesSet();
|
||||
handler.afterSingletonsInstantiated();
|
||||
|
||||
MockServerWebExchange exchange = MockServerHttpRequest.get("").toExchange();
|
||||
setPathWithinHandlerMapping(exchange, "foo.css");
|
||||
handler.handle(exchange).block(TIMEOUT);
|
||||
|
||||
assertEquals(MediaType.parseMediaType("foo/bar"), exchange.getResponse().getHeaders().getContentType());
|
||||
assertResponseBody(exchange, "h1 { color:red; }");
|
||||
}
|
||||
|
||||
@Test // SPR-14577
|
||||
public void getMediaTypeWithFavorPathExtensionOff() throws Exception {
|
||||
CompositeContentTypeResolver contentTypeResolver = new RequestedContentTypeResolverBuilder()
|
||||
.favorPathExtension(false)
|
||||
.build();
|
||||
|
||||
List<Resource> paths = Collections.singletonList(new ClassPathResource("test/", getClass()));
|
||||
ResourceWebHandler handler = new ResourceWebHandler();
|
||||
handler.setLocations(paths);
|
||||
handler.setContentTypeResolver(contentTypeResolver);
|
||||
handler.afterPropertiesSet();
|
||||
handler.afterSingletonsInstantiated();
|
||||
|
||||
MockServerWebExchange exchange = MockServerHttpRequest.get("")
|
||||
.header("Accept", "application/json,text/plain,*/*").toExchange();
|
||||
@@ -367,7 +336,6 @@ public class ResourceWebHandlerTests {
|
||||
handler.setResourceResolvers(Collections.singletonList(pathResolver));
|
||||
handler.setLocations(Arrays.asList(location1, location2));
|
||||
handler.afterPropertiesSet();
|
||||
handler.afterSingletonsInstantiated();
|
||||
|
||||
Resource[] locations = pathResolver.getAllowedLocations();
|
||||
assertEquals(1, locations.length);
|
||||
|
||||
Reference in New Issue
Block a user