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:
Rossen Stoyanchev
2017-06-07 14:36:57 -04:00
parent b65bfdb979
commit cb604738cf
5 changed files with 4 additions and 135 deletions

View File

@@ -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;
}
}

View File

@@ -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);

View File

@@ -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();

View File

@@ -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

View File

@@ -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);