Skip to content

Commit 9ed087d

Browse files
committed
Consistent support for multiple Accept headers
Issue: SPR-14506 (cherry picked from commit e59a599)
1 parent 77f22e9 commit 9ed087d

File tree

6 files changed

+80
-46
lines changed

6 files changed

+80
-46
lines changed

spring-web/src/main/java/org/springframework/http/HttpHeaders.java

Lines changed: 6 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -429,19 +429,7 @@ public void setAccept(List<MediaType> acceptableMediaTypes) {
429429
* <p>Returns an empty list when the acceptable media types are unspecified.
430430
*/
431431
public List<MediaType> getAccept() {
432-
String value = getFirst(ACCEPT);
433-
List<MediaType> result = (value != null ? MediaType.parseMediaTypes(value) : Collections.<MediaType>emptyList());
434-
435-
// Some containers parse 'Accept' into multiple values
436-
if (result.size() == 1) {
437-
List<String> acceptHeader = get(ACCEPT);
438-
if (acceptHeader.size() > 1) {
439-
value = StringUtils.collectionToCommaDelimitedString(acceptHeader);
440-
result = MediaType.parseMediaTypes(value);
441-
}
442-
}
443-
444-
return result;
432+
return MediaType.parseMediaTypes(get(ACCEPT));
445433
}
446434

447435
/**
@@ -452,7 +440,7 @@ public void setAccessControlAllowCredentials(boolean allowCredentials) {
452440
}
453441

454442
/**
455-
* Returns the value of the {@code Access-Control-Allow-Credentials} response header.
443+
* Return the value of the {@code Access-Control-Allow-Credentials} response header.
456444
*/
457445
public boolean getAccessControlAllowCredentials() {
458446
return Boolean.parseBoolean(getFirst(ACCESS_CONTROL_ALLOW_CREDENTIALS));
@@ -466,7 +454,7 @@ public void setAccessControlAllowHeaders(List<String> allowedHeaders) {
466454
}
467455

468456
/**
469-
* Returns the value of the {@code Access-Control-Allow-Headers} response header.
457+
* Return the value of the {@code Access-Control-Allow-Headers} response header.
470458
*/
471459
public List<String> getAccessControlAllowHeaders() {
472460
return getValuesAsList(ACCESS_CONTROL_ALLOW_HEADERS);
@@ -519,7 +507,7 @@ public void setAccessControlExposeHeaders(List<String> exposedHeaders) {
519507
}
520508

521509
/**
522-
* Returns the value of the {@code Access-Control-Expose-Headers} response header.
510+
* Return the value of the {@code Access-Control-Expose-Headers} response header.
523511
*/
524512
public List<String> getAccessControlExposeHeaders() {
525513
return getValuesAsList(ACCESS_CONTROL_EXPOSE_HEADERS);
@@ -533,7 +521,7 @@ public void setAccessControlMaxAge(long maxAge) {
533521
}
534522

535523
/**
536-
* Returns the value of the {@code Access-Control-Max-Age} response header.
524+
* Return the value of the {@code Access-Control-Max-Age} response header.
537525
* <p>Returns -1 when the max age is unknown.
538526
*/
539527
public long getAccessControlMaxAge() {
@@ -549,7 +537,7 @@ public void setAccessControlRequestHeaders(List<String> requestHeaders) {
549537
}
550538

551539
/**
552-
* Returns the value of the {@code Access-Control-Request-Headers} request header.
540+
* Return the value of the {@code Access-Control-Request-Headers} request header.
553541
*/
554542
public List<String> getAccessControlRequestHeaders() {
555543
return getValuesAsList(ACCESS_CONTROL_REQUEST_HEADERS);

spring-web/src/main/java/org/springframework/http/MediaType.java

Lines changed: 32 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@
2727
import java.util.Map;
2828

2929
import org.springframework.util.Assert;
30+
import org.springframework.util.CollectionUtils;
3031
import org.springframework.util.InvalidMimeTypeException;
3132
import org.springframework.util.MimeType;
3233
import org.springframework.util.MimeTypeUtils;
@@ -42,8 +43,7 @@
4243
* @author Rossen Stoyanchev
4344
* @author Sebastien Deleuze
4445
* @since 3.0
45-
* @see <a href="http://tools.ietf.org/html/rfc7231#section-3.1.1.1">HTTP 1.1: Semantics
46-
* and Content, section 3.1.1.1</a>
46+
* @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>
4747
*/
4848
public class MediaType extends MimeType implements Serializable {
4949

@@ -397,6 +397,8 @@ public MediaType removeQualityValue() {
397397
* Parse the given String value into a {@code MediaType} object,
398398
* with this method name following the 'valueOf' naming convention
399399
* (as supported by {@link org.springframework.core.convert.ConversionService}.
400+
* @param value the string to parse
401+
* @throws InvalidMediaTypeException if the media type value cannot be parsed
400402
* @see #parseMediaType(String)
401403
*/
402404
public static MediaType valueOf(String value) {
@@ -407,7 +409,7 @@ public static MediaType valueOf(String value) {
407409
* Parse the given String into a single {@code MediaType}.
408410
* @param mediaType the string to parse
409411
* @return the media type
410-
* @throws InvalidMediaTypeException if the string cannot be parsed
412+
* @throws InvalidMediaTypeException if the media type value cannot be parsed
411413
*/
412414
public static MediaType parseMediaType(String mediaType) {
413415
MimeType type;
@@ -425,13 +427,12 @@ public static MediaType parseMediaType(String mediaType) {
425427
}
426428
}
427429

428-
429430
/**
430-
* Parse the given, comma-separated string into a list of {@code MediaType} objects.
431+
* Parse the given comma-separated string into a list of {@code MediaType} objects.
431432
* <p>This method can be used to parse an Accept or Content-Type header.
432433
* @param mediaTypes the string to parse
433434
* @return the list of media types
434-
* @throws IllegalArgumentException if the string cannot be parsed
435+
* @throws InvalidMediaTypeException if the media type value cannot be parsed
435436
*/
436437
public static List<MediaType> parseMediaTypes(String mediaTypes) {
437438
if (!StringUtils.hasLength(mediaTypes)) {
@@ -445,6 +446,31 @@ public static List<MediaType> parseMediaTypes(String mediaTypes) {
445446
return result;
446447
}
447448

449+
/**
450+
* Parse the given list of (potentially) comma-separated strings into a
451+
* list of {@code MediaType} objects.
452+
* <p>This method can be used to parse an Accept or Content-Type header.
453+
* @param mediaTypes the string to parse
454+
* @return the list of media types
455+
* @throws InvalidMediaTypeException if the media type value cannot be parsed
456+
* @since 4.3.2
457+
*/
458+
public static List<MediaType> parseMediaTypes(List<String> mediaTypes) {
459+
if (CollectionUtils.isEmpty(mediaTypes)) {
460+
return Collections.<MediaType>emptyList();
461+
}
462+
else if (mediaTypes.size() == 1) {
463+
return parseMediaTypes(mediaTypes.get(0));
464+
}
465+
else {
466+
List<MediaType> result = new ArrayList<MediaType>(8);
467+
for (String mediaType : mediaTypes) {
468+
result.addAll(parseMediaTypes(mediaType));
469+
}
470+
return result;
471+
}
472+
}
473+
448474
/**
449475
* Return a string representation of the given list of {@code MediaType} objects.
450476
* <p>This method can be used to for an {@code Accept} or {@code Content-Type} header.

spring-web/src/main/java/org/springframework/web/accept/HeaderContentNegotiationStrategy.java

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -16,20 +16,21 @@
1616

1717
package org.springframework.web.accept;
1818

19+
import java.util.Arrays;
1920
import java.util.Collections;
2021
import java.util.List;
2122

2223
import org.springframework.http.HttpHeaders;
2324
import org.springframework.http.InvalidMediaTypeException;
2425
import org.springframework.http.MediaType;
25-
import org.springframework.util.StringUtils;
2626
import org.springframework.web.HttpMediaTypeNotAcceptableException;
2727
import org.springframework.web.context.request.NativeWebRequest;
2828

2929
/**
3030
* A {@code ContentNegotiationStrategy} that checks the 'Accept' request header.
3131
*
3232
* @author Rossen Stoyanchev
33+
* @author Juergen Hoeller
3334
* @since 3.2
3435
*/
3536
public class HeaderContentNegotiationStrategy implements ContentNegotiationStrategy {
@@ -42,18 +43,20 @@ public class HeaderContentNegotiationStrategy implements ContentNegotiationStrat
4243
public List<MediaType> resolveMediaTypes(NativeWebRequest request)
4344
throws HttpMediaTypeNotAcceptableException {
4445

45-
String header = request.getHeader(HttpHeaders.ACCEPT);
46-
if (!StringUtils.hasText(header)) {
47-
return Collections.emptyList();
46+
String[] headerValueArray = request.getHeaderValues(HttpHeaders.ACCEPT);
47+
if (headerValueArray == null) {
48+
return Collections.<MediaType>emptyList();
4849
}
50+
51+
List<String> headerValues = Arrays.asList(headerValueArray);
4952
try {
50-
List<MediaType> mediaTypes = MediaType.parseMediaTypes(header);
53+
List<MediaType> mediaTypes = MediaType.parseMediaTypes(headerValues);
5154
MediaType.sortBySpecificityAndQuality(mediaTypes);
5255
return mediaTypes;
5356
}
5457
catch (InvalidMediaTypeException ex) {
5558
throw new HttpMediaTypeNotAcceptableException(
56-
"Could not parse 'Accept' header [" + header + "]: " + ex.getMessage());
59+
"Could not parse 'Accept' header " + headerValues + ": " + ex.getMessage());
5760
}
5861
}
5962

spring-web/src/test/java/org/springframework/http/HttpHeadersTests.java

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@
3232
import org.hamcrest.Matchers;
3333
import org.junit.Test;
3434

35-
import static org.hamcrest.Matchers.is;
35+
import static org.hamcrest.Matchers.*;
3636
import static org.junit.Assert.*;
3737

3838
/**
@@ -67,13 +67,22 @@ public void accept() {
6767
}
6868

6969
@Test // SPR-9655
70-
public void acceptIPlanet() {
70+
public void acceptWithMultipleHeaderValues() {
7171
headers.add("Accept", "text/html");
7272
headers.add("Accept", "text/plain");
7373
List<MediaType> expected = Arrays.asList(new MediaType("text", "html"), new MediaType("text", "plain"));
7474
assertEquals("Invalid Accept header", expected, headers.getAccept());
7575
}
7676

77+
@Test // SPR-14506
78+
public void acceptWithMultipleCommaSeparatedHeaderValues() {
79+
headers.add("Accept", "text/html,text/pdf");
80+
headers.add("Accept", "text/plain,text/csv");
81+
List<MediaType> expected = Arrays.asList(new MediaType("text", "html"), new MediaType("text", "pdf"),
82+
new MediaType("text", "plain"), new MediaType("text", "csv"));
83+
assertEquals("Invalid Accept header", expected, headers.getAccept());
84+
}
85+
7786
@Test
7887
public void acceptCharsets() {
7988
Charset charset1 = Charset.forName("UTF-8");

spring-web/src/test/java/org/springframework/http/MediaTypeTests.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2013 the original author or authors.
2+
* Copyright 2002-2016 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -138,7 +138,7 @@ public void parseMediaTypes() throws Exception {
138138
assertNotNull("No media types returned", mediaTypes);
139139
assertEquals("Invalid amount of media types", 4, mediaTypes.size());
140140

141-
mediaTypes = MediaType.parseMediaTypes(null);
141+
mediaTypes = MediaType.parseMediaTypes("");
142142
assertNotNull("No media types returned", mediaTypes);
143143
assertEquals("Invalid amount of media types", 0, mediaTypes.size());
144144
}

spring-web/src/test/java/org/springframework/web/accept/HeaderContentNegotiationStrategyTests.java

Lines changed: 20 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2012 the original author or authors.
2+
* Copyright 2002-2016 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -13,11 +13,11 @@
1313
* See the License for the specific language governing permissions and
1414
* limitations under the License.
1515
*/
16+
1617
package org.springframework.web.accept;
1718

1819
import java.util.List;
1920

20-
import org.junit.Before;
2121
import org.junit.Test;
2222

2323
import org.springframework.http.MediaType;
@@ -32,21 +32,16 @@
3232
* Test fixture for HeaderContentNegotiationStrategy tests.
3333
*
3434
* @author Rossen Stoyanchev
35+
* @author Juergen Hoeller
3536
*/
3637
public class HeaderContentNegotiationStrategyTests {
3738

38-
private HeaderContentNegotiationStrategy strategy;
39+
private final HeaderContentNegotiationStrategy strategy = new HeaderContentNegotiationStrategy();
3940

40-
private NativeWebRequest webRequest;
41+
private final MockHttpServletRequest servletRequest = new MockHttpServletRequest();
4142

42-
private MockHttpServletRequest servletRequest;
43+
private final NativeWebRequest webRequest = new ServletWebRequest(this.servletRequest);
4344

44-
@Before
45-
public void setup() {
46-
this.strategy = new HeaderContentNegotiationStrategy();
47-
this.servletRequest = new MockHttpServletRequest();
48-
this.webRequest = new ServletWebRequest(servletRequest );
49-
}
5045

5146
@Test
5247
public void resolveMediaTypes() throws Exception {
@@ -60,7 +55,20 @@ public void resolveMediaTypes() throws Exception {
6055
assertEquals("text/plain;q=0.5", mediaTypes.get(3).toString());
6156
}
6257

63-
@Test(expected=HttpMediaTypeNotAcceptableException.class)
58+
@Test // SPR-14506
59+
public void resolveMediaTypesFromMultipleHeaderValues() throws Exception {
60+
this.servletRequest.addHeader("Accept", "text/plain; q=0.5, text/html");
61+
this.servletRequest.addHeader("Accept", "text/x-dvi; q=0.8, text/x-c");
62+
List<MediaType> mediaTypes = this.strategy.resolveMediaTypes(this.webRequest);
63+
64+
assertEquals(4, mediaTypes.size());
65+
assertEquals("text/html", mediaTypes.get(0).toString());
66+
assertEquals("text/x-c", mediaTypes.get(1).toString());
67+
assertEquals("text/x-dvi;q=0.8", mediaTypes.get(2).toString());
68+
assertEquals("text/plain;q=0.5", mediaTypes.get(3).toString());
69+
}
70+
71+
@Test(expected = HttpMediaTypeNotAcceptableException.class)
6472
public void resolveMediaTypesParseError() throws Exception {
6573
this.servletRequest.addHeader("Accept", "textplain; q=0.5");
6674
this.strategy.resolveMediaTypes(this.webRequest);

0 commit comments

Comments
 (0)