diff options
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.java | 238 |
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 |