WW-4263 Adds proper and thread safe handling of If-Modified-Since header

git-svn-id: https://svn.apache.org/repos/asf/struts/struts2/trunk@1555018 13f79535-47bb-0310-9956-ffa450edef68
This commit is contained in:
Lukasz Lenart
2014-01-03 06:42:19 +00:00
parent 3c3168bff4
commit bb31672e8a
3 changed files with 76 additions and 31 deletions
@@ -21,7 +21,15 @@
package org.apache.struts2;
import com.opensymphony.xwork2.util.logging.Logger;
import com.opensymphony.xwork2.util.logging.LoggerFactory;
import org.apache.commons.lang3.time.FastDateFormat;
import javax.servlet.http.HttpServletRequest;
import java.text.ParseException;
import java.util.Date;
import java.util.Locale;
import java.util.TimeZone;
/**
@@ -29,6 +37,20 @@ import javax.servlet.http.HttpServletRequest;
*/
public class RequestUtils {
private static final Logger LOG = LoggerFactory.getLogger(RequestUtils.class);
private static final TimeZone GMT = TimeZone.getTimeZone("GMT");
private static final String FORMAT_PATTERN_RFC1123 = "EEE, dd MMM yyyy HH:mm:ss zzz";
private static final String FORMAT_PATTERN_RFC1036 = "EEE, dd-MMM-yy HH:mm:ss zzz";
private static final String FORMAT_PATTERN_ASCTIME = "EEE MMM d HH:mm:ss yyyy";
private static final FastDateFormat[] IF_MODIFIED_SINCE_FORMATS = {
FastDateFormat.getInstance(FORMAT_PATTERN_RFC1123, GMT, Locale.US),
FastDateFormat.getInstance(FORMAT_PATTERN_RFC1036, GMT, Locale.US),
FastDateFormat.getInstance(FORMAT_PATTERN_ASCTIME, GMT, Locale.US)
};
/**
* Retrieves the current request servlet path.
* Deals with differences between servlet specs (2.2 vs 2.3+)
@@ -84,4 +106,30 @@ public class RequestUtils {
uri = request.getRequestURI();
return uri.substring(request.getContextPath().length());
}
/**
* Parse input string as date in formats defined for If-Modified-Since header,
* see:
* https://issues.apache.org/jira/browse/WW-4263
* https://web.archive.org/web/20081014021349/http://rfc.net/rfc2616.html#p20
*
* @param headerValue value of If-Modified-Since header
* @return proper date or null
*/
public static Date parseIfModifiedSince(String headerValue) {
for (FastDateFormat fastDateFormat : IF_MODIFIED_SINCE_FORMATS) {
try {
return fastDateFormat.parse(headerValue);
} catch (ParseException ignore) {
if (LOG.isDebugEnabled()) {
LOG.debug("Error parsing value [#0] as [#1]!", headerValue, fastDateFormat);
}
}
}
if (LOG.isDebugEnabled()) {
LOG.debug("Error parsing value [#0] as date!", headerValue);
}
return null;
}
}
@@ -25,12 +25,12 @@ package org.apache.struts2;
* <code>RequestUtilsTest</code>
*
*/
import javax.servlet.http.HttpServletRequest;
import junit.framework.TestCase;
import org.easymock.MockControl;
import javax.servlet.http.HttpServletRequest;
import java.util.Date;
public class RequestUtilsTest extends TestCase {
private MockControl control;
@@ -86,6 +86,22 @@ public class RequestUtilsTest extends TestCase {
control.verify();
}
public void testParseRFC1123() {
Date date = RequestUtils.parseIfModifiedSince("Thu, 23 Jul 2013 19:42:23 GMT");
assertNotNull(date);
}
public void testParseRFC1036() {
Date date = RequestUtils.parseIfModifiedSince("Thursday, 23-Jul-13 19:42:23 GMT");
assertNotNull(date);
}
public void testParseASC() {
Date date = RequestUtils.parseIfModifiedSince("Thu Jul 23 19:42:23 2013");
assertNotNull(date);
}
protected void setUp() {
control = MockControl.createControl(HttpServletRequest.class);
requestMock = (HttpServletRequest) control.getMock();
@@ -21,29 +21,19 @@
package org.apache.struts2.rest;
import com.opensymphony.xwork2.util.logging.Logger;
import com.opensymphony.xwork2.util.logging.LoggerFactory;
import org.apache.struts2.RequestUtils;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;
import static javax.servlet.http.HttpServletResponse.SC_CREATED;
import static javax.servlet.http.HttpServletResponse.SC_NOT_MODIFIED;
import static javax.servlet.http.HttpServletResponse.SC_OK;
import static javax.servlet.http.HttpServletResponse.*;
/**
* Default implementation of rest info that uses fluent-style construction
*/
public class DefaultHttpHeaders implements HttpHeaders {
private static final Logger LOG = LoggerFactory.getLogger(DefaultHttpHeaders.class);
private static final String IF_MODIFIED_SINCE_DATE_FORMAT = "EEE, dd MMM yyyy HH:mm:ss zzz";
private static final SimpleDateFormat IF_MODIFIED_SINCE_FORMAT = new SimpleDateFormat(IF_MODIFIED_SINCE_DATE_FORMAT);
private String resultCode;
private int status = SC_OK;
private Object etag;
@@ -144,13 +134,13 @@ public class DefaultHttpHeaders implements HttpHeaders {
}
}
String reqLastModified = request.getHeader("If-Modified-Since");
if (lastModified != null && reqLastModified != null) {
lastModifiedNotChanged = compareIfModifiedSince(reqLastModified);
String headerIfModifiedSince = request.getHeader("If-Modified-Since");
if (lastModified != null && headerIfModifiedSince != null) {
lastModifiedNotChanged = compareIfModifiedSince(headerIfModifiedSince);
}
if ((etagNotChanged && lastModifiedNotChanged) ||
(etagNotChanged && reqLastModified == null) ||
(etagNotChanged && headerIfModifiedSince == null) ||
(lastModifiedNotChanged && reqETag == null)) {
status = SC_NOT_MODIFIED;
}
@@ -160,18 +150,9 @@ public class DefaultHttpHeaders implements HttpHeaders {
return resultCode;
}
private boolean compareIfModifiedSince(String reqLastModified) {
try {
if (lastModified.compareTo(IF_MODIFIED_SINCE_FORMAT.parse(reqLastModified)) >= 0) {
return true;
}
} catch (ParseException e) {
if (LOG.isDebugEnabled()) {
LOG.debug("Got error parsing If-Modified-Since header value [#0] as [#1]!", e, reqLastModified, IF_MODIFIED_SINCE_DATE_FORMAT);
}
return false;
}
return false;
private boolean compareIfModifiedSince(String headerIfModifiedSince) {
Date requestLastModified = RequestUtils.parseIfModifiedSince(headerIfModifiedSince);
return requestLastModified != null && lastModified.compareTo(requestLastModified) >= 0;
}
public int getStatus() {