Refactor RequestedContentTypeResolverBuilder
The revised builder emphasizes creating a list of resolvers either built-in or custom with each top-level builder method resulting in adding a resolver. By default only the Header resolver is configured. The path extension resolver is removed altogether to discourage its use but is trivial to create manually with the helpf of UriUtils#extractFileExtension + MediaTypeFactory. Issue: SPR-15639
This commit is contained in:
@@ -1,82 +0,0 @@
|
||||
/*
|
||||
* Copyright 2002-2017 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.Locale;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.http.MediaTypeFactory;
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.util.StringUtils;
|
||||
import org.springframework.web.server.ServerWebExchange;
|
||||
|
||||
/**
|
||||
* Base class for resolvers that extract a key from the request and look up a
|
||||
* mapping to a MediaType. The use case is URI-based content negotiation for
|
||||
* example based on query parameter or file extension in the request path.
|
||||
*
|
||||
* @author Rossen Stoyanchev
|
||||
* @since 5.0
|
||||
*/
|
||||
public abstract class AbstractMappingContentTypeResolver implements RequestedContentTypeResolver {
|
||||
|
||||
/** Primary lookup for media types by key (e.g. "json" -> "application/json") */
|
||||
private final Map<String, MediaType> mediaTypeLookup = new ConcurrentHashMap<>(64);
|
||||
|
||||
|
||||
public AbstractMappingContentTypeResolver(Map<String, MediaType> mediaTypes) {
|
||||
mediaTypes.forEach((key, mediaType) ->
|
||||
this.mediaTypeLookup.put(key.toLowerCase(Locale.ENGLISH), mediaType));
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public List<MediaType> resolveMediaTypes(ServerWebExchange exchange) {
|
||||
String key = getKey(exchange);
|
||||
if (StringUtils.hasText(key)) {
|
||||
MediaType mediaType = getMediaType(key);
|
||||
if (mediaType != null) {
|
||||
this.mediaTypeLookup.putIfAbsent(key, mediaType);
|
||||
return Collections.singletonList(mediaType);
|
||||
}
|
||||
}
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the key to look up a MediaType with.
|
||||
*/
|
||||
@Nullable
|
||||
protected abstract String getKey(ServerWebExchange exchange);
|
||||
|
||||
/**
|
||||
* Get the MediaType for the given key.
|
||||
*/
|
||||
@Nullable
|
||||
protected MediaType getMediaType(String key) {
|
||||
key = key.toLowerCase(Locale.ENGLISH);
|
||||
MediaType mediaType = this.mediaTypeLookup.get(key);
|
||||
if (mediaType == null) {
|
||||
mediaType = MediaTypeFactory.getMediaType("filename." + key).orElse(null);
|
||||
}
|
||||
return mediaType;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,55 +0,0 @@
|
||||
/*
|
||||
* Copyright 2002-2017 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.List;
|
||||
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.web.server.ServerWebExchange;
|
||||
|
||||
/**
|
||||
* Contains and delegates to other {@link RequestedContentTypeResolver}.
|
||||
*
|
||||
* @author Rossen Stoyanchev
|
||||
* @since 5.0
|
||||
*/
|
||||
public class CompositeContentTypeResolver implements RequestedContentTypeResolver {
|
||||
|
||||
private final List<RequestedContentTypeResolver> resolvers = new ArrayList<>();
|
||||
|
||||
|
||||
public CompositeContentTypeResolver(List<RequestedContentTypeResolver> resolvers) {
|
||||
Assert.notEmpty(resolvers, "At least one resolver is expected.");
|
||||
this.resolvers.addAll(resolvers);
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public List<MediaType> resolveMediaTypes(ServerWebExchange exchange) {
|
||||
for (RequestedContentTypeResolver resolver : this.resolvers) {
|
||||
List<MediaType> mediaTypes = resolver.resolveMediaTypes(exchange);
|
||||
if (mediaTypes.isEmpty() || (mediaTypes.size() == 1 && mediaTypes.contains(MediaType.ALL))) {
|
||||
continue;
|
||||
}
|
||||
return mediaTypes;
|
||||
}
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -26,7 +26,9 @@ import org.springframework.http.MediaType;
|
||||
import org.springframework.web.server.ServerWebExchange;
|
||||
|
||||
/**
|
||||
* {@code RequestedContentTypeResolver} with a fixed list of media types.
|
||||
* Resolver that always resolves to a fixed list of media types. This can be
|
||||
* used as the "last in line" strategy providing a fallback for when the client
|
||||
* has not requested any media types.
|
||||
*
|
||||
* @author Rossen Stoyanchev
|
||||
* @since 5.0
|
||||
@@ -65,7 +67,6 @@ public class FixedContentTypeResolver implements RequestedContentTypeResolver {
|
||||
}
|
||||
|
||||
|
||||
|
||||
@Override
|
||||
public List<MediaType> resolveMediaTypes(ServerWebExchange exchange) {
|
||||
if (logger.isDebugEnabled()) {
|
||||
|
||||
@@ -23,7 +23,7 @@ import org.springframework.web.server.NotAcceptableStatusException;
|
||||
import org.springframework.web.server.ServerWebExchange;
|
||||
|
||||
/**
|
||||
* A {@link RequestedContentTypeResolver} that checks the 'Accept' request header.
|
||||
* Resolver that looks at the 'Accept' header of the request.
|
||||
*
|
||||
* @author Rossen Stoyanchev
|
||||
* @since 5.0
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2016 the original author or authors.
|
||||
* Copyright 2002-2017 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.
|
||||
@@ -13,34 +13,50 @@
|
||||
* 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.List;
|
||||
import java.util.Locale;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.http.MediaTypeFactory;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.StringUtils;
|
||||
import org.springframework.web.server.NotAcceptableStatusException;
|
||||
import org.springframework.web.server.ServerWebExchange;
|
||||
|
||||
/**
|
||||
* Query parameter based {@link AbstractMappingContentTypeResolver}.
|
||||
* Resolver that checks a query parameter and uses it to lookup a matching
|
||||
* MediaType. Lookup keys can be registered or as a fallback
|
||||
* {@link MediaTypeFactory} can be used to perform a lookup.
|
||||
*
|
||||
* @author Rossen Stoyanchev
|
||||
* @since 5.0
|
||||
*/
|
||||
public class ParameterContentTypeResolver extends AbstractMappingContentTypeResolver {
|
||||
public class ParameterContentTypeResolver implements RequestedContentTypeResolver {
|
||||
|
||||
/** Primary lookup for media types by key (e.g. "json" -> "application/json") */
|
||||
private final Map<String, MediaType> mediaTypes = new ConcurrentHashMap<>(64);
|
||||
|
||||
private String parameterName = "format";
|
||||
|
||||
|
||||
public ParameterContentTypeResolver(Map<String, MediaType> mediaTypes) {
|
||||
super(mediaTypes);
|
||||
mediaTypes.forEach((key, value) -> this.mediaTypes.put(formatKey(key), value));
|
||||
}
|
||||
|
||||
private static String formatKey(String key) {
|
||||
return key.toLowerCase(Locale.ENGLISH);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Set the name of the parameter to use to determine requested media types.
|
||||
* <p>By default this is set to {@code "format"}.
|
||||
* <p>By default this is set to {@literal "format"}.
|
||||
*/
|
||||
public void setParameterName(String parameterName) {
|
||||
Assert.notNull(parameterName, "'parameterName' is required");
|
||||
@@ -53,8 +69,22 @@ public class ParameterContentTypeResolver extends AbstractMappingContentTypeReso
|
||||
|
||||
|
||||
@Override
|
||||
protected String getKey(ServerWebExchange exchange) {
|
||||
return exchange.getRequest().getQueryParams().getFirst(getParameterName());
|
||||
public List<MediaType> resolveMediaTypes(ServerWebExchange exchange) throws NotAcceptableStatusException {
|
||||
String key = exchange.getRequest().getQueryParams().getFirst(getParameterName());
|
||||
if (!StringUtils.hasText(key)) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
key = formatKey(key);
|
||||
MediaType match = this.mediaTypes.get(key);
|
||||
if (match == null) {
|
||||
match = MediaTypeFactory.getMediaType("filename." + key)
|
||||
.orElseThrow(() -> {
|
||||
List<MediaType> supported = new ArrayList<>(this.mediaTypes.values());
|
||||
return new NotAcceptableStatusException(supported);
|
||||
});
|
||||
}
|
||||
this.mediaTypes.putIfAbsent(key, match);
|
||||
return Collections.singletonList(match);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,45 +0,0 @@
|
||||
/*
|
||||
* Copyright 2002-2017 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.springframework.http.MediaType;
|
||||
import org.springframework.web.server.ServerWebExchange;
|
||||
import org.springframework.web.util.UriUtils;
|
||||
|
||||
/**
|
||||
* Path file extension sub-class of {@link AbstractMappingContentTypeResolver}.
|
||||
*
|
||||
* @author Rossen Stoyanchev
|
||||
* @since 5.0
|
||||
*/
|
||||
public class PathExtensionContentTypeResolver extends AbstractMappingContentTypeResolver {
|
||||
|
||||
|
||||
public PathExtensionContentTypeResolver(Map<String, MediaType> mediaTypes) {
|
||||
super(mediaTypes);
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
protected String getKey(ServerWebExchange exchange) {
|
||||
String path = exchange.getRequest().getURI().getRawPath();
|
||||
return UriUtils.extractFileExtension(path);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -23,8 +23,10 @@ import org.springframework.web.server.NotAcceptableStatusException;
|
||||
import org.springframework.web.server.ServerWebExchange;
|
||||
|
||||
/**
|
||||
* Strategy for resolving the requested media types for a
|
||||
* {@code ServerWebExchange}.
|
||||
* Strategy to resolve the requested media types for a {@code ServerWebExchange}.
|
||||
*
|
||||
* <p>See {@link RequestedContentTypeResolverBuilder} to create a sequence of
|
||||
* strategies.
|
||||
*
|
||||
* @author Rossen Stoyanchev
|
||||
* @since 5.0
|
||||
@@ -36,7 +38,7 @@ public interface RequestedContentTypeResolver {
|
||||
* list is ordered by specificity first and by quality parameter second.
|
||||
* @param exchange the current exchange
|
||||
* @return the requested media types or an empty list
|
||||
* @throws NotAcceptableStatusException if the requested media types is invalid
|
||||
* @throws NotAcceptableStatusException if the requested media type is invalid
|
||||
*/
|
||||
List<MediaType> resolveMediaTypes(ServerWebExchange exchange);
|
||||
|
||||
|
||||
@@ -17,209 +17,141 @@ package org.springframework.web.reactive.accept;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.Map;
|
||||
import java.util.function.Supplier;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.CollectionUtils;
|
||||
|
||||
|
||||
/**
|
||||
* Factory to create a {@link CompositeContentTypeResolver} and configure it with
|
||||
* one or more {@link RequestedContentTypeResolver} instances with build style
|
||||
* methods. The following table shows methods, resulting strategy instances, and
|
||||
* if in use by default:
|
||||
* Builder for a composite {@link RequestedContentTypeResolver} that delegates
|
||||
* to one or more other resolvers each implementing a different strategy to
|
||||
* determine the requested content type(s), e.g. from the Accept header,
|
||||
* through a query parameter, or other custom strategy.
|
||||
*
|
||||
* <table>
|
||||
* <tr>
|
||||
* <th>Property Setter</th>
|
||||
* <th>Underlying Strategy</th>
|
||||
* <th>Default Setting</th>
|
||||
* </tr>
|
||||
* <tr>
|
||||
* <td>{@link #favorPathExtension}</td>
|
||||
* <td>{@link PathExtensionContentTypeResolver Path Extension resolver}</td>
|
||||
* <td>On</td>
|
||||
* </tr>
|
||||
* <tr>
|
||||
* <td>{@link #favorParameter}</td>
|
||||
* <td>{@link ParameterContentTypeResolver Parameter resolver}</td>
|
||||
* <td>Off</td>
|
||||
* </tr>
|
||||
* <tr>
|
||||
* <td>{@link #ignoreAcceptHeader}</td>
|
||||
* <td>{@link HeaderContentTypeResolver Header resolver}</td>
|
||||
* <td>Off</td>
|
||||
* </tr>
|
||||
* <tr>
|
||||
* <td>{@link #defaultContentType}</td>
|
||||
* <td>{@link FixedContentTypeResolver Fixed content resolver}</td>
|
||||
* <td>Not set</td>
|
||||
* </tr>
|
||||
* <tr>
|
||||
* <td>{@link #defaultContentTypeResolver}</td>
|
||||
* <td>{@link RequestedContentTypeResolver}</td>
|
||||
* <td>Not set</td>
|
||||
* </tr>
|
||||
* </table>
|
||||
* <p>Use methods of this builder to add resolvers in the desired order.
|
||||
* The result of the first resolver to return a non-empty list of media types
|
||||
* is used.
|
||||
*
|
||||
* <p>The order in which resolvers are configured is fixed. Config methods may
|
||||
* only turn individual resolvers on or off. If you need a custom order for any
|
||||
* reason simply instantiate {@code {@link CompositeContentTypeResolver}}
|
||||
* directly.
|
||||
*
|
||||
* <p>For the path extension and parameter resolvers you may explicitly add
|
||||
* {@link #mediaTypes(Map)}. This will be used to resolve path extensions or a
|
||||
* parameter value such as "json" to a media type such as "application/json".
|
||||
*
|
||||
* <p>The path extension strategy will also use
|
||||
* {@link org.springframework.http.MediaTypeFactory} to resolve a path extension
|
||||
* to a MediaType.
|
||||
* <p>If no resolvers are configured, by default the builder will configure
|
||||
* {@link HeaderContentTypeResolver} only.
|
||||
*
|
||||
* @author Rossen Stoyanchev
|
||||
* @since 5.0
|
||||
*/
|
||||
public class RequestedContentTypeResolverBuilder {
|
||||
|
||||
private boolean favorPathExtension = true;
|
||||
|
||||
private boolean favorParameter = false;
|
||||
|
||||
private boolean ignoreAcceptHeader = false;
|
||||
|
||||
private Map<String, MediaType> mediaTypes = new HashMap<>();
|
||||
|
||||
private String parameterName = "format";
|
||||
|
||||
private RequestedContentTypeResolver contentTypeResolver;
|
||||
private final List<Supplier<RequestedContentTypeResolver>> candidates = new ArrayList<>();
|
||||
|
||||
|
||||
/**
|
||||
* Whether the path extension in the URL path should be used to determine
|
||||
* the requested media type.
|
||||
* <p>By default this is set to {@code true} in which case a request
|
||||
* for {@code /hotels.pdf} will be interpreted as a request for
|
||||
* {@code "application/pdf"} regardless of the 'Accept' header.
|
||||
* Add resolver extracting the requested content type from a query parameter.
|
||||
* By default the expected query parameter name is {@code "format"}.
|
||||
*/
|
||||
public RequestedContentTypeResolverBuilder favorPathExtension(boolean favorPathExtension) {
|
||||
this.favorPathExtension = favorPathExtension;
|
||||
return this;
|
||||
public ParameterResolverConfigurer parameterResolver() {
|
||||
ParameterResolverConfigurer parameterBuilder = new ParameterResolverConfigurer();
|
||||
this.candidates.add(parameterBuilder::createResolver);
|
||||
return parameterBuilder;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a mapping from a key, extracted from a path extension or a query
|
||||
* parameter, to a MediaType. This is required in order for the parameter
|
||||
* strategy to work. Any extensions explicitly registered here are also
|
||||
* whitelisted for the purpose of Reflected File Download attack detection
|
||||
* (see Spring Framework reference documentation for more details on RFD
|
||||
* attack protection).
|
||||
* <p>The path extension strategy will also use the
|
||||
* {@link org.springframework.http.MediaTypeFactory} to resolve path
|
||||
* extensions.
|
||||
* @param mediaTypes media type mappings
|
||||
* Add resolver extracting the requested content type from the
|
||||
* {@literal "Accept"} header.
|
||||
*/
|
||||
public RequestedContentTypeResolverBuilder mediaTypes(Map<String, MediaType> mediaTypes) {
|
||||
if (!CollectionUtils.isEmpty(mediaTypes)) {
|
||||
for (Map.Entry<String, MediaType> entry : mediaTypes.entrySet()) {
|
||||
String extension = entry.getKey().toLowerCase(Locale.ENGLISH);
|
||||
this.mediaTypes.put(extension, entry.getValue());
|
||||
public void headerResolver() {
|
||||
this.candidates.add(HeaderContentTypeResolver::new);
|
||||
}
|
||||
|
||||
/**
|
||||
* Add resolver that always returns a fixed set of media types.
|
||||
* @param mediaTypes the media types to use
|
||||
*/
|
||||
public void fixedResolver(MediaType... mediaTypes) {
|
||||
this.candidates.add(() -> new FixedContentTypeResolver(Arrays.asList(mediaTypes)));
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a custom resolver.
|
||||
* @param resolver the resolver to add
|
||||
*/
|
||||
public void resolver(RequestedContentTypeResolver resolver) {
|
||||
this.candidates.add(() -> resolver);
|
||||
}
|
||||
|
||||
/**
|
||||
* Build a {@link RequestedContentTypeResolver} that delegates to the list
|
||||
* of resolvers configured through this builder.
|
||||
*/
|
||||
public RequestedContentTypeResolver build() {
|
||||
|
||||
List<RequestedContentTypeResolver> resolvers =
|
||||
this.candidates.isEmpty() ?
|
||||
Collections.singletonList(new HeaderContentTypeResolver()) :
|
||||
this.candidates.stream().map(Supplier::get).collect(Collectors.toList());
|
||||
|
||||
return exchange -> {
|
||||
for (RequestedContentTypeResolver resolver : resolvers) {
|
||||
List<MediaType> type = resolver.resolveMediaTypes(exchange);
|
||||
if (type.isEmpty() || (type.size() == 1 && type.contains(MediaType.ALL))) {
|
||||
continue;
|
||||
}
|
||||
return type;
|
||||
}
|
||||
}
|
||||
return this;
|
||||
return Collections.emptyList();
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Alternative to {@link #mediaTypes} to add a single mapping.
|
||||
* Helps to create a {@link ParameterContentTypeResolver}.
|
||||
*/
|
||||
public RequestedContentTypeResolverBuilder mediaType(String key, MediaType mediaType) {
|
||||
this.mediaTypes.put(key, mediaType);
|
||||
return this;
|
||||
}
|
||||
public static class ParameterResolverConfigurer {
|
||||
|
||||
/**
|
||||
* Whether a request parameter ("format" by default) should be used to
|
||||
* determine the requested media type. For this option to work you must
|
||||
* register {@link #mediaTypes media type mappings}.
|
||||
* <p>By default this is set to {@code false}.
|
||||
* @see #parameterName
|
||||
*/
|
||||
public RequestedContentTypeResolverBuilder favorParameter(boolean favorParameter) {
|
||||
this.favorParameter = favorParameter;
|
||||
return this;
|
||||
}
|
||||
private final Map<String, MediaType> mediaTypes = new HashMap<>();
|
||||
|
||||
/**
|
||||
* Set the query parameter name to use when {@link #favorParameter} is on.
|
||||
* <p>The default parameter name is {@code "format"}.
|
||||
*/
|
||||
public RequestedContentTypeResolverBuilder parameterName(String parameterName) {
|
||||
Assert.notNull(parameterName, "parameterName is required");
|
||||
this.parameterName = parameterName;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether to disable checking the 'Accept' request header.
|
||||
* <p>By default this value is set to {@code false}.
|
||||
*/
|
||||
public RequestedContentTypeResolverBuilder ignoreAcceptHeader(boolean ignoreAcceptHeader) {
|
||||
this.ignoreAcceptHeader = ignoreAcceptHeader;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the default content type(s) to use when no content type is requested
|
||||
* in order of priority.
|
||||
*
|
||||
* <p>If destinations are present that do not support any of the given media
|
||||
* types, consider appending {@link MediaType#ALL} at the end.
|
||||
*
|
||||
* <p>By default this is not set.
|
||||
*
|
||||
* @see #defaultContentTypeResolver
|
||||
*/
|
||||
public RequestedContentTypeResolverBuilder defaultContentType(MediaType... contentTypes) {
|
||||
this.contentTypeResolver = new FixedContentTypeResolver(Arrays.asList(contentTypes));
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set a custom {@link RequestedContentTypeResolver} to use to determine
|
||||
* the content type to use when no content type is requested.
|
||||
* <p>By default this is not set.
|
||||
* @see #defaultContentType
|
||||
*/
|
||||
public RequestedContentTypeResolverBuilder defaultContentTypeResolver(RequestedContentTypeResolver resolver) {
|
||||
this.contentTypeResolver = resolver;
|
||||
return this;
|
||||
}
|
||||
private String parameterName;
|
||||
|
||||
|
||||
public CompositeContentTypeResolver build() {
|
||||
List<RequestedContentTypeResolver> resolvers = new ArrayList<>();
|
||||
|
||||
if (this.favorPathExtension) {
|
||||
resolvers.add(new PathExtensionContentTypeResolver(this.mediaTypes));
|
||||
/**
|
||||
* Configure a mapping between a lookup key (extracted from a query
|
||||
* parameter value) and a corresponding {@code MediaType}.
|
||||
* @param key the lookup key
|
||||
* @param mediaType the MediaType for that key
|
||||
*/
|
||||
public ParameterResolverConfigurer mediaType(String key, MediaType mediaType) {
|
||||
this.mediaTypes.put(key, mediaType);
|
||||
return this;
|
||||
}
|
||||
|
||||
if (this.favorParameter) {
|
||||
/**
|
||||
* Map-based variant of {@link #mediaType(String, MediaType)}.
|
||||
* @param mediaTypes the mappings to copy
|
||||
*/
|
||||
public ParameterResolverConfigurer mediaType(Map<String, MediaType> mediaTypes) {
|
||||
this.mediaTypes.putAll(mediaTypes);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the name of the parameter to use to determine requested media types.
|
||||
* <p>By default this is set to {@literal "format"}.
|
||||
*/
|
||||
public ParameterResolverConfigurer parameterName(String parameterName) {
|
||||
this.parameterName = parameterName;
|
||||
return this;
|
||||
}
|
||||
|
||||
RequestedContentTypeResolver createResolver() {
|
||||
ParameterContentTypeResolver resolver = new ParameterContentTypeResolver(this.mediaTypes);
|
||||
resolver.setParameterName(this.parameterName);
|
||||
resolvers.add(resolver);
|
||||
if (this.parameterName != null) {
|
||||
resolver.setParameterName(this.parameterName);
|
||||
}
|
||||
return resolver;
|
||||
}
|
||||
|
||||
if (!this.ignoreAcceptHeader) {
|
||||
resolvers.add(new HeaderContentTypeResolver());
|
||||
}
|
||||
|
||||
if (this.contentTypeResolver != null) {
|
||||
resolvers.add(this.contentTypeResolver);
|
||||
}
|
||||
|
||||
return new CompositeContentTypeResolver(resolvers);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -16,7 +16,6 @@
|
||||
|
||||
package org.springframework.web.reactive.config;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
@@ -34,7 +33,6 @@ import org.springframework.format.Formatter;
|
||||
import org.springframework.format.FormatterRegistry;
|
||||
import org.springframework.format.support.DefaultFormattingConversionService;
|
||||
import org.springframework.format.support.FormattingConversionService;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.http.codec.ServerCodecConfigurer;
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.util.ClassUtils;
|
||||
@@ -47,7 +45,7 @@ import org.springframework.web.bind.support.ConfigurableWebBindingInitializer;
|
||||
import org.springframework.web.cors.CorsConfiguration;
|
||||
import org.springframework.web.reactive.DispatcherHandler;
|
||||
import org.springframework.web.reactive.HandlerMapping;
|
||||
import org.springframework.web.reactive.accept.CompositeContentTypeResolver;
|
||||
import org.springframework.web.reactive.accept.RequestedContentTypeResolver;
|
||||
import org.springframework.web.reactive.accept.RequestedContentTypeResolverBuilder;
|
||||
import org.springframework.web.reactive.function.server.support.HandlerFunctionAdapter;
|
||||
import org.springframework.web.reactive.function.server.support.RouterFunctionMapping;
|
||||
@@ -147,25 +145,12 @@ public class WebFluxConfigurationSupport implements ApplicationContextAware {
|
||||
}
|
||||
|
||||
@Bean
|
||||
public CompositeContentTypeResolver webFluxContentTypeResolver() {
|
||||
public RequestedContentTypeResolver webFluxContentTypeResolver() {
|
||||
RequestedContentTypeResolverBuilder builder = new RequestedContentTypeResolverBuilder();
|
||||
builder.mediaTypes(getDefaultMediaTypeMappings());
|
||||
configureContentTypeResolver(builder);
|
||||
return builder.build();
|
||||
}
|
||||
|
||||
/**
|
||||
* Override to configure media type mappings.
|
||||
* @see RequestedContentTypeResolverBuilder#mediaTypes(Map)
|
||||
*/
|
||||
protected Map<String, MediaType> getDefaultMediaTypeMappings() {
|
||||
Map<String, MediaType> map = new HashMap<>();
|
||||
if (jackson2Present) {
|
||||
map.put("json", MediaType.APPLICATION_JSON);
|
||||
}
|
||||
return map;
|
||||
}
|
||||
|
||||
/**
|
||||
* Override to configure how the requested content type is resolved.
|
||||
*/
|
||||
|
||||
@@ -23,8 +23,6 @@ import org.springframework.http.codec.ServerCodecConfigurer;
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.validation.MessageCodesResolver;
|
||||
import org.springframework.validation.Validator;
|
||||
import org.springframework.web.reactive.accept.CompositeContentTypeResolver;
|
||||
import org.springframework.web.reactive.accept.RequestedContentTypeResolver;
|
||||
import org.springframework.web.reactive.accept.RequestedContentTypeResolverBuilder;
|
||||
import org.springframework.web.reactive.result.method.annotation.ArgumentResolverConfigurer;
|
||||
|
||||
@@ -45,11 +43,7 @@ public interface WebFluxConfigurer {
|
||||
|
||||
/**
|
||||
* Configure how the content type requested for the response is resolved.
|
||||
* <p>The given builder will create a composite of multiple
|
||||
* {@link RequestedContentTypeResolver}s, each defining a way to resolve
|
||||
* the requested content type (accept HTTP header, path extension,
|
||||
* parameter, etc).
|
||||
* @param builder factory that creates a {@link CompositeContentTypeResolver}
|
||||
* @param builder for configuring the resolvers to use
|
||||
*/
|
||||
default void configureContentTypeResolver(RequestedContentTypeResolverBuilder builder) {
|
||||
}
|
||||
|
||||
@@ -28,9 +28,9 @@ import org.springframework.http.MediaType;
|
||||
import org.springframework.web.accept.ContentNegotiationManager;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.cors.reactive.CorsUtils;
|
||||
import org.springframework.web.reactive.accept.RequestedContentTypeResolverBuilder;
|
||||
import org.springframework.web.reactive.accept.RequestedContentTypeResolver;
|
||||
import org.springframework.web.reactive.accept.HeaderContentTypeResolver;
|
||||
import org.springframework.web.reactive.accept.RequestedContentTypeResolver;
|
||||
import org.springframework.web.reactive.accept.RequestedContentTypeResolverBuilder;
|
||||
import org.springframework.web.server.NotAcceptableStatusException;
|
||||
import org.springframework.web.server.ServerWebExchange;
|
||||
|
||||
@@ -99,8 +99,7 @@ public final class ProducesRequestCondition extends AbstractRequestCondition<Pro
|
||||
|
||||
this.expressions = new ArrayList<>(expressions);
|
||||
Collections.sort(this.expressions);
|
||||
this.contentTypeResolver = (resolver != null ?
|
||||
resolver : new RequestedContentTypeResolverBuilder().build());
|
||||
this.contentTypeResolver = (resolver != null ? resolver : new RequestedContentTypeResolverBuilder().build());
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -1,132 +0,0 @@
|
||||
/*
|
||||
* Copyright 2002-2017 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 org.junit.Test;
|
||||
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.mock.http.server.reactive.test.MockServerHttpRequest;
|
||||
import org.springframework.mock.http.server.reactive.test.MockServerWebExchange;
|
||||
import org.springframework.web.server.ServerWebExchange;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
|
||||
/**
|
||||
* Unit tests for {@link RequestedContentTypeResolverBuilder}.
|
||||
*
|
||||
* @author Rossen Stoyanchev
|
||||
*/
|
||||
public class CompositeContentTypeResolverBuilderTests {
|
||||
|
||||
@Test
|
||||
public void defaultSettings() throws Exception {
|
||||
RequestedContentTypeResolver resolver = new RequestedContentTypeResolverBuilder().build();
|
||||
|
||||
MockServerWebExchange exchange = MockServerHttpRequest.get("/flower.gif").toExchange();
|
||||
|
||||
assertEquals("Should be able to resolve file extensions by default",
|
||||
Collections.singletonList(MediaType.IMAGE_GIF), resolver.resolveMediaTypes(exchange));
|
||||
|
||||
exchange = MockServerHttpRequest.get("/flower.foobar").toExchange();
|
||||
|
||||
assertEquals("Should ignore unknown extensions by default",
|
||||
Collections.<MediaType>emptyList(), resolver.resolveMediaTypes(exchange));
|
||||
|
||||
exchange = MockServerHttpRequest.get("/flower?format=gif").toExchange();
|
||||
|
||||
assertEquals("Should not resolve request parameters by default",
|
||||
Collections.<MediaType>emptyList(), resolver.resolveMediaTypes(exchange));
|
||||
|
||||
exchange = MockServerHttpRequest.get("/flower").accept(MediaType.IMAGE_GIF).toExchange();
|
||||
|
||||
assertEquals("Should resolve Accept header by default",
|
||||
Collections.singletonList(MediaType.IMAGE_GIF), resolver.resolveMediaTypes(exchange));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void favorPath() throws Exception {
|
||||
RequestedContentTypeResolver resolver = new RequestedContentTypeResolverBuilder()
|
||||
.favorPathExtension(true)
|
||||
.mediaType("foo", new MediaType("application", "foo"))
|
||||
.mediaType("bar", new MediaType("application", "bar"))
|
||||
.build();
|
||||
|
||||
ServerWebExchange exchange = MockServerHttpRequest.get("/flower.foo").toExchange();
|
||||
assertEquals(Collections.singletonList(new MediaType("application", "foo")),
|
||||
resolver.resolveMediaTypes(exchange));
|
||||
|
||||
exchange = MockServerHttpRequest.get("/flower.bar").toExchange();
|
||||
assertEquals(Collections.singletonList(new MediaType("application", "bar")),
|
||||
resolver.resolveMediaTypes(exchange));
|
||||
|
||||
exchange = MockServerHttpRequest.get("/flower.gif").toExchange();
|
||||
assertEquals(Collections.singletonList(MediaType.IMAGE_GIF), resolver.resolveMediaTypes(exchange));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void favorParameter() throws Exception {
|
||||
RequestedContentTypeResolver resolver = new RequestedContentTypeResolverBuilder()
|
||||
.favorParameter(true)
|
||||
.mediaType("json", MediaType.APPLICATION_JSON)
|
||||
.build();
|
||||
|
||||
ServerWebExchange exchange = MockServerHttpRequest.get("/flower?format=json").toExchange();
|
||||
|
||||
assertEquals(Collections.singletonList(MediaType.APPLICATION_JSON), resolver.resolveMediaTypes(exchange));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void ignoreAcceptHeader() throws Exception {
|
||||
RequestedContentTypeResolver resolver = new RequestedContentTypeResolverBuilder()
|
||||
.ignoreAcceptHeader(true)
|
||||
.build();
|
||||
|
||||
ServerWebExchange exchange = MockServerHttpRequest.get("/flower").accept(MediaType.IMAGE_GIF).toExchange();
|
||||
|
||||
assertEquals(Collections.<MediaType>emptyList(), resolver.resolveMediaTypes(exchange));
|
||||
}
|
||||
|
||||
@Test // SPR-10513
|
||||
public void setDefaultContentType() throws Exception {
|
||||
RequestedContentTypeResolver resolver = new RequestedContentTypeResolverBuilder()
|
||||
.defaultContentType(MediaType.APPLICATION_JSON)
|
||||
.build();
|
||||
|
||||
ServerWebExchange exchange = MockServerHttpRequest.get("/").accept(MediaType.ALL).toExchange();
|
||||
|
||||
assertEquals(Collections.singletonList(MediaType.APPLICATION_JSON), resolver.resolveMediaTypes(exchange));
|
||||
}
|
||||
|
||||
@Test // SPR-12286
|
||||
public void setDefaultContentTypeWithStrategy() throws Exception {
|
||||
RequestedContentTypeResolver resolver = new RequestedContentTypeResolverBuilder()
|
||||
.defaultContentTypeResolver(new FixedContentTypeResolver(MediaType.APPLICATION_JSON))
|
||||
.build();
|
||||
|
||||
List<MediaType> expected = Collections.singletonList(MediaType.APPLICATION_JSON);
|
||||
|
||||
ServerWebExchange exchange = MockServerHttpRequest.get("/").toExchange();
|
||||
assertEquals(expected, resolver.resolveMediaTypes(exchange));
|
||||
|
||||
exchange = MockServerHttpRequest.get("/").accept(MediaType.ALL).toExchange();
|
||||
assertEquals(expected, resolver.resolveMediaTypes(exchange));
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -1,103 +0,0 @@
|
||||
/*
|
||||
* Copyright 2002-2017 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 org.junit.Test;
|
||||
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.mock.http.server.reactive.test.MockServerHttpRequest;
|
||||
import org.springframework.web.server.NotAcceptableStatusException;
|
||||
import org.springframework.web.server.ServerWebExchange;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
|
||||
/**
|
||||
* Unit tests for {@link AbstractMappingContentTypeResolver}.
|
||||
* @author Rossen Stoyanchev
|
||||
*/
|
||||
public class MappingContentTypeResolverTests {
|
||||
|
||||
@Test // SPR-13747
|
||||
public void resolveCaseInsensitive() {
|
||||
Map<String, MediaType> mapping = Collections.singletonMap("json", MediaType.APPLICATION_JSON);
|
||||
TestMappingContentTypeResolver resolver = new TestMappingContentTypeResolver("JSoN", mapping);
|
||||
List<MediaType> mediaTypes = resolver.resolve();
|
||||
|
||||
assertEquals(Collections.singletonList(MediaType.APPLICATION_JSON), mediaTypes);
|
||||
}
|
||||
|
||||
@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.resolve();
|
||||
|
||||
assertEquals(1, mediaTypes.size());
|
||||
assertEquals("application/json", mediaTypes.get(0).toString());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void resolveNoMatch() throws Exception {
|
||||
TestMappingContentTypeResolver resolver = new TestMappingContentTypeResolver("blah", Collections.emptyMap());
|
||||
List<MediaType> mediaTypes = resolver.resolve();
|
||||
|
||||
assertEquals(0, mediaTypes.size());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void resolveNoKey() throws Exception {
|
||||
Map<String, MediaType> mapping = Collections.singletonMap("json", MediaType.APPLICATION_JSON);
|
||||
TestMappingContentTypeResolver resolver = new TestMappingContentTypeResolver(null, mapping);
|
||||
List<MediaType> mediaTypes = resolver.resolve();
|
||||
|
||||
assertEquals(0, mediaTypes.size());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void resolveMediaTypesHandleNoMatch() throws Exception {
|
||||
TestMappingContentTypeResolver resolver = new TestMappingContentTypeResolver("xml", Collections.emptyMap());
|
||||
List<MediaType> mediaTypes = resolver.resolve();
|
||||
|
||||
assertEquals(1, mediaTypes.size());
|
||||
assertEquals("application/xml", mediaTypes.get(0).toString());
|
||||
}
|
||||
|
||||
|
||||
private static class TestMappingContentTypeResolver extends AbstractMappingContentTypeResolver {
|
||||
|
||||
private final String key;
|
||||
|
||||
TestMappingContentTypeResolver(@Nullable String key, Map<String, MediaType> mapping) {
|
||||
super(mapping);
|
||||
this.key = key;
|
||||
}
|
||||
|
||||
public List<MediaType> resolve() throws NotAcceptableStatusException {
|
||||
return super.resolveMediaTypes(MockServerHttpRequest.get("/").toExchange());
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String getKey(ServerWebExchange exchange) {
|
||||
return this.key;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,92 @@
|
||||
/*
|
||||
* Copyright 2002-2017 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 org.junit.Test;
|
||||
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.mock.http.server.reactive.test.MockServerHttpRequest;
|
||||
import org.springframework.mock.http.server.reactive.test.MockServerWebExchange;
|
||||
import org.springframework.web.server.NotAcceptableStatusException;
|
||||
import org.springframework.web.server.ServerWebExchange;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
|
||||
/**
|
||||
* Unit tests for {@link ParameterContentTypeResolver}.
|
||||
* @author Rossen Stoyanchev
|
||||
*/
|
||||
public class ParameterContentTypeResolverTests {
|
||||
|
||||
@Test
|
||||
public void noKey() throws Exception {
|
||||
ParameterContentTypeResolver resolver = new ParameterContentTypeResolver(Collections.emptyMap());
|
||||
List<MediaType> mediaTypes = resolver.resolveMediaTypes(MockServerHttpRequest.get("/").toExchange());
|
||||
|
||||
assertEquals(0, mediaTypes.size());
|
||||
}
|
||||
|
||||
@Test(expected = NotAcceptableStatusException.class)
|
||||
public void noMatchForKey() throws Exception {
|
||||
ParameterContentTypeResolver resolver = new ParameterContentTypeResolver(Collections.emptyMap());
|
||||
List<MediaType> mediaTypes = resolver.resolveMediaTypes(createExchange("blah"));
|
||||
|
||||
assertEquals(0, mediaTypes.size());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void resolveKeyFromRegistrations() throws Exception {
|
||||
ServerWebExchange exchange = createExchange("html");
|
||||
|
||||
Map<String, MediaType> mapping = Collections.emptyMap();
|
||||
RequestedContentTypeResolver resolver = new ParameterContentTypeResolver(mapping);
|
||||
List<MediaType> mediaTypes = resolver.resolveMediaTypes(exchange);
|
||||
assertEquals(Collections.singletonList(new MediaType("text", "html")), mediaTypes);
|
||||
|
||||
mapping = Collections.singletonMap("HTML", MediaType.APPLICATION_XHTML_XML);
|
||||
resolver = new ParameterContentTypeResolver(mapping);
|
||||
mediaTypes = resolver.resolveMediaTypes(exchange);
|
||||
assertEquals(Collections.singletonList(new MediaType("application", "xhtml+xml")), mediaTypes);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void resolveKeyThroughMediaTypeFactory() throws Exception {
|
||||
ServerWebExchange exchange = createExchange("xls");
|
||||
RequestedContentTypeResolver resolver = new ParameterContentTypeResolver(Collections.emptyMap());
|
||||
List<MediaType> mediaTypes = resolver.resolveMediaTypes(exchange);
|
||||
|
||||
assertEquals(Collections.singletonList(new MediaType("application", "vnd.ms-excel")), mediaTypes);
|
||||
}
|
||||
|
||||
@Test // SPR-13747
|
||||
public void resolveKeyIsCaseInsensitive() {
|
||||
ServerWebExchange exchange = createExchange("JSoN");
|
||||
Map<String, MediaType> mapping = Collections.singletonMap("json", MediaType.APPLICATION_JSON);
|
||||
ParameterContentTypeResolver resolver = new ParameterContentTypeResolver(mapping);
|
||||
List<MediaType> mediaTypes = resolver.resolveMediaTypes(exchange);
|
||||
|
||||
assertEquals(Collections.singletonList(MediaType.APPLICATION_JSON), mediaTypes);
|
||||
}
|
||||
|
||||
private MockServerWebExchange createExchange(String format) {
|
||||
return MockServerHttpRequest.get("/path?format=" + format).toExchange();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,83 +0,0 @@
|
||||
/*
|
||||
* Copyright 2002-2017 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 org.junit.Test;
|
||||
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.mock.http.server.reactive.test.MockServerHttpRequest;
|
||||
import org.springframework.web.server.ServerWebExchange;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
|
||||
/**
|
||||
* Unit tests for {@link PathExtensionContentTypeResolver}.
|
||||
*
|
||||
* @author Rossen Stoyanchev
|
||||
*/
|
||||
public class PathExtensionContentTypeResolverTests {
|
||||
|
||||
@Test
|
||||
public void resolveFromRegistrations() throws Exception {
|
||||
ServerWebExchange exchange = MockServerHttpRequest.get("/test.html").toExchange();
|
||||
PathExtensionContentTypeResolver resolver = createResolver();
|
||||
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 resolveFromMediaTypeFactory() throws Exception {
|
||||
ServerWebExchange exchange = MockServerHttpRequest.get("test.xls").toExchange();
|
||||
PathExtensionContentTypeResolver resolver = createResolver();
|
||||
List<MediaType> mediaTypes = resolver.resolveMediaTypes(exchange);
|
||||
|
||||
assertEquals(Collections.singletonList(new MediaType("application", "vnd.ms-excel")), mediaTypes);
|
||||
}
|
||||
|
||||
@Test // SPR-9390
|
||||
public void resolveFromFilenameWithEncodedURI() throws Exception {
|
||||
ServerWebExchange exchange = MockServerHttpRequest.get("/quo%20vadis%3f.html").toExchange();
|
||||
PathExtensionContentTypeResolver resolver = createResolver();
|
||||
List<MediaType> result = resolver.resolveMediaTypes(exchange);
|
||||
|
||||
assertEquals("Invalid content type", Collections.singletonList(new MediaType("text", "html")), result);
|
||||
}
|
||||
|
||||
@Test // SPR-10170
|
||||
public void resolveAndIgnoreUnknownExtension() throws Exception {
|
||||
ServerWebExchange exchange = MockServerHttpRequest.get("test.foobar").toExchange();
|
||||
PathExtensionContentTypeResolver resolver = createResolver();
|
||||
List<MediaType> mediaTypes = resolver.resolveMediaTypes(exchange);
|
||||
|
||||
assertEquals(Collections.<MediaType>emptyList(), mediaTypes);
|
||||
}
|
||||
|
||||
private PathExtensionContentTypeResolver createResolver() {
|
||||
return new PathExtensionContentTypeResolver(Collections.emptyMap());
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,100 @@
|
||||
/*
|
||||
* Copyright 2002-2017 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 org.junit.Test;
|
||||
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.mock.http.server.reactive.test.MockServerHttpRequest;
|
||||
import org.springframework.web.server.ServerWebExchange;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
|
||||
/**
|
||||
* Unit tests for {@link RequestedContentTypeResolverBuilder}.
|
||||
* @author Rossen Stoyanchev
|
||||
*/
|
||||
public class RequestedContentTypeResolverBuilderTests {
|
||||
|
||||
@Test
|
||||
public void defaultSettings() throws Exception {
|
||||
|
||||
RequestedContentTypeResolver resolver = new RequestedContentTypeResolverBuilder().build();
|
||||
ServerWebExchange exchange = MockServerHttpRequest.get("/flower").accept(MediaType.IMAGE_GIF).toExchange();
|
||||
List<MediaType> mediaTypes = resolver.resolveMediaTypes(exchange);
|
||||
|
||||
assertEquals(Collections.singletonList(MediaType.IMAGE_GIF), mediaTypes);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void parameterResolver() throws Exception {
|
||||
|
||||
RequestedContentTypeResolverBuilder builder = new RequestedContentTypeResolverBuilder();
|
||||
builder.parameterResolver().mediaType("json", MediaType.APPLICATION_JSON);
|
||||
RequestedContentTypeResolver resolver = builder.build();
|
||||
|
||||
ServerWebExchange exchange = MockServerHttpRequest.get("/flower?format=json").toExchange();
|
||||
List<MediaType> mediaTypes = resolver.resolveMediaTypes(exchange);
|
||||
|
||||
assertEquals(Collections.singletonList(MediaType.APPLICATION_JSON), mediaTypes);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void parameterResolverWithCustomParamName() throws Exception {
|
||||
|
||||
RequestedContentTypeResolverBuilder builder = new RequestedContentTypeResolverBuilder();
|
||||
builder.parameterResolver().mediaType("json", MediaType.APPLICATION_JSON).parameterName("s");
|
||||
RequestedContentTypeResolver resolver = builder.build();
|
||||
|
||||
ServerWebExchange exchange = MockServerHttpRequest.get("/flower?s=json").toExchange();
|
||||
List<MediaType> mediaTypes = resolver.resolveMediaTypes(exchange);
|
||||
|
||||
assertEquals(Collections.singletonList(MediaType.APPLICATION_JSON), mediaTypes);
|
||||
}
|
||||
|
||||
@Test // SPR-10513
|
||||
public void fixedResolver() throws Exception {
|
||||
|
||||
RequestedContentTypeResolverBuilder builder = new RequestedContentTypeResolverBuilder();
|
||||
builder.fixedResolver(MediaType.APPLICATION_JSON);
|
||||
RequestedContentTypeResolver resolver = builder.build();
|
||||
|
||||
ServerWebExchange exchange = MockServerHttpRequest.get("/").accept(MediaType.ALL).toExchange();
|
||||
List<MediaType> mediaTypes = resolver.resolveMediaTypes(exchange);
|
||||
|
||||
assertEquals(Collections.singletonList(MediaType.APPLICATION_JSON), mediaTypes);
|
||||
}
|
||||
|
||||
@Test // SPR-12286
|
||||
public void resolver() throws Exception {
|
||||
|
||||
RequestedContentTypeResolverBuilder builder = new RequestedContentTypeResolverBuilder();
|
||||
builder.resolver(new FixedContentTypeResolver(MediaType.APPLICATION_JSON));
|
||||
RequestedContentTypeResolver resolver = builder.build();
|
||||
|
||||
ServerWebExchange exchange = MockServerHttpRequest.get("/").toExchange();
|
||||
List<MediaType> mediaTypes = resolver.resolveMediaTypes(exchange);
|
||||
assertEquals(Collections.singletonList(MediaType.APPLICATION_JSON), mediaTypes);
|
||||
|
||||
exchange = MockServerHttpRequest.get("/").accept(MediaType.ALL).toExchange();
|
||||
mediaTypes = resolver.resolveMediaTypes(exchange);
|
||||
assertEquals(Collections.singletonList(MediaType.APPLICATION_JSON), mediaTypes);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -40,7 +40,6 @@ import org.springframework.http.codec.ServerCodecConfigurer;
|
||||
import org.springframework.http.codec.json.Jackson2JsonEncoder;
|
||||
import org.springframework.http.codec.xml.Jaxb2XmlDecoder;
|
||||
import org.springframework.http.codec.xml.Jaxb2XmlEncoder;
|
||||
import org.springframework.mock.http.server.reactive.test.MockServerHttpRequest;
|
||||
import org.springframework.util.MimeType;
|
||||
import org.springframework.util.MimeTypeUtils;
|
||||
import org.springframework.util.MultiValueMap;
|
||||
@@ -76,6 +75,7 @@ import static org.springframework.http.MediaType.APPLICATION_OCTET_STREAM;
|
||||
import static org.springframework.http.MediaType.APPLICATION_XML;
|
||||
import static org.springframework.http.MediaType.IMAGE_PNG;
|
||||
import static org.springframework.http.MediaType.TEXT_PLAIN;
|
||||
import static org.springframework.mock.http.server.reactive.test.MockServerHttpRequest.get;
|
||||
|
||||
/**
|
||||
* Unit tests for {@link WebFluxConfigurationSupport}.
|
||||
@@ -102,12 +102,8 @@ public class WebFluxConfigurationSupportTests {
|
||||
RequestedContentTypeResolver resolver = context.getBean(name, RequestedContentTypeResolver.class);
|
||||
assertSame(resolver, mapping.getContentTypeResolver());
|
||||
|
||||
ServerWebExchange exchange = MockServerHttpRequest.get("/path.json").toExchange();
|
||||
List<MediaType> list = Collections.singletonList(MediaType.APPLICATION_JSON);
|
||||
assertEquals(list, resolver.resolveMediaTypes(exchange));
|
||||
|
||||
exchange = MockServerHttpRequest.get("/path.foobar").toExchange();
|
||||
assertEquals(Collections.emptyList(), resolver.resolveMediaTypes(exchange));
|
||||
ServerWebExchange exchange = get("/path").accept(MediaType.APPLICATION_JSON).toExchange();
|
||||
assertEquals(Collections.singletonList(MediaType.APPLICATION_JSON), resolver.resolveMediaTypes(exchange));
|
||||
}
|
||||
|
||||
@Test
|
||||
|
||||
@@ -52,11 +52,13 @@ import org.springframework.util.ObjectUtils;
|
||||
import org.springframework.web.reactive.accept.RequestedContentTypeResolver;
|
||||
import org.springframework.web.reactive.accept.RequestedContentTypeResolverBuilder;
|
||||
|
||||
import static org.junit.Assert.*;
|
||||
import static org.springframework.core.io.buffer.support.DataBufferTestUtils.*;
|
||||
import static org.springframework.http.MediaType.*;
|
||||
import static org.springframework.web.method.ResolvableMethod.*;
|
||||
import static org.springframework.web.reactive.HandlerMapping.*;
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertNull;
|
||||
import static org.springframework.core.io.buffer.support.DataBufferTestUtils.dumpString;
|
||||
import static org.springframework.http.MediaType.APPLICATION_JSON;
|
||||
import static org.springframework.http.MediaType.APPLICATION_JSON_UTF8;
|
||||
import static org.springframework.web.method.ResolvableMethod.on;
|
||||
import static org.springframework.web.reactive.HandlerMapping.PRODUCIBLE_MEDIA_TYPES_ATTRIBUTE;
|
||||
|
||||
/**
|
||||
* Unit tests for {@link AbstractMessageWriterResultHandler}.
|
||||
|
||||
@@ -29,8 +29,8 @@ import rx.Single;
|
||||
import org.springframework.core.codec.ByteBufferEncoder;
|
||||
import org.springframework.core.codec.CharSequenceEncoder;
|
||||
import org.springframework.http.codec.EncoderHttpMessageWriter;
|
||||
import org.springframework.http.codec.ResourceHttpMessageWriter;
|
||||
import org.springframework.http.codec.HttpMessageWriter;
|
||||
import org.springframework.http.codec.ResourceHttpMessageWriter;
|
||||
import org.springframework.http.codec.json.Jackson2JsonEncoder;
|
||||
import org.springframework.http.codec.xml.Jaxb2XmlEncoder;
|
||||
import org.springframework.stereotype.Controller;
|
||||
|
||||
@@ -44,8 +44,8 @@ import org.springframework.http.HttpHeaders;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.http.codec.EncoderHttpMessageWriter;
|
||||
import org.springframework.http.codec.ResourceHttpMessageWriter;
|
||||
import org.springframework.http.codec.HttpMessageWriter;
|
||||
import org.springframework.http.codec.ResourceHttpMessageWriter;
|
||||
import org.springframework.http.codec.json.Jackson2JsonEncoder;
|
||||
import org.springframework.http.codec.xml.Jaxb2XmlEncoder;
|
||||
import org.springframework.mock.http.server.reactive.test.MockServerWebExchange;
|
||||
|
||||
Reference in New Issue
Block a user