diff --git a/org.springframework.web.portlet/src/main/java/org/springframework/web/portlet/context/PortletWebRequest.java b/org.springframework.web.portlet/src/main/java/org/springframework/web/portlet/context/PortletWebRequest.java
index 9be71736c4539170a47f6961f0bbbb796859a413..3eedfb651b60e8bae0cdcf854a86311edf634e7a 100644
--- a/org.springframework.web.portlet/src/main/java/org/springframework/web/portlet/context/PortletWebRequest.java
+++ b/org.springframework.web.portlet/src/main/java/org/springframework/web/portlet/context/PortletWebRequest.java
@@ -177,6 +177,14 @@ public class PortletWebRequest extends PortletRequestAttributes implements Nativ
return false;
}
+ /**
+ * Last-modified handling not supported for portlet requests:
+ * As a consequence, this method always returns false
.
+ */
+ public boolean checkNotModified(String eTag) {
+ return false;
+ }
+
public String getDescription(boolean includeClientInfo) {
PortletRequest request = getRequest();
StringBuilder result = new StringBuilder();
diff --git a/org.springframework.web/src/main/java/org/springframework/web/context/request/FacesWebRequest.java b/org.springframework.web/src/main/java/org/springframework/web/context/request/FacesWebRequest.java
index 458d67a29f722aab571cb992b1a89d081c7e0162..edd13a778a61034ad8ed25f1035242fa323676e7 100644
--- a/org.springframework.web/src/main/java/org/springframework/web/context/request/FacesWebRequest.java
+++ b/org.springframework.web/src/main/java/org/springframework/web/context/request/FacesWebRequest.java
@@ -130,6 +130,10 @@ public class FacesWebRequest extends FacesRequestAttributes implements NativeWeb
return false;
}
+ public boolean checkNotModified(String eTag) {
+ return false;
+ }
+
public String getDescription(boolean includeClientInfo) {
ExternalContext externalContext = getExternalContext();
StringBuilder sb = new StringBuilder();
diff --git a/org.springframework.web/src/main/java/org/springframework/web/context/request/ServletWebRequest.java b/org.springframework.web/src/main/java/org/springframework/web/context/request/ServletWebRequest.java
index b59909052b0d94ad71c63bab407b8256a59e95bf..17a23c08cedcf326c9593baca3b78e8cfb6ebe4c 100644
--- a/org.springframework.web/src/main/java/org/springframework/web/context/request/ServletWebRequest.java
+++ b/org.springframework.web/src/main/java/org/springframework/web/context/request/ServletWebRequest.java
@@ -40,10 +40,16 @@ import org.springframework.util.StringUtils;
*/
public class ServletWebRequest extends ServletRequestAttributes implements NativeWebRequest {
+ private static final String HEADER_ETAG = "ETag";
+
private static final String HEADER_IF_MODIFIED_SINCE = "If-Modified-Since";
+ private static final String HEADER_IF_NONE_MATCH = "If-None-Match";
+
private static final String HEADER_LAST_MODIFIED = "Last-Modified";
+ private static final String METHOD_GET = "GET";
+
private HttpServletResponse response;
@@ -186,7 +192,7 @@ public class ServletWebRequest extends ServletRequestAttributes implements Nativ
long ifModifiedSince = getRequest().getDateHeader(HEADER_IF_MODIFIED_SINCE);
this.notModified = (ifModifiedSince >= (lastModifiedTimestamp / 1000 * 1000));
if (this.response != null) {
- if (this.notModified && "GET".equals(getRequest().getMethod())) {
+ if (this.notModified && METHOD_GET.equals(getRequest().getMethod())) {
this.response.setStatus(HttpServletResponse.SC_NOT_MODIFIED);
}
else {
@@ -197,6 +203,30 @@ public class ServletWebRequest extends ServletRequestAttributes implements Nativ
return this.notModified;
}
+ public boolean checkNotModified(String eTag) {
+ if (StringUtils.hasLength(eTag) && !this.notModified &&
+ (this.response == null || !this.response.containsHeader(HEADER_ETAG))) {
+ if (!eTag.startsWith("\"")) {
+ eTag = "\"" + eTag;
+ }
+ if (!eTag.endsWith("\"")) {
+ eTag = eTag + "\"";
+ }
+ String ifNoneMatch = getRequest().getHeader(HEADER_IF_NONE_MATCH);
+ this.notModified = eTag.equals(ifNoneMatch);
+ if (this.response != null) {
+ if (this.notModified && METHOD_GET.equals(getRequest().getMethod())) {
+ this.response.setStatus(HttpServletResponse.SC_NOT_MODIFIED);
+ }
+ else {
+ this.response.setHeader(HEADER_ETAG, eTag);
+ }
+ }
+ }
+ return this.notModified;
+
+ }
+
public boolean isNotModified() {
return this.notModified;
}
diff --git a/org.springframework.web/src/main/java/org/springframework/web/context/request/WebRequest.java b/org.springframework.web/src/main/java/org/springframework/web/context/request/WebRequest.java
index 61219935944787b1bc718a2cdf271c66b60ff7e7..9db1794781c58fe6dfb144396b441d57412f7c83 100644
--- a/org.springframework.web/src/main/java/org/springframework/web/context/request/WebRequest.java
+++ b/org.springframework.web/src/main/java/org/springframework/web/context/request/WebRequest.java
@@ -141,6 +141,9 @@ public interface WebRequest extends RequestAttributes {
* model.addAttribute(...);
* return "myViewName";
* }
+ *
Note: that you typically want to use either + * this {@link #checkNotModified(long)} method; or + * {@link #checkNotModified(String)}, but not both. * @param lastModifiedTimestamp the last-modified timestamp that * the application determined for the underlying resource * @return whether the request qualifies as not modified, @@ -149,6 +152,35 @@ public interface WebRequest extends RequestAttributes { */ boolean checkNotModified(long lastModifiedTimestamp); + /** + * Check whether the request qualifies as not modified given the + * supplied {@code ETag} (entity tag), as determined by the application. + *
This will also transparently set the appropriate response headers, + * for both the modified case and the not-modified case. + *
Typical usage: + *
+ * public String myHandleMethod(WebRequest webRequest, Model model) { + * String eTag = // application-specific calculation + * if (request.checkNotModified(eTag)) { + * // shortcut exit - no further processing necessary + * return null; + * } + * // further request processing, actually building content + * model.addAttribute(...); + * return "myViewName"; + * }+ *
Note: that you typically want to use either + * this {@link #checkNotModified(String)} method; or + * {@link #checkNotModified(long)}, but not both. + * @param eTag the entity tag that the application determined + * for the underlying resource. This parameter will be padded + * with quotes (") if necessary. + * @return whether the request qualifies as not modified, + * allowing to abort request processing and relying on the response + * telling the client that the content has not been modified + */ + boolean checkNotModified(String eTag); + /** * Get a short description of this request, * typically containing request URI and session id. diff --git a/org.springframework.web/src/test/java/org/springframework/web/context/request/ServletWebRequestTests.java b/org.springframework.web/src/test/java/org/springframework/web/context/request/ServletWebRequestTests.java index f8ae9ff4783e479ab0547efbe4de8544317d41a6..2e02082de587ffe9996e82b47511a51116ae344c 100644 --- a/org.springframework.web/src/test/java/org/springframework/web/context/request/ServletWebRequestTests.java +++ b/org.springframework.web/src/test/java/org/springframework/web/context/request/ServletWebRequestTests.java @@ -16,6 +16,7 @@ package org.springframework.web.context.request; +import java.util.Date; import java.util.Locale; import java.util.Map; import javax.servlet.ServletRequest; @@ -25,27 +26,40 @@ import javax.servlet.http.HttpServletRequestWrapper; import javax.servlet.http.HttpServletResponse; import javax.servlet.http.HttpServletResponseWrapper; -import static org.junit.Assert.*; +import org.junit.Before; import org.junit.Test; import org.springframework.mock.web.MockHttpServletRequest; import org.springframework.mock.web.MockHttpServletResponse; import org.springframework.web.multipart.MultipartRequest; +import static org.junit.Assert.*; + /** * @author Juergen Hoeller * @since 26.07.2006 */ public class ServletWebRequestTests { + private MockHttpServletRequest servletRequest; + + private MockHttpServletResponse servletResponse; + + private ServletWebRequest request; + + @Before + public void setUp() { + servletRequest = new MockHttpServletRequest(); + servletResponse = new MockHttpServletResponse(); + request = new ServletWebRequest(servletRequest, servletResponse); + } + @Test - public void testParameters() { - MockHttpServletRequest servletRequest = new MockHttpServletRequest(); + public void parameters() { servletRequest.addParameter("param1", "value1"); servletRequest.addParameter("param2", "value2"); servletRequest.addParameter("param2", "value2a"); - ServletWebRequest request = new ServletWebRequest(servletRequest); assertEquals("value1", request.getParameter("param1")); assertEquals(1, request.getParameterValues("param1").length); assertEquals("value1", request.getParameterValues("param1")[0]); @@ -64,19 +78,14 @@ public class ServletWebRequestTests { } @Test - public void testLocale() { - MockHttpServletRequest servletRequest = new MockHttpServletRequest(); + public void locale() { servletRequest.addPreferredLocale(Locale.UK); - ServletWebRequest request = new ServletWebRequest(servletRequest); assertEquals(Locale.UK, request.getLocale()); } @Test - public void testNativeRequest() { - MockHttpServletRequest servletRequest = new MockHttpServletRequest(); - MockHttpServletResponse servletResponse = new MockHttpServletResponse(); - ServletWebRequest request = new ServletWebRequest(servletRequest, servletResponse); + public void nativeRequest() { assertSame(servletRequest, request.getNativeRequest()); assertSame(servletRequest, request.getNativeRequest(ServletRequest.class)); assertSame(servletRequest, request.getNativeRequest(HttpServletRequest.class)); @@ -90,9 +99,7 @@ public class ServletWebRequestTests { } @Test - public void testDecoratedNativeRequest() { - MockHttpServletRequest servletRequest = new MockHttpServletRequest(); - MockHttpServletResponse servletResponse = new MockHttpServletResponse(); + public void decoratedNativeRequest() { HttpServletRequest decoratedRequest = new HttpServletRequestWrapper(servletRequest); HttpServletResponse decoratedResponse = new HttpServletResponseWrapper(servletResponse); ServletWebRequest request = new ServletWebRequest(decoratedRequest, decoratedResponse); @@ -108,4 +115,65 @@ public class ServletWebRequestTests { assertNull(request.getNativeResponse(MultipartRequest.class)); } + @Test + public void checkNotModifiedTimeStamp() { + long currentTime = new Date().getTime(); + servletRequest.setMethod("GET"); + servletRequest.addHeader("If-Modified-Since", currentTime); + + request.checkNotModified(currentTime); + + assertEquals(304, servletResponse.getStatus()); + } + + @Test + public void checkModifiedTimeStamp() { + long currentTime = new Date().getTime(); + long oneMinuteAgo = currentTime - (1000 * 60); + servletRequest.setMethod("GET"); + servletRequest.addHeader("If-Modified-Since", oneMinuteAgo); + + request.checkNotModified(currentTime); + + assertEquals(200, servletResponse.getStatus()); + assertEquals(currentTime, servletResponse.getHeader("Last-Modified")); + } + + @Test + public void checkNotModifiedETag() { + String eTag = "Foo"; + servletRequest.setMethod("GET"); + servletRequest.addHeader("If-None-Match", "\"" + eTag + "\""); + + request.checkNotModified(eTag); + + assertEquals(304, servletResponse.getStatus()); + } + + @Test + public void checkModifiedETagNonQuoted() { + String currentETag = "Foo"; + String oldEtag = "Bar"; + servletRequest.setMethod("GET"); + servletRequest.addHeader("If-None-Match", "\"" + oldEtag + "\""); + + request.checkNotModified(currentETag); + + assertEquals(200, servletResponse.getStatus()); + assertEquals("\"" + currentETag + "\"", servletResponse.getHeader("ETag")); + } + + @Test + public void checkModifiedETagQuoted() { + String currentETag = "\"Foo\""; + String oldEtag = "Bar"; + servletRequest.setMethod("GET"); + servletRequest.addHeader("If-None-Match", oldEtag); + + request.checkNotModified(currentETag); + + assertEquals(200, servletResponse.getStatus()); + assertEquals(currentETag, servletResponse.getHeader("ETag")); + } + }