Consistent support for multiple Accept headers

Issue: SPR-14506
(cherry picked from commit e59a599)
This commit is contained in:
Juergen Hoeller
2016-07-22 22:27:58 +02:00
parent 77f22e9674
commit 9ed087d5da
6 changed files with 80 additions and 46 deletions

View File

@@ -429,19 +429,7 @@ public class HttpHeaders implements MultiValueMap<String, String>, Serializable
* <p>Returns an empty list when the acceptable media types are unspecified.
*/
public List<MediaType> getAccept() {
String value = getFirst(ACCEPT);
List<MediaType> result = (value != null ? MediaType.parseMediaTypes(value) : Collections.<MediaType>emptyList());
// Some containers parse 'Accept' into multiple values
if (result.size() == 1) {
List<String> acceptHeader = get(ACCEPT);
if (acceptHeader.size() > 1) {
value = StringUtils.collectionToCommaDelimitedString(acceptHeader);
result = MediaType.parseMediaTypes(value);
}
}
return result;
return MediaType.parseMediaTypes(get(ACCEPT));
}
/**
@@ -452,7 +440,7 @@ public class HttpHeaders implements MultiValueMap<String, String>, Serializable
}
/**
* Returns the value of the {@code Access-Control-Allow-Credentials} response header.
* Return the value of the {@code Access-Control-Allow-Credentials} response header.
*/
public boolean getAccessControlAllowCredentials() {
return Boolean.parseBoolean(getFirst(ACCESS_CONTROL_ALLOW_CREDENTIALS));
@@ -466,7 +454,7 @@ public class HttpHeaders implements MultiValueMap<String, String>, Serializable
}
/**
* Returns the value of the {@code Access-Control-Allow-Headers} response header.
* Return the value of the {@code Access-Control-Allow-Headers} response header.
*/
public List<String> getAccessControlAllowHeaders() {
return getValuesAsList(ACCESS_CONTROL_ALLOW_HEADERS);
@@ -519,7 +507,7 @@ public class HttpHeaders implements MultiValueMap<String, String>, Serializable
}
/**
* Returns the value of the {@code Access-Control-Expose-Headers} response header.
* Return the value of the {@code Access-Control-Expose-Headers} response header.
*/
public List<String> getAccessControlExposeHeaders() {
return getValuesAsList(ACCESS_CONTROL_EXPOSE_HEADERS);
@@ -533,7 +521,7 @@ public class HttpHeaders implements MultiValueMap<String, String>, Serializable
}
/**
* Returns the value of the {@code Access-Control-Max-Age} response header.
* Return the value of the {@code Access-Control-Max-Age} response header.
* <p>Returns -1 when the max age is unknown.
*/
public long getAccessControlMaxAge() {
@@ -549,7 +537,7 @@ public class HttpHeaders implements MultiValueMap<String, String>, Serializable
}
/**
* Returns the value of the {@code Access-Control-Request-Headers} request header.
* Return the value of the {@code Access-Control-Request-Headers} request header.
*/
public List<String> getAccessControlRequestHeaders() {
return getValuesAsList(ACCESS_CONTROL_REQUEST_HEADERS);

View File

@@ -27,6 +27,7 @@ import java.util.List;
import java.util.Map;
import org.springframework.util.Assert;
import org.springframework.util.CollectionUtils;
import org.springframework.util.InvalidMimeTypeException;
import org.springframework.util.MimeType;
import org.springframework.util.MimeTypeUtils;
@@ -42,8 +43,7 @@ import org.springframework.util.comparator.CompoundComparator;
* @author Rossen Stoyanchev
* @author Sebastien Deleuze
* @since 3.0
* @see <a href="http://tools.ietf.org/html/rfc7231#section-3.1.1.1">HTTP 1.1: Semantics
* and Content, section 3.1.1.1</a>
* @see <a href="http://tools.ietf.org/html/rfc7231#section-3.1.1.1">HTTP 1.1: Semantics and Content, section 3.1.1.1</a>
*/
public class MediaType extends MimeType implements Serializable {
@@ -397,6 +397,8 @@ public class MediaType extends MimeType implements Serializable {
* Parse the given String value into a {@code MediaType} object,
* with this method name following the 'valueOf' naming convention
* (as supported by {@link org.springframework.core.convert.ConversionService}.
* @param value the string to parse
* @throws InvalidMediaTypeException if the media type value cannot be parsed
* @see #parseMediaType(String)
*/
public static MediaType valueOf(String value) {
@@ -407,7 +409,7 @@ public class MediaType extends MimeType implements Serializable {
* Parse the given String into a single {@code MediaType}.
* @param mediaType the string to parse
* @return the media type
* @throws InvalidMediaTypeException if the string cannot be parsed
* @throws InvalidMediaTypeException if the media type value cannot be parsed
*/
public static MediaType parseMediaType(String mediaType) {
MimeType type;
@@ -425,13 +427,12 @@ public class MediaType extends MimeType implements Serializable {
}
}
/**
* Parse the given, comma-separated string into a list of {@code MediaType} objects.
* Parse the given comma-separated string into a list of {@code MediaType} objects.
* <p>This method can be used to parse an Accept or Content-Type header.
* @param mediaTypes the string to parse
* @return the list of media types
* @throws IllegalArgumentException if the string cannot be parsed
* @throws InvalidMediaTypeException if the media type value cannot be parsed
*/
public static List<MediaType> parseMediaTypes(String mediaTypes) {
if (!StringUtils.hasLength(mediaTypes)) {
@@ -445,6 +446,31 @@ public class MediaType extends MimeType implements Serializable {
return result;
}
/**
* Parse the given list of (potentially) comma-separated strings into a
* list of {@code MediaType} objects.
* <p>This method can be used to parse an Accept or Content-Type header.
* @param mediaTypes the string to parse
* @return the list of media types
* @throws InvalidMediaTypeException if the media type value cannot be parsed
* @since 4.3.2
*/
public static List<MediaType> parseMediaTypes(List<String> mediaTypes) {
if (CollectionUtils.isEmpty(mediaTypes)) {
return Collections.<MediaType>emptyList();
}
else if (mediaTypes.size() == 1) {
return parseMediaTypes(mediaTypes.get(0));
}
else {
List<MediaType> result = new ArrayList<MediaType>(8);
for (String mediaType : mediaTypes) {
result.addAll(parseMediaTypes(mediaType));
}
return result;
}
}
/**
* Return a string representation of the given list of {@code MediaType} objects.
* <p>This method can be used to for an {@code Accept} or {@code Content-Type} header.

View File

@@ -16,13 +16,13 @@
package org.springframework.web.accept;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import org.springframework.http.HttpHeaders;
import org.springframework.http.InvalidMediaTypeException;
import org.springframework.http.MediaType;
import org.springframework.util.StringUtils;
import org.springframework.web.HttpMediaTypeNotAcceptableException;
import org.springframework.web.context.request.NativeWebRequest;
@@ -30,6 +30,7 @@ import org.springframework.web.context.request.NativeWebRequest;
* A {@code ContentNegotiationStrategy} that checks the 'Accept' request header.
*
* @author Rossen Stoyanchev
* @author Juergen Hoeller
* @since 3.2
*/
public class HeaderContentNegotiationStrategy implements ContentNegotiationStrategy {
@@ -42,18 +43,20 @@ public class HeaderContentNegotiationStrategy implements ContentNegotiationStrat
public List<MediaType> resolveMediaTypes(NativeWebRequest request)
throws HttpMediaTypeNotAcceptableException {
String header = request.getHeader(HttpHeaders.ACCEPT);
if (!StringUtils.hasText(header)) {
return Collections.emptyList();
String[] headerValueArray = request.getHeaderValues(HttpHeaders.ACCEPT);
if (headerValueArray == null) {
return Collections.<MediaType>emptyList();
}
List<String> headerValues = Arrays.asList(headerValueArray);
try {
List<MediaType> mediaTypes = MediaType.parseMediaTypes(header);
List<MediaType> mediaTypes = MediaType.parseMediaTypes(headerValues);
MediaType.sortBySpecificityAndQuality(mediaTypes);
return mediaTypes;
}
catch (InvalidMediaTypeException ex) {
throw new HttpMediaTypeNotAcceptableException(
"Could not parse 'Accept' header [" + header + "]: " + ex.getMessage());
"Could not parse 'Accept' header " + headerValues + ": " + ex.getMessage());
}
}