summaryrefslogtreecommitdiff
path: root/spring-web/src/main/java/org/springframework/http/HttpHeaders.java
diff options
context:
space:
mode:
Diffstat (limited to 'spring-web/src/main/java/org/springframework/http/HttpHeaders.java')
-rw-r--r--spring-web/src/main/java/org/springframework/http/HttpHeaders.java238
1 files changed, 182 insertions, 56 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 1170bb06..0ba9a02a 100644
--- a/spring-web/src/main/java/org/springframework/http/HttpHeaders.java
+++ b/spring-web/src/main/java/org/springframework/http/HttpHeaders.java
@@ -34,6 +34,8 @@ import java.util.Locale;
import java.util.Map;
import java.util.Set;
import java.util.TimeZone;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
import org.springframework.util.Assert;
import org.springframework.util.LinkedCaseInsensitiveMap;
@@ -55,6 +57,8 @@ 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<String, String>, Serializable {
@@ -372,6 +376,12 @@ public class HttpHeaders implements MultiValueMap<String, String>, Serializable
"EEE MMM dd HH:mm:ss yyyy"
};
+ /**
+ * Pattern matching ETag multiple field values in headers such as "If-Match", "If-None-Match"
+ * @see <a href="https://tools.ietf.org/html/rfc7232#section-2.3">Section 2.3 of RFC 7232</a>
+ */
+ private static final Pattern ETAG_HEADER_VALUE_PATTERN = Pattern.compile("\\*|\\s*((W\\/)?(\"[^\"]*\"))\\s*,?");
+
private static TimeZone GMT = TimeZone.getTimeZone("GMT");
@@ -419,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));
}
/**
@@ -442,10 +440,10 @@ 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 new Boolean(getFirst(ACCESS_CONTROL_ALLOW_CREDENTIALS));
+ return Boolean.parseBoolean(getFirst(ACCESS_CONTROL_ALLOW_CREDENTIALS));
}
/**
@@ -456,10 +454,10 @@ 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 getFirstValueAsList(ACCESS_CONTROL_ALLOW_HEADERS);
+ return getValuesAsList(ACCESS_CONTROL_ALLOW_HEADERS);
}
/**
@@ -476,7 +474,7 @@ public class HttpHeaders implements MultiValueMap<String, String>, Serializable
List<HttpMethod> result = new ArrayList<HttpMethod>();
String value = getFirst(ACCESS_CONTROL_ALLOW_METHODS);
if (value != null) {
- String[] tokens = value.split(",\\s*");
+ String[] tokens = StringUtils.tokenizeToStringArray(value, ",", true, true);
for (String token : tokens) {
HttpMethod resolved = HttpMethod.resolve(token);
if (resolved != null) {
@@ -498,7 +496,7 @@ public class HttpHeaders implements MultiValueMap<String, String>, Serializable
* Return the value of the {@code Access-Control-Allow-Origin} response header.
*/
public String getAccessControlAllowOrigin() {
- return getFirst(ACCESS_CONTROL_ALLOW_ORIGIN);
+ return getFieldValues(ACCESS_CONTROL_ALLOW_ORIGIN);
}
/**
@@ -509,10 +507,10 @@ 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 getFirstValueAsList(ACCESS_CONTROL_EXPOSE_HEADERS);
+ return getValuesAsList(ACCESS_CONTROL_EXPOSE_HEADERS);
}
/**
@@ -523,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() {
@@ -539,10 +537,10 @@ 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 getFirstValueAsList(ACCESS_CONTROL_REQUEST_HEADERS);
+ return getValuesAsList(ACCESS_CONTROL_REQUEST_HEADERS);
}
/**
@@ -643,7 +641,7 @@ public class HttpHeaders implements MultiValueMap<String, String>, Serializable
* Return the value of the {@code Cache-Control} header.
*/
public String getCacheControl() {
- return getFirst(CACHE_CONTROL);
+ return getFieldValues(CACHE_CONTROL);
}
/**
@@ -664,7 +662,7 @@ public class HttpHeaders implements MultiValueMap<String, String>, Serializable
* Return the value of the {@code Connection} header.
*/
public List<String> getConnection() {
- return getFirstValueAsList(CONNECTION);
+ return getValuesAsList(CONNECTION);
}
/**
@@ -783,6 +781,30 @@ public class HttpHeaders implements MultiValueMap<String, String>, Serializable
}
/**
+ * Set the (new) value of the {@code If-Match} header.
+ * @since 4.3
+ */
+ public void setIfMatch(String ifMatch) {
+ set(IF_MATCH, ifMatch);
+ }
+
+ /**
+ * Set the (new) value of the {@code If-Match} header.
+ * @since 4.3
+ */
+ public void setIfMatch(List<String> ifMatchList) {
+ set(IF_MATCH, toCommaDelimitedString(ifMatchList));
+ }
+
+ /**
+ * Return the value of the {@code If-Match} header.
+ * @since 4.3
+ */
+ public List<String> getIfMatch() {
+ return getETagValuesAsList(IF_MATCH);
+ }
+
+ /**
* Set the (new) value of the {@code If-Modified-Since} header.
* <p>The date should be specified as the number of milliseconds since
* January 1, 1970 GMT.
@@ -814,35 +836,31 @@ public class HttpHeaders implements MultiValueMap<String, String>, Serializable
set(IF_NONE_MATCH, toCommaDelimitedString(ifNoneMatchList));
}
- protected String toCommaDelimitedString(List<String> list) {
- StringBuilder builder = new StringBuilder();
- for (Iterator<String> 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-None-Match} header.
*/
public List<String> getIfNoneMatch() {
- return getFirstValueAsList(IF_NONE_MATCH);
+ return getETagValuesAsList(IF_NONE_MATCH);
}
- protected List<String> getFirstValueAsList(String header) {
- List<String> result = new ArrayList<String>();
- String value = getFirst(header);
- if (value != null) {
- String[] tokens = value.split(",\\s*");
- for (String token : tokens) {
- result.add(token);
- }
- }
- return result;
+ /**
+ * Set the (new) value of the {@code If-Unmodified-Since} header.
+ * <p>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);
+ }
+
+ /**
+ * Return the value of the {@code If-Unmodified-Since} header.
+ * <p>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);
}
/**
@@ -943,11 +961,43 @@ public class HttpHeaders implements MultiValueMap<String, String>, Serializable
}
/**
+ * Set the request header names (e.g. "Accept-Language") for which the
+ * response is subject to content negotiation and variances based on the
+ * value of those request headers.
+ * @param requestHeaders the request header names
+ * @since 4.3
+ */
+ public void setVary(List<String> requestHeaders) {
+ set(VARY, toCommaDelimitedString(requestHeaders));
+ }
+
+ /**
+ * Return the request header names subject to content negotiation.
+ * @since 4.3
+ */
+ public List<String> 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);
@@ -992,16 +1042,92 @@ public class HttpHeaders implements MultiValueMap<String, String>, 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 List<String> getValuesAsList(String headerName) {
+ List<String> values = get(headerName);
+ if (values != null) {
+ List<String> result = new ArrayList<String>();
+ 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<String> getETagValuesAsList(String headerName) {
+ List<String> values = get(headerName);
+ if (values != null) {
+ List<String> result = new ArrayList<String>();
+ 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
*/
- 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)));
+ protected String getFieldValues(String headerName) {
+ List<String> 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<String> headerValues) {
+ StringBuilder builder = new StringBuilder();
+ for (Iterator<String> 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