Add path extension and parameter ContentTypeResolver's
This commit is contained in:
parent
9ffc0b5e65
commit
1f283acb98
|
|
@ -0,0 +1,159 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2002-2016 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
|
||||||
|
*
|
||||||
|
* http://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.accept;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.HashSet;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Locale;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Set;
|
||||||
|
import java.util.concurrent.ConcurrentHashMap;
|
||||||
|
import java.util.concurrent.ConcurrentMap;
|
||||||
|
|
||||||
|
import org.springframework.http.MediaType;
|
||||||
|
import org.springframework.util.LinkedMultiValueMap;
|
||||||
|
import org.springframework.util.MultiValueMap;
|
||||||
|
import org.springframework.util.StringUtils;
|
||||||
|
import org.springframework.web.HttpMediaTypeNotAcceptableException;
|
||||||
|
import org.springframework.web.server.ServerWebExchange;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Abstract base class for {@link MappingContentTypeResolver} implementations.
|
||||||
|
* Maintains the actual mappings and pre-implements the overall algorithm with
|
||||||
|
* sub-classes left to provide a way to extract the lookup key (e.g. file
|
||||||
|
* extension, query parameter, etc) for a given exchange.
|
||||||
|
*
|
||||||
|
* @author Rossen Stoyanchev
|
||||||
|
*/
|
||||||
|
public abstract class AbstractMappingContentTypeResolver implements MappingContentTypeResolver {
|
||||||
|
|
||||||
|
/** Primary lookup for media types by key (e.g. "json" -> "application/json") */
|
||||||
|
private final ConcurrentMap<String, MediaType> mediaTypeLookup = new ConcurrentHashMap<>(64);
|
||||||
|
|
||||||
|
/** Reverse lookup for keys associated with a media type */
|
||||||
|
private final MultiValueMap<MediaType, String> keyLookup = new LinkedMultiValueMap<>(64);
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create an instance with the given map of file extensions and media types.
|
||||||
|
*/
|
||||||
|
public AbstractMappingContentTypeResolver(Map<String, MediaType> mediaTypes) {
|
||||||
|
if (mediaTypes != null) {
|
||||||
|
for (Map.Entry<String, MediaType> entry : mediaTypes.entrySet()) {
|
||||||
|
String extension = entry.getKey().toLowerCase(Locale.ENGLISH);
|
||||||
|
MediaType mediaType = entry.getValue();
|
||||||
|
this.mediaTypeLookup.put(extension, mediaType);
|
||||||
|
this.keyLookup.add(mediaType, extension);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sub-classes can use this method to look up a MediaType by key.
|
||||||
|
* @param key the key converted to lower case
|
||||||
|
* @return a MediaType or {@code null}
|
||||||
|
*/
|
||||||
|
protected MediaType getMediaType(String key) {
|
||||||
|
return this.mediaTypeLookup.get(key.toLowerCase(Locale.ENGLISH));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sub-classes can use this method get all mapped media types.
|
||||||
|
*/
|
||||||
|
protected List<MediaType> getMediaTypes() {
|
||||||
|
return new ArrayList<>(this.mediaTypeLookup.values());
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// ContentTypeResolver implementation
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<MediaType> resolveMediaTypes(ServerWebExchange exchange)
|
||||||
|
throws HttpMediaTypeNotAcceptableException {
|
||||||
|
|
||||||
|
String key = extractKey(exchange);
|
||||||
|
return resolveMediaTypes(key);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An overloaded resolve method with a pre-resolved lookup key.
|
||||||
|
* @param key the key for looking up media types
|
||||||
|
* @return a list of resolved media types or an empty list
|
||||||
|
* @throws HttpMediaTypeNotAcceptableException
|
||||||
|
*/
|
||||||
|
public List<MediaType> resolveMediaTypes(String key)
|
||||||
|
throws HttpMediaTypeNotAcceptableException {
|
||||||
|
|
||||||
|
if (StringUtils.hasText(key)) {
|
||||||
|
MediaType mediaType = getMediaType(key);
|
||||||
|
if (mediaType != null) {
|
||||||
|
handleMatch(key, mediaType);
|
||||||
|
return Collections.singletonList(mediaType);
|
||||||
|
}
|
||||||
|
mediaType = handleNoMatch(key);
|
||||||
|
if (mediaType != null) {
|
||||||
|
MediaType previous = this.mediaTypeLookup.putIfAbsent(key, mediaType);
|
||||||
|
if (previous == null) {
|
||||||
|
this.keyLookup.add(mediaType, key);
|
||||||
|
}
|
||||||
|
return Collections.singletonList(mediaType);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return Collections.emptyList();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Extract the key to use to look up a media type from the given exchange,
|
||||||
|
* e.g. file extension, query parameter, etc.
|
||||||
|
* @return the key or {@code null}
|
||||||
|
*/
|
||||||
|
protected abstract String extractKey(ServerWebExchange exchange);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Override to provide handling when a key is successfully resolved via
|
||||||
|
* {@link #getMediaType(String)}.
|
||||||
|
*/
|
||||||
|
@SuppressWarnings("UnusedParameters")
|
||||||
|
protected void handleMatch(String key, MediaType mediaType) {
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Override to provide handling when a key is not resolved via.
|
||||||
|
* {@link #getMediaType(String)}. If a MediaType is returned from
|
||||||
|
* this method it will be added to the mappings.
|
||||||
|
*/
|
||||||
|
@SuppressWarnings("UnusedParameters")
|
||||||
|
protected MediaType handleNoMatch(String key) throws HttpMediaTypeNotAcceptableException {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// MappingContentTypeResolver implementation
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Set<String> getKeysFor(MediaType mediaType) {
|
||||||
|
List<String> keys = this.keyLookup.get(mediaType);
|
||||||
|
return (keys != null ? new HashSet<>(keys) : Collections.emptySet());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Set<String> getKeys() {
|
||||||
|
return new HashSet<>(this.mediaTypeLookup.keySet());
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,82 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2002-2016 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
|
||||||
|
*
|
||||||
|
* http://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.accept;
|
||||||
|
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
import org.apache.commons.logging.Log;
|
||||||
|
import org.apache.commons.logging.LogFactory;
|
||||||
|
|
||||||
|
import org.springframework.http.MediaType;
|
||||||
|
import org.springframework.util.Assert;
|
||||||
|
import org.springframework.web.HttpMediaTypeNotAcceptableException;
|
||||||
|
import org.springframework.web.server.ServerWebExchange;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A {@link ContentTypeResolver} that extracts the media type lookup key from a
|
||||||
|
* known query parameter named "format" by default.
|
||||||
|
*s
|
||||||
|
* @author Rossen Stoyanchev
|
||||||
|
*/
|
||||||
|
public class ParameterContentTypeResolver extends AbstractMappingContentTypeResolver {
|
||||||
|
|
||||||
|
private static final Log logger = LogFactory.getLog(ParameterContentTypeResolver.class);
|
||||||
|
|
||||||
|
private String parameterName = "format";
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create an instance with the given map of file extensions and media types.
|
||||||
|
*/
|
||||||
|
public ParameterContentTypeResolver(Map<String, MediaType> mediaTypes) {
|
||||||
|
super(mediaTypes);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set the name of the parameter to use to determine requested media types.
|
||||||
|
* <p>By default this is set to {@code "format"}.
|
||||||
|
*/
|
||||||
|
public void setParameterName(String parameterName) {
|
||||||
|
Assert.notNull(parameterName, "parameterName is required");
|
||||||
|
this.parameterName = parameterName;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getParameterName() {
|
||||||
|
return this.parameterName;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected String extractKey(ServerWebExchange exchange) {
|
||||||
|
return exchange.getRequest().getQueryParams().getFirst(getParameterName());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void handleMatch(String mediaTypeKey, MediaType mediaType) {
|
||||||
|
if (logger.isDebugEnabled()) {
|
||||||
|
logger.debug("Requested media type is '" + mediaType +
|
||||||
|
"' based on '" + getParameterName() + "'='" + mediaTypeKey + "'.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected MediaType handleNoMatch(String key) throws HttpMediaTypeNotAcceptableException {
|
||||||
|
throw new HttpMediaTypeNotAcceptableException(getMediaTypes());
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,196 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2002-2016 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
|
||||||
|
*
|
||||||
|
* http://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.accept;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.InputStream;
|
||||||
|
import java.util.Locale;
|
||||||
|
import java.util.Map;
|
||||||
|
import javax.activation.FileTypeMap;
|
||||||
|
import javax.activation.MimetypesFileTypeMap;
|
||||||
|
|
||||||
|
import org.apache.commons.logging.Log;
|
||||||
|
import org.apache.commons.logging.LogFactory;
|
||||||
|
|
||||||
|
import org.springframework.core.io.ClassPathResource;
|
||||||
|
import org.springframework.core.io.Resource;
|
||||||
|
import org.springframework.http.MediaType;
|
||||||
|
import org.springframework.util.Assert;
|
||||||
|
import org.springframework.util.ClassUtils;
|
||||||
|
import org.springframework.util.StringUtils;
|
||||||
|
import org.springframework.web.HttpMediaTypeNotAcceptableException;
|
||||||
|
import org.springframework.web.accept.PathExtensionContentNegotiationStrategy;
|
||||||
|
import org.springframework.web.server.ServerWebExchange;
|
||||||
|
import org.springframework.web.util.WebUtils;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A {@link ContentTypeResolver} that extracts the file extension from the
|
||||||
|
* request path and uses that as the media type lookup key.
|
||||||
|
*
|
||||||
|
* <p>If the file extension is not found in the explicit registrations provided
|
||||||
|
* to the constructor, the Java Activation Framework (JAF) is used as a fallback
|
||||||
|
* mechanism. The presence of the JAF is detected and enabled automatically but
|
||||||
|
* the {@link #setUseJaf(boolean)} property may be set to false.
|
||||||
|
*
|
||||||
|
* @author Rossen Stoyanchev
|
||||||
|
*/
|
||||||
|
public class PathExtensionContentTypeResolver extends AbstractMappingContentTypeResolver {
|
||||||
|
|
||||||
|
private static final Log logger = LogFactory.getLog(PathExtensionContentNegotiationStrategy.class);
|
||||||
|
|
||||||
|
private static final boolean JAF_PRESENT = ClassUtils.isPresent(
|
||||||
|
"javax.activation.FileTypeMap",
|
||||||
|
PathExtensionContentNegotiationStrategy.class.getClassLoader());
|
||||||
|
|
||||||
|
|
||||||
|
private boolean useJaf = true;
|
||||||
|
|
||||||
|
private boolean ignoreUnknownExtensions = true;
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create an instance with the given map of file extensions and media types.
|
||||||
|
*/
|
||||||
|
public PathExtensionContentTypeResolver(Map<String, MediaType> mediaTypes) {
|
||||||
|
super(mediaTypes);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create an instance without any mappings to start with. Mappings may be added
|
||||||
|
* later on if any extensions are resolved through the Java Activation framework.
|
||||||
|
*/
|
||||||
|
public PathExtensionContentTypeResolver() {
|
||||||
|
super(null);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Whether to use the Java Activation Framework to look up file extensions.
|
||||||
|
* <p>By default this is set to "true" but depends on JAF being present.
|
||||||
|
*/
|
||||||
|
public void setUseJaf(boolean useJaf) {
|
||||||
|
this.useJaf = useJaf;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Whether to ignore requests with unknown file extension. Setting this to
|
||||||
|
* {@code false} results in {@code HttpMediaTypeNotAcceptableException}.
|
||||||
|
* <p>By default this is set to {@code true}.
|
||||||
|
*/
|
||||||
|
public void setIgnoreUnknownExtensions(boolean ignoreUnknownExtensions) {
|
||||||
|
this.ignoreUnknownExtensions = ignoreUnknownExtensions;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected String extractKey(ServerWebExchange exchange) {
|
||||||
|
String path = exchange.getRequest().getURI().getRawPath();
|
||||||
|
String filename = WebUtils.extractFullFilenameFromUrlPath(path);
|
||||||
|
String extension = StringUtils.getFilenameExtension(filename);
|
||||||
|
return (StringUtils.hasText(extension)) ? extension.toLowerCase(Locale.ENGLISH) : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected MediaType handleNoMatch(String key) throws HttpMediaTypeNotAcceptableException {
|
||||||
|
if (this.useJaf && JAF_PRESENT) {
|
||||||
|
MediaType mediaType = JafMediaTypeFactory.getMediaType("file." + key);
|
||||||
|
if (mediaType != null && !MediaType.APPLICATION_OCTET_STREAM.equals(mediaType)) {
|
||||||
|
return mediaType;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!this.ignoreUnknownExtensions) {
|
||||||
|
throw new HttpMediaTypeNotAcceptableException(getMediaTypes());
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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 JAF.
|
||||||
|
* @param resource the resource
|
||||||
|
* @return the MediaType for the extension or {@code null}.
|
||||||
|
*/
|
||||||
|
public MediaType resolveMediaTypeForResource(Resource resource) {
|
||||||
|
Assert.notNull(resource);
|
||||||
|
MediaType mediaType = null;
|
||||||
|
String filename = resource.getFilename();
|
||||||
|
String extension = StringUtils.getFilenameExtension(filename);
|
||||||
|
if (extension != null) {
|
||||||
|
mediaType = getMediaType(extension);
|
||||||
|
}
|
||||||
|
if (mediaType == null && JAF_PRESENT) {
|
||||||
|
mediaType = JafMediaTypeFactory.getMediaType(filename);
|
||||||
|
}
|
||||||
|
if (MediaType.APPLICATION_OCTET_STREAM.equals(mediaType)) {
|
||||||
|
mediaType = null;
|
||||||
|
}
|
||||||
|
return mediaType;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Inner class to avoid hard-coded dependency on JAF.
|
||||||
|
*/
|
||||||
|
private static class JafMediaTypeFactory {
|
||||||
|
|
||||||
|
private static final FileTypeMap fileTypeMap;
|
||||||
|
|
||||||
|
static {
|
||||||
|
fileTypeMap = initFileTypeMap();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Find extended mime.types from the spring-context-support module.
|
||||||
|
*/
|
||||||
|
private static FileTypeMap initFileTypeMap() {
|
||||||
|
Resource resource = new ClassPathResource("org/springframework/mail/javamail/mime.types");
|
||||||
|
if (resource.exists()) {
|
||||||
|
if (logger.isTraceEnabled()) {
|
||||||
|
logger.trace("Loading JAF FileTypeMap from " + resource);
|
||||||
|
}
|
||||||
|
InputStream inputStream = null;
|
||||||
|
try {
|
||||||
|
inputStream = resource.getInputStream();
|
||||||
|
return new MimetypesFileTypeMap(inputStream);
|
||||||
|
}
|
||||||
|
catch (IOException ex) {
|
||||||
|
// ignore
|
||||||
|
}
|
||||||
|
finally {
|
||||||
|
if (inputStream != null) {
|
||||||
|
try {
|
||||||
|
inputStream.close();
|
||||||
|
}
|
||||||
|
catch (IOException ex) {
|
||||||
|
// ignore
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (logger.isTraceEnabled()) {
|
||||||
|
logger.trace("Loading default Java Activation Framework FileTypeMap");
|
||||||
|
}
|
||||||
|
return FileTypeMap.getDefaultFileTypeMap();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static MediaType getMediaType(String filename) {
|
||||||
|
String mediaType = fileTypeMap.getContentType(filename);
|
||||||
|
return (StringUtils.hasText(mediaType) ? MediaType.parseMediaType(mediaType) : null);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,122 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2002-2016 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
|
||||||
|
*
|
||||||
|
* http://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.accept;
|
||||||
|
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
|
import org.junit.Test;
|
||||||
|
|
||||||
|
import org.springframework.http.MediaType;
|
||||||
|
import org.springframework.web.server.ServerWebExchange;
|
||||||
|
|
||||||
|
import static org.junit.Assert.assertEquals;
|
||||||
|
import static org.junit.Assert.assertTrue;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Unit tests for {@link AbstractMappingContentTypeResolver}.
|
||||||
|
* @author Rossen Stoyanchev
|
||||||
|
*/
|
||||||
|
public class MappingContentTypeResolverTests {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void resolveExtensions() {
|
||||||
|
Map<String, MediaType> mapping = Collections.singletonMap("json", MediaType.APPLICATION_JSON);
|
||||||
|
TestMappingContentTypeResolver resolver = new TestMappingContentTypeResolver("", mapping);
|
||||||
|
Set<String> keys = resolver.getKeysFor(MediaType.APPLICATION_JSON);
|
||||||
|
|
||||||
|
assertEquals(1, keys.size());
|
||||||
|
assertEquals("json", keys.iterator().next());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void resolveExtensionsNoMatch() {
|
||||||
|
Map<String, MediaType> mapping = Collections.singletonMap("json", MediaType.APPLICATION_JSON);
|
||||||
|
TestMappingContentTypeResolver resolver = new TestMappingContentTypeResolver("", mapping);
|
||||||
|
Set<String> keys = resolver.getKeysFor(MediaType.TEXT_HTML);
|
||||||
|
|
||||||
|
assertTrue(keys.isEmpty());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test // SPR-13747
|
||||||
|
public void lookupMediaTypeCaseInsensitive() {
|
||||||
|
Map<String, MediaType> mapping = Collections.singletonMap("json", MediaType.APPLICATION_JSON);
|
||||||
|
TestMappingContentTypeResolver resolver = new TestMappingContentTypeResolver("", mapping);
|
||||||
|
MediaType mediaType = resolver.getMediaType("JSoN");
|
||||||
|
|
||||||
|
assertEquals(mediaType, MediaType.APPLICATION_JSON);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void resolveMediaTypes() throws Exception {
|
||||||
|
Map<String, MediaType> mapping = Collections.singletonMap("json", MediaType.APPLICATION_JSON);
|
||||||
|
TestMappingContentTypeResolver resolver = new TestMappingContentTypeResolver("json", mapping);
|
||||||
|
List<MediaType> mediaTypes = resolver.resolveMediaTypes((ServerWebExchange) null);
|
||||||
|
|
||||||
|
assertEquals(1, mediaTypes.size());
|
||||||
|
assertEquals("application/json", mediaTypes.get(0).toString());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void resolveMediaTypesNoMatch() throws Exception {
|
||||||
|
TestMappingContentTypeResolver resolver = new TestMappingContentTypeResolver("blah", null);
|
||||||
|
List<MediaType> mediaTypes = resolver.resolveMediaTypes((ServerWebExchange) null);
|
||||||
|
|
||||||
|
assertEquals(0, mediaTypes.size());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void resolveMediaTypesNoKey() throws Exception {
|
||||||
|
Map<String, MediaType> mapping = Collections.singletonMap("json", MediaType.APPLICATION_JSON);
|
||||||
|
TestMappingContentTypeResolver resolver = new TestMappingContentTypeResolver(null, mapping);
|
||||||
|
List<MediaType> mediaTypes = resolver.resolveMediaTypes((ServerWebExchange) null);
|
||||||
|
|
||||||
|
assertEquals(0, mediaTypes.size());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void resolveMediaTypesHandleNoMatch() throws Exception {
|
||||||
|
TestMappingContentTypeResolver resolver = new TestMappingContentTypeResolver("xml", null);
|
||||||
|
List<MediaType> mediaTypes = resolver.resolveMediaTypes((ServerWebExchange) null);
|
||||||
|
|
||||||
|
assertEquals(1, mediaTypes.size());
|
||||||
|
assertEquals("application/xml", mediaTypes.get(0).toString());
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private static class TestMappingContentTypeResolver extends AbstractMappingContentTypeResolver {
|
||||||
|
|
||||||
|
private final String key;
|
||||||
|
|
||||||
|
public TestMappingContentTypeResolver(String key, Map<String, MediaType> mapping) {
|
||||||
|
super(mapping);
|
||||||
|
this.key = key;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected String extractKey(ServerWebExchange exchange) {
|
||||||
|
return this.key;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected MediaType handleNoMatch(String mappingKey) {
|
||||||
|
return "xml".equals(mappingKey) ? MediaType.APPLICATION_XML : null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,119 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2002-2016 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
|
||||||
|
*
|
||||||
|
* http://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.accept;
|
||||||
|
|
||||||
|
import java.net.URI;
|
||||||
|
import java.net.URISyntaxException;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
import org.junit.Test;
|
||||||
|
|
||||||
|
import org.springframework.http.HttpMethod;
|
||||||
|
import org.springframework.http.MediaType;
|
||||||
|
import org.springframework.http.server.reactive.MockServerHttpRequest;
|
||||||
|
import org.springframework.http.server.reactive.MockServerHttpResponse;
|
||||||
|
import org.springframework.http.server.reactive.ServerHttpRequest;
|
||||||
|
import org.springframework.web.HttpMediaTypeNotAcceptableException;
|
||||||
|
import org.springframework.web.server.ServerWebExchange;
|
||||||
|
import org.springframework.web.server.adapter.DefaultServerWebExchange;
|
||||||
|
import org.springframework.web.server.session.WebSessionManager;
|
||||||
|
|
||||||
|
import static org.junit.Assert.assertEquals;
|
||||||
|
import static org.mockito.Mockito.mock;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Unit tests for {@link PathExtensionContentTypeResolver}.
|
||||||
|
*
|
||||||
|
* @author Rossen Stoyanchev
|
||||||
|
*/
|
||||||
|
public class PathExtensionContentNegotiationStrategyTests {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void resolveMediaTypesFromMapping() throws Exception {
|
||||||
|
ServerWebExchange exchange = createExchange("/test.html");
|
||||||
|
PathExtensionContentTypeResolver resolver = new PathExtensionContentTypeResolver();
|
||||||
|
List<MediaType> mediaTypes = resolver.resolveMediaTypes(exchange);
|
||||||
|
|
||||||
|
assertEquals(Collections.singletonList(new MediaType("text", "html")), mediaTypes);
|
||||||
|
|
||||||
|
Map<String, MediaType> mapping = Collections.singletonMap("HTML", MediaType.APPLICATION_XHTML_XML);
|
||||||
|
resolver = new PathExtensionContentTypeResolver(mapping);
|
||||||
|
mediaTypes = resolver.resolveMediaTypes(exchange);
|
||||||
|
|
||||||
|
assertEquals(Collections.singletonList(new MediaType("application", "xhtml+xml")), mediaTypes);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void resolveMediaTypesFromJaf() throws Exception {
|
||||||
|
ServerWebExchange exchange = createExchange("test.xls");
|
||||||
|
PathExtensionContentTypeResolver resolver = new PathExtensionContentTypeResolver();
|
||||||
|
List<MediaType> mediaTypes = resolver.resolveMediaTypes(exchange);
|
||||||
|
|
||||||
|
assertEquals(Collections.singletonList(new MediaType("application", "vnd.ms-excel")), mediaTypes);
|
||||||
|
}
|
||||||
|
|
||||||
|
// SPR-10334
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void getMediaTypeFromFilenameNoJaf() throws Exception {
|
||||||
|
ServerWebExchange exchange = createExchange("test.json");
|
||||||
|
PathExtensionContentTypeResolver resolver = new PathExtensionContentTypeResolver();
|
||||||
|
resolver.setUseJaf(false);
|
||||||
|
List<MediaType> mediaTypes = resolver.resolveMediaTypes(exchange);
|
||||||
|
|
||||||
|
assertEquals(Collections.<MediaType>emptyList(), mediaTypes);
|
||||||
|
}
|
||||||
|
|
||||||
|
// SPR-9390
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void getMediaTypeFilenameWithEncodedURI() throws Exception {
|
||||||
|
ServerWebExchange exchange = createExchange("/quo%20vadis%3f.html");
|
||||||
|
PathExtensionContentTypeResolver resolver = new PathExtensionContentTypeResolver();
|
||||||
|
List<MediaType> result = resolver.resolveMediaTypes(exchange);
|
||||||
|
|
||||||
|
assertEquals("Invalid content type", Collections.singletonList(new MediaType("text", "html")), result);
|
||||||
|
}
|
||||||
|
|
||||||
|
// SPR-10170
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void resolveMediaTypesIgnoreUnknownExtension() throws Exception {
|
||||||
|
ServerWebExchange exchange = createExchange("test.xyz");
|
||||||
|
PathExtensionContentTypeResolver resolver = new PathExtensionContentTypeResolver();
|
||||||
|
List<MediaType> mediaTypes = resolver.resolveMediaTypes(exchange);
|
||||||
|
|
||||||
|
assertEquals(Collections.<MediaType>emptyList(), mediaTypes);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test(expected = HttpMediaTypeNotAcceptableException.class)
|
||||||
|
public void resolveMediaTypesDoNotIgnoreUnknownExtension() throws Exception {
|
||||||
|
ServerWebExchange exchange = createExchange("test.xyz");
|
||||||
|
PathExtensionContentTypeResolver resolver = new PathExtensionContentTypeResolver();
|
||||||
|
resolver.setIgnoreUnknownExtensions(false);
|
||||||
|
resolver.resolveMediaTypes(exchange);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private ServerWebExchange createExchange(String path) throws URISyntaxException {
|
||||||
|
ServerHttpRequest request = new MockServerHttpRequest(HttpMethod.GET, new URI(path));
|
||||||
|
WebSessionManager sessionManager = mock(WebSessionManager.class);
|
||||||
|
return new DefaultServerWebExchange(request, new MockServerHttpResponse(), sessionManager);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
Loading…
Reference in New Issue