Added more headers.

This commit is contained in:
Arjen Poutsma 2009-07-13 13:05:46 +00:00
parent ff1dac8381
commit c9f4de3ebf
2 changed files with 280 additions and 23 deletions

View File

@ -18,9 +18,12 @@ package org.springframework.http;
import java.net.URI; import java.net.URI;
import java.nio.charset.Charset; import java.nio.charset.Charset;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collection; import java.util.Collection;
import java.util.Collections; import java.util.Collections;
import java.util.Date;
import java.util.EnumSet; import java.util.EnumSet;
import java.util.Iterator; import java.util.Iterator;
import java.util.LinkedList; import java.util.LinkedList;
@ -28,6 +31,7 @@ import java.util.List;
import java.util.Locale; import java.util.Locale;
import java.util.Map; import java.util.Map;
import java.util.Set; import java.util.Set;
import java.util.TimeZone;
import org.springframework.util.Assert; import org.springframework.util.Assert;
import org.springframework.util.LinkedCaseInsensitiveMap; import org.springframework.util.LinkedCaseInsensitiveMap;
@ -51,24 +55,43 @@ import org.springframework.util.StringUtils;
*/ */
public class HttpHeaders implements MultiValueMap<String, String> { public class HttpHeaders implements MultiValueMap<String, String> {
private static String ACCEPT = "Accept"; private static final String ACCEPT = "Accept";
private static String ACCEPT_CHARSET = "Accept-Charset"; private static final String ACCEPT_CHARSET = "Accept-Charset";
private static String ALLOW = "Allow"; private static final String ALLOW = "Allow";
private static String CONTENT_LENGTH = "Content-Length"; private static final String CONTENT_LENGTH = "Content-Length";
private static String CONTENT_TYPE = "Content-Type"; private static final String CONTENT_TYPE = "Content-Type";
private static String LOCATION = "Location"; private static final String DATE = "Date";
private static final String ETAG = "ETag";
private static final String EXPIRES = "Expires";
private static final String IF_NONE_MATCH = "If-None-Match";
private static final String LAST_MODIFIED = "Last-Modified";
private static final String LOCATION = "Location";
private static final String[] DATE_FORMATS = new String[] {
"EEE, dd MMM yyyy HH:mm:ss zzz",
"EEE, dd-MMM-yy HH:mm:ss zzz",
"EEE MMM dd HH:mm:ss yyyy"
};
private static TimeZone GMT = TimeZone.getTimeZone("GMT");
private final Map<String, List<String>> headers = new LinkedCaseInsensitiveMap<List<String>>(8); private final Map<String, List<String>> headers = new LinkedCaseInsensitiveMap<List<String>>(8);
/** /**
* Set the list of acceptable {@linkplain MediaType media types}, as specified by the <code>Accept</code> header. * Set the list of acceptable {@linkplain MediaType media types}, as specified by the {@code Accept} header.
* @param acceptableMediaTypes the acceptable media types * @param acceptableMediaTypes the acceptable media types
*/ */
public void setAccept(List<MediaType> acceptableMediaTypes) { public void setAccept(List<MediaType> acceptableMediaTypes) {
@ -76,7 +99,7 @@ public class HttpHeaders implements MultiValueMap<String, String> {
} }
/** /**
* Return the list of acceptable {@linkplain MediaType media types}, as specified by the <code>Accept</code> header. * Return the list of acceptable {@linkplain MediaType media types}, as specified by the {@code Accept} header.
* <p>Returns an empty list when the acceptable media types are unspecified. * <p>Returns an empty list when the acceptable media types are unspecified.
* @return the acceptable media types * @return the acceptable media types
*/ */
@ -86,7 +109,7 @@ public class HttpHeaders implements MultiValueMap<String, String> {
} }
/** /**
* Set the list of acceptable {@linkplain Charset charsets}, as specified by the <code>Accept-Charset</code> header. * Set the list of acceptable {@linkplain Charset charsets}, as specified by the {@code Accept-Charset} header.
* @param acceptableCharsets the acceptable charsets * @param acceptableCharsets the acceptable charsets
*/ */
public void setAcceptCharset(List<Charset> acceptableCharsets) { public void setAcceptCharset(List<Charset> acceptableCharsets) {
@ -102,7 +125,7 @@ public class HttpHeaders implements MultiValueMap<String, String> {
} }
/** /**
* Return the list of acceptable {@linkplain Charset charsets}, as specified by the <code>Accept-Charset</code> * Return the list of acceptable {@linkplain Charset charsets}, as specified by the {@code Accept-Charset}
* header. * header.
* @return the acceptable charsets * @return the acceptable charsets
*/ */
@ -125,7 +148,7 @@ public class HttpHeaders implements MultiValueMap<String, String> {
} }
/** /**
* Set the set of allowed {@link HttpMethod HTTP methods}, as specified by the <code>Allow</code> header. * Set the set of allowed {@link HttpMethod HTTP methods}, as specified by the {@code Allow} header.
* @param allowedMethods the allowed methods * @param allowedMethods the allowed methods
*/ */
public void setAllow(Set<HttpMethod> allowedMethods) { public void setAllow(Set<HttpMethod> allowedMethods) {
@ -133,7 +156,7 @@ public class HttpHeaders implements MultiValueMap<String, String> {
} }
/** /**
* Return the set of allowed {@link HttpMethod HTTP methods}, as specified by the <code>Allow</code> header. * Return the set of allowed {@link HttpMethod HTTP methods}, as specified by the {@code Allow} header.
* <p>Returns an empty set when the allowed methods are unspecified. * <p>Returns an empty set when the allowed methods are unspecified.
* @return the allowed methods * @return the allowed methods
*/ */
@ -153,7 +176,7 @@ public class HttpHeaders implements MultiValueMap<String, String> {
} }
/** /**
* Set the length of the body in bytes, as specified by the <code>Content-Length</code> header. * Set the length of the body in bytes, as specified by the {@code Content-Length} header.
* @param contentLength the content length * @param contentLength the content length
*/ */
public void setContentLength(long contentLength) { public void setContentLength(long contentLength) {
@ -161,7 +184,7 @@ public class HttpHeaders implements MultiValueMap<String, String> {
} }
/** /**
* Return the length of the body in bytes, as specified by the <code>Content-Length</code> header. * Return the length of the body in bytes, as specified by the {@code Content-Length} header.
* <p>Returns -1 when the content-length is unknown. * <p>Returns -1 when the content-length is unknown.
* @return the content length * @return the content length
*/ */
@ -171,7 +194,7 @@ public class HttpHeaders implements MultiValueMap<String, String> {
} }
/** /**
* Set the {@linkplain MediaType media type} of the body, as specified by the <code>Content-Type</code> header. * Set the {@linkplain MediaType media type} of the body, as specified by the {@code Content-Type} header.
* @param mediaType the media type * @param mediaType the media type
*/ */
public void setContentType(MediaType mediaType) { public void setContentType(MediaType mediaType) {
@ -181,8 +204,8 @@ public class HttpHeaders implements MultiValueMap<String, String> {
} }
/** /**
* Return the {@linkplain MediaType media type} of the body, as specified by the <code>Content-Type</code> header. * Return the {@linkplain MediaType media type} of the body, as specified by the {@code Content-Type} header.
* <p>Returns <code>null</code> when the content-type is unknown. * <p>Returns {@code null} when the content-type is unknown.
* @return the content type * @return the content type
*/ */
public MediaType getContentType() { public MediaType getContentType() {
@ -191,7 +214,119 @@ public class HttpHeaders implements MultiValueMap<String, String> {
} }
/** /**
* Set the (new) location of a resource, as specified by the <code>Location</code> header. * Sets the date and time at which the message was created, as specified by the {@code Date} header.
* <p>The date should be specified as the number of milliseconds since January 1, 1970 GMT.
* @param date the date
*/
public void setDate(long date) {
setDate(DATE, date);
}
/**
* Returns the date and time at which the message was created, as specified by the {@code Date} header.
* <p>The date is returned as the number of milliseconds since January 1, 1970 GMT. Returns -1 when the date is unknown.
* @return the creation date/time
* @throws IllegalArgumentException if the value can't be converted to a date
*/
public long getDate() {
return getFirstDate(DATE);
}
/**
* Sets the (new) entity tag of the body, as specified by the {@code ETag} header.
* @param eTag the new entity tag
*/
public void setETag(String eTag) {
set(ETAG, quote(eTag));
}
/**
* Returns the entity tag of the body, as specified by the {@code ETag} header.
* @return the entity tag
*/
public String getETag() {
return unquote(getFirst(ETAG));
}
/**
* Sets the date and time at which the message is no longer valid, as specified by the {@code Expires} header.
* <p>The date should be specified as the number of milliseconds since January 1, 1970 GMT.
* @param expires the new expires header value
*/
public void setExpires(long expires) {
setDate(EXPIRES, expires);
}
/**
* Returns the date and time at which the message is no longer valid, as specified by the {@code Expires} header.
* <p>The date is returned as the number of milliseconds since January 1, 1970 GMT. Returns -1 when the date is unknown.
* @return the expires value
*/
public long getExpires() {
return getFirstDate(EXPIRES);
}
/**
* Sets the (new) value of the {@code If-None-Match} header.
* @param ifNoneMatch the new value of the header
*/
public void setIfNoneMatch(String ifNoneMatch) {
set(IF_NONE_MATCH, quote(ifNoneMatch));
}
/**
* Sets the (new) values of the {@code If-None-Match} header.
* @param ifNoneMatchList the new value of the header
*/
public void setIfNoneMatch(List<String> ifNoneMatchList) {
StringBuilder builder = new StringBuilder();
for (Iterator<String> iterator = ifNoneMatchList.iterator(); iterator.hasNext();) {
String ifNoneMatch = iterator.next();
builder.append(quote(ifNoneMatch));
if (iterator.hasNext()) {
builder.append(", ");
}
}
set(IF_NONE_MATCH, builder.toString());
}
/**
* Returns the value of the {@code If-None-Match} header.
* @return the header value
*/
public List<String> getIfNoneMatch() {
List<String> result = new ArrayList<String>();
String value = getFirst(IF_NONE_MATCH);
if (value != null) {
String[] tokens = value.split(",\\s*");
for (String token : tokens) {
result.add(unquote(token));
}
}
return result;
}
/**
* Sets the time the resource was last changed, as specified by the {@code Last-Modified} header.
* <p>The date should be specified as the number of milliseconds since January 1, 1970 GMT.
* @param lastModified the last modified date
*/
public void setLastModified(long lastModified) {
setDate(LAST_MODIFIED, lastModified);
}
/**
* Returns the time the resource was last changed, as specified by the {@code Last-Modified} header.
* <p>The date is returned as the number of milliseconds since January 1, 1970 GMT. Returns -1 when the date is unknown.
* @return the last modified date
*/
public long getLastModified() {
return getFirstDate(LAST_MODIFIED);
}
/**
* Set the (new) location of a resource, as specified by the {@code Location} header.
* @param location the location * @param location the location
*/ */
public void setLocation(URI location) { public void setLocation(URI location) {
@ -199,8 +334,8 @@ public class HttpHeaders implements MultiValueMap<String, String> {
} }
/** /**
* Return the (new) location of a resource, as specified by the <code>Location</code> header. * Return the (new) location of a resource, as specified by the {@code Location} header.
* <p>Returns <code>null</code> when the location is unknown. * <p>Returns {@code null} when the location is unknown.
* @return the location * @return the location
*/ */
public URI getLocation() { public URI getLocation() {
@ -208,13 +343,64 @@ public class HttpHeaders implements MultiValueMap<String, String> {
return (value != null ? URI.create(value) : null); return (value != null ? URI.create(value) : null);
} }
// Utility methods
private String quote(String s) {
Assert.notNull(s);
if (!s.startsWith("\"")) {
s = "\"" + s;
}
if (!s.endsWith("\"")) {
s = s + "\"";
}
return s;
}
private String unquote(String s) {
if (s == null) {
return null;
}
if (s.startsWith("\"")) {
s = s.substring(1);
}
if (s.endsWith("\"")) {
s = s.substring(0, s.length() - 1);
}
return s;
}
private long getFirstDate(String headerName) {
String headerValue = getFirst(headerName);
if (headerValue == null) {
return -1;
}
for (String dateFormat : DATE_FORMATS) {
SimpleDateFormat simpleDateFormat = new SimpleDateFormat(dateFormat, Locale.US);
simpleDateFormat.setTimeZone(GMT);
try {
return simpleDateFormat.parse(headerValue).getTime();
}
catch (ParseException e) {
// ignore
}
}
throw new IllegalArgumentException("Cannot parse date value \"" + headerValue +
"\" for \"" + headerName + "\" header");
}
private void setDate(String headerName, long date) {
SimpleDateFormat dateFormat = new SimpleDateFormat(DATE_FORMATS[0]);
dateFormat.setTimeZone(GMT);
set(headerName, dateFormat.format(new Date(date)));
}
// Single string methods // Single string methods
/** /**
* Return the first header value for the given header name, if any. * Return the first header value for the given header name, if any.
* @param headerName the header name * @param headerName the header name
* @return the first header value; or <code>null</code> * @return the first header value; or {@code null}
*/ */
public String getFirst(String headerName) { public String getFirst(String headerName) {
List<String> headerValues = headers.get(headerName); List<String> headerValues = headers.get(headerName);

View File

@ -20,15 +20,16 @@ import java.net.URI;
import java.net.URISyntaxException; import java.net.URISyntaxException;
import java.nio.charset.Charset; import java.nio.charset.Charset;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Calendar;
import java.util.EnumSet; import java.util.EnumSet;
import java.util.GregorianCalendar;
import java.util.List; import java.util.List;
import java.util.TimeZone;
import static org.junit.Assert.*; import static org.junit.Assert.*;
import org.junit.Before; import org.junit.Before;
import org.junit.Test; import org.junit.Test;
import org.springframework.http.MediaType;
/** /**
* @author Arjen Poutsma * @author Arjen Poutsma
*/ */
@ -96,4 +97,74 @@ public class HttpHeadersTests {
assertEquals("Invalid Location header", location, headers.getLocation()); assertEquals("Invalid Location header", location, headers.getLocation());
assertEquals("Invalid Location header", "http://www.example.com/hotels", headers.getFirst("Location")); assertEquals("Invalid Location header", "http://www.example.com/hotels", headers.getFirst("Location"));
} }
@Test
public void eTag() {
String eTag = "v2.6";
headers.setETag(eTag);
assertEquals("Invalid ETag header", eTag, headers.getETag());
assertEquals("Invalid ETag header", "\"v2.6\"", headers.getFirst("ETag"));
}
@Test
public void ifNoneMatch() {
String ifNoneMatch = "v2.6";
headers.setIfNoneMatch(ifNoneMatch);
assertEquals("Invalid If-None-Match header", ifNoneMatch, headers.getIfNoneMatch().get(0));
assertEquals("Invalid If-None-Match header", "\"v2.6\"", headers.getFirst("If-None-Match"));
}
@Test
public void ifNoneMatchList() {
String ifNoneMatch1 = "v2.6";
String ifNoneMatch2 = "v2.7";
List<String> ifNoneMatchList = new ArrayList<String>(2);
ifNoneMatchList.add(ifNoneMatch1);
ifNoneMatchList.add(ifNoneMatch2);
headers.setIfNoneMatch(ifNoneMatchList);
assertEquals("Invalid If-None-Match header", ifNoneMatchList, headers.getIfNoneMatch());
assertEquals("Invalid If-None-Match header", "\"v2.6\", \"v2.7\"", headers.getFirst("If-None-Match"));
}
@Test
public void date() {
Calendar calendar = new GregorianCalendar(2008, 11, 18, 11, 20);
calendar.setTimeZone(TimeZone.getTimeZone("CET"));
long date = calendar.getTimeInMillis();
headers.setDate(date);
assertEquals("Invalid Date header", date, headers.getDate());
assertEquals("Invalid Date header", "Thu, 18 Dec 2008 10:20:00 GMT", headers.getFirst("date"));
// RFC 850
headers.set("Date", "Thursday, 18-Dec-08 11:20:00 CET");
assertEquals("Invalid Date header", date, headers.getDate());
}
@Test(expected = IllegalArgumentException.class)
public void dateInvalid() {
headers.set("Date", "Foo Bar Baz");
headers.getDate();
}
@Test
public void lastModified() {
Calendar calendar = new GregorianCalendar(2008, 11, 18, 11, 20);
calendar.setTimeZone(TimeZone.getTimeZone("CET"));
long date = calendar.getTimeInMillis();
headers.setLastModified(date);
assertEquals("Invalid Last-Modified header", date, headers.getLastModified());
assertEquals("Invalid Last-Modified header", "Thu, 18 Dec 2008 10:20:00 GMT", headers.getFirst("last-modified"));
}
@Test
public void expires() {
Calendar calendar = new GregorianCalendar(2008, 11, 18, 11, 20);
calendar.setTimeZone(TimeZone.getTimeZone("CET"));
long date = calendar.getTimeInMillis();
headers.setExpires(date);
assertEquals("Invalid Expires header", date, headers.getExpires());
assertEquals("Invalid Expires header", "Thu, 18 Dec 2008 10:20:00 GMT", headers.getFirst("expires"));
}
} }