Skip to content

Commit e7b0b65

Browse files
committed
Support for MediaType mappings in ResourceWebHandler
Closes gh-26170
1 parent e9caee1 commit e7b0b65

File tree

5 files changed

+110
-2
lines changed

5 files changed

+110
-2
lines changed

spring-webflux/src/main/java/org/springframework/web/reactive/config/ResourceHandlerRegistration.java

Lines changed: 29 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2016 the original author or authors.
2+
* Copyright 2002-2020 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -18,12 +18,16 @@
1818

1919
import java.util.ArrayList;
2020
import java.util.Arrays;
21+
import java.util.HashMap;
2122
import java.util.List;
23+
import java.util.Map;
2224

2325
import org.springframework.cache.Cache;
2426
import org.springframework.core.io.Resource;
2527
import org.springframework.core.io.ResourceLoader;
2628
import org.springframework.http.CacheControl;
29+
import org.springframework.http.MediaType;
30+
import org.springframework.http.MediaTypeFactory;
2731
import org.springframework.lang.Nullable;
2832
import org.springframework.util.Assert;
2933
import org.springframework.web.reactive.resource.ResourceWebHandler;
@@ -50,6 +54,10 @@ public class ResourceHandlerRegistration {
5054

5155
private boolean useLastModified = true;
5256

57+
@Nullable
58+
private Map<String, MediaType> mediaTypes;
59+
60+
5361

5462
/**
5563
* Create a {@link ResourceHandlerRegistration} instance.
@@ -146,6 +154,23 @@ public ResourceChainRegistration resourceChain(boolean cacheResources, Cache cac
146154
return this.resourceChainRegistration;
147155
}
148156

157+
/**
158+
* Add mappings between file extensions extracted from the filename of static
159+
* {@link Resource}s and the media types to use for the response.
160+
* <p>Use of this method is typically not necessary since mappings can be
161+
* also determined via {@link MediaTypeFactory#getMediaType(Resource)}.
162+
* @param mediaTypes media type mappings
163+
* @since 5.3.2
164+
*/
165+
public void setMediaTypes(Map<String, MediaType> mediaTypes) {
166+
if (this.mediaTypes == null) {
167+
this.mediaTypes = new HashMap<>(mediaTypes.size());
168+
}
169+
this.mediaTypes.clear();
170+
this.mediaTypes.putAll(mediaTypes);
171+
}
172+
173+
149174
/**
150175
* Returns the URL path patterns for the resource handler.
151176
*/
@@ -168,6 +193,9 @@ protected ResourceWebHandler getRequestHandler() {
168193
handler.setCacheControl(this.cacheControl);
169194
}
170195
handler.setUseLastModified(this.useLastModified);
196+
if (this.mediaTypes != null) {
197+
handler.setMediaTypes(this.mediaTypes);
198+
}
171199
return handler;
172200
}
173201

spring-webflux/src/main/java/org/springframework/web/reactive/resource/ResourceWebHandler.java

Lines changed: 50 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,10 @@
2323
import java.util.ArrayList;
2424
import java.util.Collections;
2525
import java.util.EnumSet;
26+
import java.util.HashMap;
2627
import java.util.List;
28+
import java.util.Locale;
29+
import java.util.Map;
2730
import java.util.Set;
2831
import java.util.stream.Collectors;
2932

@@ -111,6 +114,9 @@ public class ResourceWebHandler implements WebHandler, InitializingBean {
111114
@Nullable
112115
private ResourceHttpMessageWriter resourceHttpMessageWriter;
113116

117+
@Nullable
118+
private Map<String, MediaType> mediaTypes;
119+
114120
@Nullable
115121
private ResourceLoader resourceLoader;
116122

@@ -230,6 +236,30 @@ public ResourceHttpMessageWriter getResourceHttpMessageWriter() {
230236
return this.resourceHttpMessageWriter;
231237
}
232238

239+
/**
240+
* Add mappings between file extensions extracted from the filename of static
241+
* {@link Resource}s and the media types to use for the response.
242+
* <p>Use of this method is typically not necessary since mappings can be
243+
* also determined via {@link MediaTypeFactory#getMediaType(Resource)}.
244+
* @param mediaTypes media type mappings
245+
* @since 5.3.2
246+
*/
247+
public void setMediaTypes(Map<String, MediaType> mediaTypes) {
248+
if (this.mediaTypes == null) {
249+
this.mediaTypes = new HashMap<>(mediaTypes.size());
250+
}
251+
mediaTypes.forEach((ext, type) ->
252+
this.mediaTypes.put(ext.toLowerCase(Locale.ENGLISH), type));
253+
}
254+
255+
/**
256+
* Return the {@link #setMediaTypes(Map) configured} media type mappings.
257+
* @since 5.3.2
258+
*/
259+
public Map<String, MediaType> getMediaTypes() {
260+
return (this.mediaTypes != null ? this.mediaTypes : Collections.emptyMap());
261+
}
262+
233263
/**
234264
* Provide the ResourceLoader to load {@link #setLocationValues(List)
235265
* location values} with.
@@ -374,7 +404,7 @@ public Mono<Void> handle(ServerWebExchange exchange) {
374404
}
375405

376406
// Check the media type for the resource
377-
MediaType mediaType = MediaTypeFactory.getMediaType(resource).orElse(null);
407+
MediaType mediaType = getMediaType(resource);
378408
setHeaders(exchange, resource, mediaType);
379409

380410
// Content phase
@@ -535,6 +565,25 @@ protected boolean isInvalidPath(String path) {
535565
return false;
536566
}
537567

568+
@Nullable
569+
private MediaType getMediaType(Resource resource) {
570+
MediaType mediaType = null;
571+
String filename = resource.getFilename();
572+
if (!CollectionUtils.isEmpty(this.mediaTypes)) {
573+
String ext = StringUtils.getFilenameExtension(filename);
574+
if (ext != null) {
575+
mediaType = this.mediaTypes.get(ext.toLowerCase(Locale.ENGLISH));
576+
}
577+
}
578+
if (mediaType == null) {
579+
List<MediaType> mediaTypes = MediaTypeFactory.getMediaTypes(filename);
580+
if (!CollectionUtils.isEmpty(mediaTypes)) {
581+
mediaType = mediaTypes.get(0);
582+
}
583+
}
584+
return mediaType;
585+
}
586+
538587
/**
539588
* Set headers on the response. Called for both GET and HEAD requests.
540589
* @param exchange current exchange

spring-webflux/src/test/java/org/springframework/web/reactive/config/ResourceHandlerRegistryTests.java

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
package org.springframework.web.reactive.config;
1818

1919
import java.time.Duration;
20+
import java.util.Collections;
2021
import java.util.List;
2122
import java.util.concurrent.TimeUnit;
2223

@@ -28,6 +29,7 @@
2829
import org.springframework.cache.concurrent.ConcurrentMapCache;
2930
import org.springframework.context.support.GenericApplicationContext;
3031
import org.springframework.http.CacheControl;
32+
import org.springframework.http.MediaType;
3133
import org.springframework.http.server.PathContainer;
3234
import org.springframework.web.reactive.HandlerMapping;
3335
import org.springframework.web.reactive.handler.SimpleUrlHandlerMapping;
@@ -99,6 +101,16 @@ public void cacheControl() {
99101
.isEqualTo(CacheControl.noCache().cachePrivate().getHeaderValue());
100102
}
101103

104+
@Test
105+
public void mediaTypes() {
106+
MediaType mediaType = MediaType.parseMediaType("foo/bar");
107+
this.registration.setMediaTypes(Collections.singletonMap("bar", mediaType));
108+
ResourceWebHandler requestHandler = this.registration.getRequestHandler();
109+
110+
assertThat(requestHandler.getMediaTypes()).size().isEqualTo(1);
111+
assertThat(requestHandler.getMediaTypes()).containsEntry("bar", mediaType);
112+
}
113+
102114
@Test
103115
public void order() {
104116
assertThat(this.registry.getHandlerMapping().getOrder()).isEqualTo(Integer.MAX_VALUE -1);

spring-webflux/src/test/java/org/springframework/web/reactive/resource/ResourceWebHandlerTests.java

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -214,6 +214,24 @@ public void getResourceFromSubDirectoryOfAlternatePath() {
214214
assertResponseBody(exchange, "function foo() { console.log(\"hello world\"); }");
215215
}
216216

217+
@Test
218+
public void getResourceWithRegisteredMediaType() throws Exception {
219+
MediaType mediaType = new MediaType("foo", "bar");
220+
221+
ResourceWebHandler handler = new ResourceWebHandler();
222+
handler.setLocations(Collections.singletonList(new ClassPathResource("test/", getClass())));
223+
handler.setMediaTypes(Collections.singletonMap("bar", mediaType));
224+
handler.afterPropertiesSet();
225+
226+
MockServerWebExchange exchange = MockServerWebExchange.from(MockServerHttpRequest.get(""));
227+
setPathWithinHandlerMapping(exchange, "foo.bar");
228+
handler.handle(exchange).block(TIMEOUT);
229+
230+
HttpHeaders headers = exchange.getResponse().getHeaders();
231+
assertThat(headers.getContentType()).isEqualTo(mediaType);
232+
assertResponseBody(exchange, "foo bar foo bar foo bar");
233+
}
234+
217235
@Test // SPR-14577
218236
public void getMediaTypeWithFavorPathExtensionOff() throws Exception {
219237
List<Resource> paths = Collections.singletonList(new ClassPathResource("test/", getClass()));
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
foo bar foo bar foo bar

0 commit comments

Comments
 (0)