Skip to content

Commit e59a599

Browse files
committed
Consistent support for multiple Accept headers
Issue: SPR-14506
1 parent 6e54ed0 commit e59a599

File tree

6 files changed

+77
-42
lines changed

6 files changed

+77
-42
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: 31 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@
2828
import java.util.stream.Collectors;
2929

3030
import org.springframework.util.Assert;
31+
import org.springframework.util.CollectionUtils;
3132
import org.springframework.util.InvalidMimeTypeException;
3233
import org.springframework.util.MimeType;
3334
import org.springframework.util.MimeTypeUtils;
@@ -397,6 +398,8 @@ public MediaType removeQualityValue() {
397398
* Parse the given String value into a {@code MediaType} object,
398399
* with this method name following the 'valueOf' naming convention
399400
* (as supported by {@link org.springframework.core.convert.ConversionService}.
401+
* @param value the string to parse
402+
* @throws InvalidMediaTypeException if the media type value cannot be parsed
400403
* @see #parseMediaType(String)
401404
*/
402405
public static MediaType valueOf(String value) {
@@ -407,7 +410,7 @@ public static MediaType valueOf(String value) {
407410
* Parse the given String into a single {@code MediaType}.
408411
* @param mediaType the string to parse
409412
* @return the media type
410-
* @throws InvalidMediaTypeException if the string cannot be parsed
413+
* @throws InvalidMediaTypeException if the media type value cannot be parsed
411414
*/
412415
public static MediaType parseMediaType(String mediaType) {
413416
MimeType type;
@@ -425,13 +428,12 @@ public static MediaType parseMediaType(String mediaType) {
425428
}
426429
}
427430

428-
429431
/**
430-
* Parse the given, comma-separated string into a list of {@code MediaType} objects.
432+
* Parse the given comma-separated string into a list of {@code MediaType} objects.
431433
* <p>This method can be used to parse an Accept or Content-Type header.
432434
* @param mediaTypes the string to parse
433435
* @return the list of media types
434-
* @throws IllegalArgumentException if the string cannot be parsed
436+
* @throws InvalidMediaTypeException if the media type value cannot be parsed
435437
*/
436438
public static List<MediaType> parseMediaTypes(String mediaTypes) {
437439
if (!StringUtils.hasLength(mediaTypes)) {
@@ -445,6 +447,31 @@ public static List<MediaType> parseMediaTypes(String mediaTypes) {
445447
return result;
446448
}
447449

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

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: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -68,13 +68,22 @@ public void accept() {
6868
}
6969

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

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

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -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)