diff --git a/spring-test/src/main/java/org/springframework/mock/web/MockHttpServletRequest.java b/spring-test/src/main/java/org/springframework/mock/web/MockHttpServletRequest.java
index 4211452929b..ad0fdde117b 100644
--- a/spring-test/src/main/java/org/springframework/mock/web/MockHttpServletRequest.java
+++ b/spring-test/src/main/java/org/springframework/mock/web/MockHttpServletRequest.java
@@ -24,6 +24,8 @@ import java.io.InputStreamReader;
import java.io.Reader;
import java.io.UnsupportedEncodingException;
import java.security.Principal;
+import java.text.ParseException;
+import java.text.SimpleDateFormat;
import java.util.Collection;
import java.util.Collections;
import java.util.Date;
@@ -36,6 +38,8 @@ import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Set;
+import java.util.TimeZone;
+
import javax.servlet.AsyncContext;
import javax.servlet.DispatcherType;
import javax.servlet.RequestDispatcher;
@@ -120,6 +124,18 @@ public class MockHttpServletRequest implements HttpServletRequest {
private static final ServletInputStream EMPTY_SERVLET_INPUT_STREAM =
new DelegatingServletInputStream(new ByteArrayInputStream(new byte[0]));
+ /**
+ * Date formats as specified in the HTTP RFC
+ * @see Section 7.1.1.1 of RFC 7231
+ */
+ 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 final TimeZone GMT = TimeZone.getTimeZone("GMT");
+
private boolean active = true;
@@ -681,7 +697,7 @@ public class MockHttpServletRequest implements HttpServletRequest {
}
/**
- * Returns the first preferred {@linkplain Locale locale} configured
+ * Return the first preferred {@linkplain Locale locale} configured
* in this mock request.
*
If no locales have been explicitly configured, the default,
* preferred {@link Locale} for the server mocked by this
@@ -699,7 +715,7 @@ public class MockHttpServletRequest implements HttpServletRequest {
}
/**
- * Returns an {@linkplain Enumeration enumeration} of the preferred
+ * Return an {@linkplain Enumeration enumeration} of the preferred
* {@linkplain Locale locales} configured in this mock request.
*
If no locales have been explicitly configured, the default,
* preferred {@link Locale} for the server mocked by this
@@ -728,7 +744,7 @@ public class MockHttpServletRequest implements HttpServletRequest {
}
/**
- * Returns {@code true} if the {@link #setSecure secure} flag has been set
+ * Return {@code true} if the {@link #setSecure secure} flag has been set
* to {@code true} or if the {@link #getScheme scheme} is {@code https}.
* @see javax.servlet.ServletRequest#isSecure()
*/
@@ -860,20 +876,16 @@ public class MockHttpServletRequest implements HttpServletRequest {
/**
* Add a header entry for the given name.
- *
If there was no entry for that header name before, the value will be used
- * as-is. In case of an existing entry, a String array will be created,
- * adding the given value (more specifically, its toString representation)
- * as further element.
- *
Multiple values can only be stored as list of Strings, following the
- * Servlet spec (see {@code getHeaders} accessor). As alternative to
- * repeated {@code addHeader} calls for individual elements, you can
- * use a single call with an entire array or Collection of values as
- * parameter.
+ *
While this method can take any {@code Object} as a parameter,
+ * it is recommended to use the following types:
+ *
+ * - String or any Object to be converted using {@code toString}, see {@link #getHeader}
+ * - String, Number or Date for date headers, see {@link #getDateHeader}
+ * - String or Number for integer headers, see {@link #getIntHeader}
+ * - {@code String[]} and {@code Collection} for multiple values, see {@link #getHeaders}
+ *
* @see #getHeaderNames
- * @see #getHeader
* @see #getHeaders
- * @see #getDateHeader
- * @see #getIntHeader
*/
public void addHeader(String name, Object value) {
if (CONTENT_TYPE_HEADER.equalsIgnoreCase(name)) {
@@ -902,6 +914,18 @@ public class MockHttpServletRequest implements HttpServletRequest {
}
}
+ /**
+ * Return the long timestamp for the date header with the given {@code name}.
+ * If the internal value representation is a String, this method will try
+ * to parse it as a date using the supported date formats:
+ *
+ * - "EEE, dd MMM yyyy HH:mm:ss zzz"
+ * - "EEE, dd-MMM-yy HH:mm:ss zzz"
+ * - "EEE MMM dd HH:mm:ss yyyy"
+ *
+ * @param name the header name
+ * @see Section 7.1.1.1 of RFC 7231
+ */
@Override
public long getDateHeader(String name) {
HeaderValueHolder header = HeaderValueHolder.getByName(this.headers, name);
@@ -912,15 +936,32 @@ public class MockHttpServletRequest implements HttpServletRequest {
else if (value instanceof Number) {
return ((Number) value).longValue();
}
+ else if (value instanceof String) {
+ return parseDateHeader(name, (String) value);
+ }
else if (value != null) {
throw new IllegalArgumentException(
- "Value for header '" + name + "' is neither a Date nor a Number: " + value);
+ "Value for header '" + name + "' is not a Date, Number, or String: " + value);
}
else {
return -1L;
}
}
+ private long parseDateHeader(String name, String value) {
+ for (String dateFormat : DATE_FORMATS) {
+ SimpleDateFormat simpleDateFormat = new SimpleDateFormat(dateFormat, Locale.US);
+ simpleDateFormat.setTimeZone(GMT);
+ try {
+ return simpleDateFormat.parse(value).getTime();
+ }
+ catch (ParseException ex) {
+ // ignore
+ }
+ }
+ throw new IllegalArgumentException("Cannot parse date value '" + value + "' for '" + name + "' header");
+ }
+
@Override
public String getHeader(String name) {
HeaderValueHolder header = HeaderValueHolder.getByName(this.headers, name);
@@ -1171,7 +1212,7 @@ public class MockHttpServletRequest implements HttpServletRequest {
@Override
public Collection getParts() throws IOException, IllegalStateException, ServletException {
List result = new LinkedList();
- for(List list : this.parts.values()) {
+ for (List list : this.parts.values()) {
result.addAll(list);
}
return result;
diff --git a/spring-test/src/main/java/org/springframework/mock/web/MockHttpServletResponse.java b/spring-test/src/main/java/org/springframework/mock/web/MockHttpServletResponse.java
index 5f2db3d3764..bccc8dbd3a6 100644
--- a/spring-test/src/main/java/org/springframework/mock/web/MockHttpServletResponse.java
+++ b/spring-test/src/main/java/org/springframework/mock/web/MockHttpServletResponse.java
@@ -23,12 +23,16 @@ import java.io.OutputStreamWriter;
import java.io.PrintWriter;
import java.io.UnsupportedEncodingException;
import java.io.Writer;
+import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
+import java.util.Date;
import java.util.List;
import java.util.Locale;
import java.util.Map;
+import java.util.TimeZone;
+
import javax.servlet.ServletOutputStream;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletResponse;
@@ -47,6 +51,7 @@ import org.springframework.web.util.WebUtils;
*
* @author Juergen Hoeller
* @author Rod Johnson
+ * @author Brian Clozel
* @since 1.0.2
*/
public class MockHttpServletResponse implements HttpServletResponse {
@@ -59,6 +64,10 @@ public class MockHttpServletResponse implements HttpServletResponse {
private static final String LOCATION_HEADER = "Location";
+ private static final String DATE_FORMAT = "EEE, dd MMM yyyy HH:mm:ss zzz";
+
+ private static final TimeZone GMT = TimeZone.getTimeZone("GMT");
+
//---------------------------------------------------------------------
// ServletResponse properties
@@ -481,12 +490,18 @@ public class MockHttpServletResponse implements HttpServletResponse {
@Override
public void setDateHeader(String name, long value) {
- setHeaderValue(name, value);
+ setHeaderValue(name, formatDate(value));
}
@Override
public void addDateHeader(String name, long value) {
- addHeaderValue(name, value);
+ addHeaderValue(name, formatDate(value));
+ }
+
+ private String formatDate(long date) {
+ SimpleDateFormat dateFormat = new SimpleDateFormat(DATE_FORMAT, Locale.US);
+ dateFormat.setTimeZone(GMT);
+ return dateFormat.format(new Date(date));
}
@Override
diff --git a/spring-test/src/test/java/org/springframework/mock/web/MockHttpServletRequestTests.java b/spring-test/src/test/java/org/springframework/mock/web/MockHttpServletRequestTests.java
index f0cd2866e60..322634e205f 100644
--- a/spring-test/src/test/java/org/springframework/mock/web/MockHttpServletRequestTests.java
+++ b/spring-test/src/test/java/org/springframework/mock/web/MockHttpServletRequestTests.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2014 the original author or authors.
+ * Copyright 2002-2015 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.
@@ -21,6 +21,7 @@ import java.nio.charset.Charset;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
+import java.util.Date;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.List;
@@ -40,11 +41,17 @@ import static org.junit.Assert.*;
* @author Mark Fisher
* @author Rossen Stoyanchev
* @author Sam Brannen
+ * @author Brian Clozel
+ * @author Jakub Narloch
*/
public class MockHttpServletRequestTests {
private static final String HOST = "Host";
+ private static final String CONTENT_TYPE = "Content-Type";
+
+ private static final String IF_MODIFIED_SINCE = "If-Modified-Since";
+
private MockHttpServletRequest request = new MockHttpServletRequest();
@@ -69,7 +76,7 @@ public class MockHttpServletRequestTests {
String contentType = "test/plain";
request.setContentType(contentType);
assertEquals(contentType, request.getContentType());
- assertEquals(contentType, request.getHeader("Content-Type"));
+ assertEquals(contentType, request.getHeader(CONTENT_TYPE));
assertNull(request.getCharacterEncoding());
}
@@ -78,7 +85,7 @@ public class MockHttpServletRequestTests {
String contentType = "test/plain;charset=UTF-8";
request.setContentType(contentType);
assertEquals(contentType, request.getContentType());
- assertEquals(contentType, request.getHeader("Content-Type"));
+ assertEquals(contentType, request.getHeader(CONTENT_TYPE));
assertEquals("UTF-8", request.getCharacterEncoding());
}
@@ -87,7 +94,7 @@ public class MockHttpServletRequestTests {
String contentType = "test/plain";
request.addHeader("Content-Type", contentType);
assertEquals(contentType, request.getContentType());
- assertEquals(contentType, request.getHeader("Content-Type"));
+ assertEquals(contentType, request.getHeader(CONTENT_TYPE));
assertNull(request.getCharacterEncoding());
}
@@ -96,7 +103,7 @@ public class MockHttpServletRequestTests {
String contentType = "test/plain;charset=UTF-8";
request.addHeader("Content-Type", contentType);
assertEquals(contentType, request.getContentType());
- assertEquals(contentType, request.getHeader("Content-Type"));
+ assertEquals(contentType, request.getHeader(CONTENT_TYPE));
assertEquals("UTF-8", request.getCharacterEncoding());
}
@@ -107,7 +114,7 @@ public class MockHttpServletRequestTests {
String contentType = "test/plain;charset=\"utf-8\";foo=\"charset=bar\";foocharset=bar;foo=bar";
request.addHeader("Content-Type", contentType);
assertEquals(contentType, request.getContentType());
- assertEquals(contentType, request.getHeader("Content-Type"));
+ assertEquals(contentType, request.getHeader(CONTENT_TYPE));
assertEquals("UTF-8", request.getCharacterEncoding());
}
@@ -116,7 +123,7 @@ public class MockHttpServletRequestTests {
request.setContentType("test/plain");
request.setCharacterEncoding("UTF-8");
assertEquals("test/plain", request.getContentType());
- assertEquals("test/plain;charset=UTF-8", request.getHeader("Content-Type"));
+ assertEquals("test/plain;charset=UTF-8", request.getHeader(CONTENT_TYPE));
assertEquals("UTF-8", request.getCharacterEncoding());
}
@@ -125,7 +132,7 @@ public class MockHttpServletRequestTests {
request.setCharacterEncoding("UTF-8");
request.setContentType("test/plain");
assertEquals("test/plain", request.getContentType());
- assertEquals("test/plain;charset=UTF-8", request.getHeader("Content-Type"));
+ assertEquals("test/plain;charset=UTF-8", request.getHeader(CONTENT_TYPE));
assertEquals("UTF-8", request.getCharacterEncoding());
}
@@ -378,6 +385,44 @@ public class MockHttpServletRequestTests {
assertTrue(request.isSecure());
}
+ @Test
+ public void httpHeaderDate() throws Exception {
+ Date date = new Date();
+ request.addHeader(IF_MODIFIED_SINCE, date);
+ assertEquals(date.getTime(), request.getDateHeader(IF_MODIFIED_SINCE));
+ }
+
+ @Test
+ public void httpHeaderTimestamp() throws Exception {
+ long timestamp = new Date().getTime();
+ request.addHeader(IF_MODIFIED_SINCE, timestamp);
+ assertEquals(timestamp, request.getDateHeader(IF_MODIFIED_SINCE));
+ }
+
+ @Test
+ public void httpHeaderRfcFormatedDate() throws Exception {
+ request.addHeader(IF_MODIFIED_SINCE, "Tue, 21 Jul 2015 10:00:00 GMT");
+ assertEquals(1437472800000L, request.getDateHeader(IF_MODIFIED_SINCE));
+ }
+
+ @Test
+ public void httpHeaderFirstVariantFormatedDate() throws Exception {
+ request.addHeader(IF_MODIFIED_SINCE, "Tue, 21-Jul-15 10:00:00 GMT");
+ assertEquals(1437472800000L, request.getDateHeader(IF_MODIFIED_SINCE));
+ }
+
+ @Test
+ public void httpHeaderSecondVariantFormatedDate() throws Exception {
+ request.addHeader(IF_MODIFIED_SINCE, "Tue Jul 21 10:00:00 2015");
+ assertEquals(1437472800000L, request.getDateHeader(IF_MODIFIED_SINCE));
+ }
+
+ @Test(expected = IllegalArgumentException.class)
+ public void httpHeaderFormatedDateError() throws Exception {
+ request.addHeader(IF_MODIFIED_SINCE, "This is not a date");
+ request.getDateHeader(IF_MODIFIED_SINCE);
+ }
+
private void assertEqualEnumerations(Enumeration> enum1, Enumeration> enum2) {
assertNotNull(enum1);
assertNotNull(enum2);
diff --git a/spring-test/src/test/java/org/springframework/mock/web/MockHttpServletResponseTests.java b/spring-test/src/test/java/org/springframework/mock/web/MockHttpServletResponseTests.java
index 3ed21c536c5..05341c8a8b0 100644
--- a/spring-test/src/test/java/org/springframework/mock/web/MockHttpServletResponseTests.java
+++ b/spring-test/src/test/java/org/springframework/mock/web/MockHttpServletResponseTests.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2014 the original author or authors.
+ * Copyright 2002-2015 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.
@@ -19,6 +19,7 @@ package org.springframework.mock.web;
import java.io.IOException;
import java.util.Arrays;
import java.util.Collection;
+
import javax.servlet.http.HttpServletResponse;
import org.junit.Test;
@@ -238,6 +239,20 @@ public class MockHttpServletResponseTests {
assertEquals(redirectUrl, response.getRedirectedUrl());
}
+ @Test
+ public void setDateHeader() {
+ response.setDateHeader("Last-Modified", 1437472800000L);
+ assertEquals("Tue, 21 Jul 2015 10:00:00 GMT", response.getHeader("Last-Modified"));
+ }
+
+ @Test
+ public void addDateHeader() {
+ response.addDateHeader("Last-Modified", 1437472800000L);
+ response.addDateHeader("Last-Modified", 1437472801000L);
+ assertEquals("Tue, 21 Jul 2015 10:00:00 GMT", response.getHeaders("Last-Modified").get(0));
+ assertEquals("Tue, 21 Jul 2015 10:00:01 GMT", response.getHeaders("Last-Modified").get(1));
+ }
+
/**
* SPR-10414
*/
diff --git a/spring-web/src/test/java/org/springframework/mock/web/test/MockHttpServletRequest.java b/spring-web/src/test/java/org/springframework/mock/web/test/MockHttpServletRequest.java
index 40b9a8ce1fa..0071cf05b9a 100644
--- a/spring-web/src/test/java/org/springframework/mock/web/test/MockHttpServletRequest.java
+++ b/spring-web/src/test/java/org/springframework/mock/web/test/MockHttpServletRequest.java
@@ -24,6 +24,8 @@ import java.io.InputStreamReader;
import java.io.Reader;
import java.io.UnsupportedEncodingException;
import java.security.Principal;
+import java.text.ParseException;
+import java.text.SimpleDateFormat;
import java.util.Collection;
import java.util.Collections;
import java.util.Date;
@@ -36,6 +38,8 @@ import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Set;
+import java.util.TimeZone;
+
import javax.servlet.AsyncContext;
import javax.servlet.DispatcherType;
import javax.servlet.RequestDispatcher;
@@ -120,6 +124,17 @@ public class MockHttpServletRequest implements HttpServletRequest {
private static final ServletInputStream EMPTY_SERVLET_INPUT_STREAM =
new DelegatingServletInputStream(new ByteArrayInputStream(new byte[0]));
+ /**
+ * Date formats as specified in the HTTP RFC
+ * @see Section 7.1.1.1 of RFC 7231
+ */
+ 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 boolean active = true;
@@ -860,20 +875,16 @@ public class MockHttpServletRequest implements HttpServletRequest {
/**
* Add a header entry for the given name.
- * If there was no entry for that header name before, the value will be used
- * as-is. In case of an existing entry, a String array will be created,
- * adding the given value (more specifically, its toString representation)
- * as further element.
- *
Multiple values can only be stored as list of Strings, following the
- * Servlet spec (see {@code getHeaders} accessor). As alternative to
- * repeated {@code addHeader} calls for individual elements, you can
- * use a single call with an entire array or Collection of values as
- * parameter.
+ *
While this method can take any {@code Object} as a parameter,
+ * it is recommended to use the following types:
+ *
+ * - String or any Object to be converted using {@code toString}, see {@link #getHeader}
+ * - String, Number or Date for date headers, see {@link #getDateHeader}
+ * - String or Number for integer headers, see {@link #getIntHeader}
+ * - {@code String[]} and {@code Collection} for multiple values, see {@link #getHeaders}
+ *
* @see #getHeaderNames
- * @see #getHeader
* @see #getHeaders
- * @see #getDateHeader
- * @see #getIntHeader
*/
public void addHeader(String name, Object value) {
if (CONTENT_TYPE_HEADER.equalsIgnoreCase(name)) {
@@ -902,6 +913,18 @@ public class MockHttpServletRequest implements HttpServletRequest {
}
}
+ /**
+ * Return the long timestamp for the date header with the given {@code name}.
+ * If the internal value representation is a String, this method will try
+ * to parse it as a date using the supported date formats:
+ *
+ * - "EEE, dd MMM yyyy HH:mm:ss zzz"
+ * - "EEE, dd-MMM-yy HH:mm:ss zzz"
+ * - "EEE MMM dd HH:mm:ss yyyy"
+ *
+ * @param name the header name
+ * @see Section 7.1.1.1 of RFC 7231
+ */
@Override
public long getDateHeader(String name) {
HeaderValueHolder header = HeaderValueHolder.getByName(this.headers, name);
@@ -912,15 +935,32 @@ public class MockHttpServletRequest implements HttpServletRequest {
else if (value instanceof Number) {
return ((Number) value).longValue();
}
+ else if (value instanceof String) {
+ return parseDateHeader(name, (String) value);
+ }
else if (value != null) {
throw new IllegalArgumentException(
- "Value for header '" + name + "' is neither a Date nor a Number: " + value);
+ "Value for header '" + name + "' is not a Date, Number, or String: " + value);
}
else {
return -1L;
}
}
+ private long parseDateHeader(String name, String value) {
+ for (String dateFormat : DATE_FORMATS) {
+ SimpleDateFormat simpleDateFormat = new SimpleDateFormat(dateFormat, Locale.US);
+ simpleDateFormat.setTimeZone(GMT);
+ try {
+ return simpleDateFormat.parse(value).getTime();
+ }
+ catch (ParseException ex) {
+ // ignore
+ }
+ }
+ throw new IllegalArgumentException("Cannot parse date value '" + value + "' for '" + name + "' header");
+ }
+
@Override
public String getHeader(String name) {
HeaderValueHolder header = HeaderValueHolder.getByName(this.headers, name);
@@ -1171,7 +1211,7 @@ public class MockHttpServletRequest implements HttpServletRequest {
@Override
public Collection getParts() throws IOException, IllegalStateException, ServletException {
List result = new LinkedList();
- for(List list : this.parts.values()) {
+ for (List list : this.parts.values()) {
result.addAll(list);
}
return result;