From 15c96b8efd9cb0a1126c48f0fe8262101067162e Mon Sep 17 00:00:00 2001 From: Juergen Hoeller Date: Mon, 27 Jun 2016 15:33:53 +0200 Subject: [PATCH] ServletResponseHttpHeaders consistently overrides HttpHeaders again Issue: SPR-14406 --- .../org/springframework/http/HttpHeaders.java | 187 ++++++++++-------- .../ServletServerHttpResponseTests.java | 13 +- 2 files changed, 111 insertions(+), 89 deletions(-) diff --git a/spring-web/src/main/java/org/springframework/http/HttpHeaders.java b/spring-web/src/main/java/org/springframework/http/HttpHeaders.java index 61aecc4991..697d458214 100644 --- a/spring-web/src/main/java/org/springframework/http/HttpHeaders.java +++ b/spring-web/src/main/java/org/springframework/http/HttpHeaders.java @@ -58,6 +58,7 @@ import org.springframework.util.StringUtils; * @author Arjen Poutsma * @author Sebastien Deleuze * @author Brian Clozel + * @author Juergen Hoeller * @since 3.0 */ public class HttpHeaders implements MultiValueMap, Serializable { @@ -454,7 +455,7 @@ public class HttpHeaders implements MultiValueMap, Serializable * Returns the value of the {@code Access-Control-Allow-Credentials} response header. */ public boolean getAccessControlAllowCredentials() { - return new Boolean(getFirst(ACCESS_CONTROL_ALLOW_CREDENTIALS)); + return Boolean.parseBoolean(getFirst(ACCESS_CONTROL_ALLOW_CREDENTIALS)); } /** @@ -510,22 +511,6 @@ public class HttpHeaders implements MultiValueMap, Serializable return getFieldValues(ACCESS_CONTROL_ALLOW_ORIGIN); } - protected String getFieldValues(String headerName) { - List headerValues = this.headers.get(headerName); - if (headerValues != null) { - StringBuilder builder = new StringBuilder(); - for (Iterator iterator = headerValues.iterator(); iterator.hasNext(); ) { - String ifNoneMatch = iterator.next(); - builder.append(ifNoneMatch); - if (iterator.hasNext()) { - builder.append(", "); - } - } - return builder.toString(); - } - return null; - } - /** * Set the (new) value of the {@code Access-Control-Expose-Headers} response header. */ @@ -809,6 +794,7 @@ public class HttpHeaders implements MultiValueMap, Serializable /** * Set the (new) value of the {@code If-Match} header. + * @since 4.3 */ public void setIfMatch(String ifMatch) { set(IF_MATCH, ifMatch); @@ -816,55 +802,20 @@ public class HttpHeaders implements MultiValueMap, Serializable /** * Set the (new) value of the {@code If-Match} header. + * @since 4.3 */ public void setIfMatch(List ifMatchList) { set(IF_MATCH, toCommaDelimitedString(ifMatchList)); } - protected String toCommaDelimitedString(List list) { - StringBuilder builder = new StringBuilder(); - for (Iterator iterator = list.iterator(); iterator.hasNext(); ) { - String ifNoneMatch = iterator.next(); - builder.append(ifNoneMatch); - if (iterator.hasNext()) { - builder.append(", "); - } - } - return builder.toString(); - } - /** * Return the value of the {@code If-Match} header. + * @since 4.3 */ public List getIfMatch() { return getETagValuesAsList(IF_MATCH); } - protected List getETagValuesAsList(String headerName) { - List values = get(headerName); - if (values != null) { - List result = new ArrayList(); - for (String value : values) { - if (value != null) { - Matcher matcher = ETAG_HEADER_VALUE_PATTERN.matcher(value); - while (matcher.find()) { - if ("*".equals(matcher.group())) { - result.add(matcher.group()); - } - else { - result.add(matcher.group(1)); - } - } - if(result.size() == 0) { - throw new IllegalArgumentException("Could not parse '" + headerName + "' value=" + value); - } - } - } - return result; - } - return Collections.emptyList(); - } - /** * Set the (new) value of the {@code If-Modified-Since} header. *

The date should be specified as the number of milliseconds since @@ -904,32 +855,11 @@ public class HttpHeaders implements MultiValueMap, Serializable return getETagValuesAsList(IF_NONE_MATCH); } - /** - * Return all values of a given header name, - * even if this header is set multiple times. - * @since 4.3 - */ - public List getValuesAsList(String headerName) { - List values = get(headerName); - if (values != null) { - List result = new ArrayList(); - for (String value : values) { - if (value != null) { - String[] tokens = StringUtils.tokenizeToStringArray(value, ","); - for (String token : tokens) { - result.add(token); - } - } - } - return result; - } - return Collections.emptyList(); - } - /** * Set the (new) value of the {@code If-Unmodified-Since} header. *

The date should be specified as the number of milliseconds since * January 1, 1970 GMT. + * @since 4.3 */ public void setIfUnmodifiedSince(long ifUnmodifiedSince) { setDate(IF_UNMODIFIED_SINCE, ifUnmodifiedSince); @@ -939,6 +869,7 @@ public class HttpHeaders implements MultiValueMap, Serializable * Return the value of the {@code If-Unmodified-Since} header. *

The date is returned as the number of milliseconds since * January 1, 1970 GMT. Returns -1 when the date is unknown. + * @since 4.3 */ public long getIfUnmodifiedSince() { return getFirstDate(IF_UNMODIFIED_SINCE, false); @@ -1054,17 +985,31 @@ public class HttpHeaders implements MultiValueMap, Serializable /** * Return the request header names subject to content negotiation. + * @since 4.3 */ public List getVary() { return getValuesAsList(VARY); } + /** + * Set the given date under the given header name after formatting it as a string + * using the pattern {@code "EEE, dd MMM yyyy HH:mm:ss zzz"}. The equivalent of + * {@link #set(String, String)} but for date headers. + * @since 3.2.4 + */ + public void setDate(String headerName, long date) { + SimpleDateFormat dateFormat = new SimpleDateFormat(DATE_FORMATS[0], Locale.US); + dateFormat.setTimeZone(GMT); + set(headerName, dateFormat.format(new Date(date))); + } + /** * Parse the first header value for the given header name as a date, * return -1 if there is no value, or raise {@link IllegalArgumentException} * if the value cannot be parsed as a date. * @param headerName the header name * @return the parsed date header, or -1 if none + * @since 3.2.4 */ public long getFirstDate(String headerName) { return getFirstDate(headerName, true); @@ -1109,16 +1054,92 @@ public class HttpHeaders implements MultiValueMap, Serializable } /** - * Set the given date under the given header name after formatting it as a string - * using the pattern {@code "EEE, dd MMM yyyy HH:mm:ss zzz"}. The equivalent of - * {@link #set(String, String)} but for date headers. + * Return all values of a given header name, + * even if this header is set multiple times. + * @param headerName the header name + * @return all associated values + * @since 4.3 */ - public void setDate(String headerName, long date) { - SimpleDateFormat dateFormat = new SimpleDateFormat(DATE_FORMATS[0], Locale.US); - dateFormat.setTimeZone(GMT); - set(headerName, dateFormat.format(new Date(date))); + public List getValuesAsList(String headerName) { + List values = get(headerName); + if (values != null) { + List result = new ArrayList(); + for (String value : values) { + if (value != null) { + String[] tokens = StringUtils.tokenizeToStringArray(value, ","); + for (String token : tokens) { + result.add(token); + } + } + } + return result; + } + return Collections.emptyList(); + } + + /** + * Retrieve a combined result from the field values of the ETag header. + * @param headerName the header name + * @return the combined result + * @since 4.3 + */ + protected List getETagValuesAsList(String headerName) { + List values = get(headerName); + if (values != null) { + List result = new ArrayList(); + for (String value : values) { + if (value != null) { + Matcher matcher = ETAG_HEADER_VALUE_PATTERN.matcher(value); + while (matcher.find()) { + if ("*".equals(matcher.group())) { + result.add(matcher.group()); + } + else { + result.add(matcher.group(1)); + } + } + if (result.isEmpty()) { + throw new IllegalArgumentException( + "Could not parse header '" + headerName + "' with value '" + value + "'"); + } + } + } + return result; + } + return Collections.emptyList(); } + /** + * Retrieve a combined result from the field values of multi-valued headers. + * @param headerName the header name + * @return the combined result + * @since 4.3 + */ + protected String getFieldValues(String headerName) { + List headerValues = get(headerName); + return (headerValues != null ? toCommaDelimitedString(headerValues) : null); + } + + /** + * Turn the given list of header values into a comma-delimited result. + * @param headerValues the list of header values + * @return a combined result with comma delimitation + */ + protected String toCommaDelimitedString(List headerValues) { + StringBuilder builder = new StringBuilder(); + for (Iterator it = headerValues.iterator(); it.hasNext(); ) { + String val = it.next(); + builder.append(val); + if (it.hasNext()) { + builder.append(", "); + } + } + return builder.toString(); + } + + + // MultiValueMap implementation + /** * Return the first header value for the given header name, if any. * @param headerName the header name diff --git a/spring-web/src/test/java/org/springframework/http/server/ServletServerHttpResponseTests.java b/spring-web/src/test/java/org/springframework/http/server/ServletServerHttpResponseTests.java index 4b8e60feef..b592a04583 100644 --- a/spring-web/src/test/java/org/springframework/http/server/ServletServerHttpResponseTests.java +++ b/spring-web/src/test/java/org/springframework/http/server/ServletServerHttpResponseTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2015 the original author or authors. + * Copyright 2002-2016 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -29,13 +29,12 @@ import org.springframework.http.MediaType; import org.springframework.mock.web.test.MockHttpServletResponse; import org.springframework.util.FileCopyUtils; -import static org.junit.Assert.assertArrayEquals; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertTrue; +import static org.junit.Assert.*; /** * @author Arjen Poutsma * @author Rossen Stoyanchev + * @author Juergen Hoeller */ public class ServletServerHttpResponseTests { @@ -79,7 +78,6 @@ public class ServletServerHttpResponseTests { @Test public void preExistingHeadersFromHttpServletResponse() { - String headerName = "Access-Control-Allow-Origin"; String headerValue = "localhost:8080"; @@ -89,6 +87,8 @@ public class ServletServerHttpResponseTests { assertEquals(headerValue, this.response.getHeaders().getFirst(headerName)); assertEquals(Collections.singletonList(headerValue), this.response.getHeaders().get(headerName)); assertTrue(this.response.getHeaders().containsKey(headerName)); + assertEquals(headerValue, this.response.getHeaders().getFirst(headerName)); + assertEquals(headerValue, this.response.getHeaders().getAccessControlAllowOrigin()); } @Test @@ -98,4 +98,5 @@ public class ServletServerHttpResponseTests { assertArrayEquals("Invalid content written", content, mockResponse.getContentAsByteArray()); } -} \ No newline at end of file + +}