Improve Last-Modified & ETag support
Prior to this change, the `"Last-Modified"` and "`Etag`" support had been improved with SPR-11324: HTTP response headers are now automatically added for conditional requests and more. This commit fixes the format of the "`Last-Modified`" and "`ETag`" values, which were using an epoch timestamp rather than an HTTP-date format defined in RFC 7231 section 7.1.1.1. Also, Conditional responses are only applied when the given response applies, i.e. when it has an compatible HTTP status (2xx). Issue: SPR-13090
This commit is contained in:
@@ -17,16 +17,19 @@
|
||||
package org.springframework.web.context.request;
|
||||
|
||||
import java.security.Principal;
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.util.Date;
|
||||
import java.util.Iterator;
|
||||
import java.util.Locale;
|
||||
import java.util.Map;
|
||||
import java.util.TimeZone;
|
||||
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
import javax.servlet.http.HttpSession;
|
||||
|
||||
import org.springframework.http.HttpMethod;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.util.CollectionUtils;
|
||||
import org.springframework.util.ObjectUtils;
|
||||
import org.springframework.util.StringUtils;
|
||||
@@ -55,6 +58,10 @@ public class ServletWebRequest extends ServletRequestAttributes implements Nativ
|
||||
|
||||
private static final String METHOD_HEAD = "HEAD";
|
||||
|
||||
private static final String DATE_FORMAT = "EEE, dd MMM yyyy HH:mm:ss zzz";
|
||||
|
||||
private static TimeZone GMT = TimeZone.getTimeZone("GMT");
|
||||
|
||||
|
||||
private boolean notModified = false;
|
||||
|
||||
@@ -174,19 +181,33 @@ public class ServletWebRequest extends ServletRequestAttributes implements Nativ
|
||||
public boolean checkNotModified(long lastModifiedTimestamp) {
|
||||
HttpServletResponse response = getResponse();
|
||||
if (lastModifiedTimestamp >= 0 && !this.notModified) {
|
||||
if (response == null || !response.containsHeader(HEADER_LAST_MODIFIED)) {
|
||||
if (response == null || isResponseCompatibleWithConditional(response, HEADER_LAST_MODIFIED)) {
|
||||
this.notModified = isTimeStampNotModified(lastModifiedTimestamp);
|
||||
if (response != null) {
|
||||
if (this.notModified && supportsNotModifiedStatus()) {
|
||||
response.setStatus(HttpServletResponse.SC_NOT_MODIFIED);
|
||||
}
|
||||
response.setDateHeader(HEADER_LAST_MODIFIED, lastModifiedTimestamp);
|
||||
response.setHeader(HEADER_LAST_MODIFIED, formatDate(lastModifiedTimestamp));
|
||||
}
|
||||
}
|
||||
}
|
||||
return this.notModified;
|
||||
}
|
||||
|
||||
private boolean isResponseCompatibleWithConditional(HttpServletResponse response, String... headers) {
|
||||
if (response != null) {
|
||||
if (HttpStatus.valueOf(response.getStatus()).is2xxSuccessful()) {
|
||||
for (String header : headers) {
|
||||
if (response.containsHeader(header)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@SuppressWarnings("deprecation")
|
||||
private boolean isTimeStampNotModified(long lastModifiedTimestamp) {
|
||||
long ifModifiedSince = -1;
|
||||
@@ -214,7 +235,7 @@ public class ServletWebRequest extends ServletRequestAttributes implements Nativ
|
||||
public boolean checkNotModified(String etag) {
|
||||
HttpServletResponse response = getResponse();
|
||||
if (StringUtils.hasLength(etag) && !this.notModified) {
|
||||
if (response == null || !response.containsHeader(HEADER_ETAG)) {
|
||||
if (response == null || isResponseCompatibleWithConditional(response, HEADER_ETAG)) {
|
||||
etag = addEtagPadding(etag);
|
||||
this.notModified = isETagNotModified(etag);
|
||||
if (response != null) {
|
||||
@@ -244,7 +265,7 @@ public class ServletWebRequest extends ServletRequestAttributes implements Nativ
|
||||
// compare weak/strong ETags as per https://tools.ietf.org/html/rfc7232#section-2.3
|
||||
if (StringUtils.hasLength(clientETag) &&
|
||||
(clientETag.replaceFirst("^W/", "").equals(etag.replaceFirst("^W/", ""))
|
||||
|| clientETag.equals("*"))) {
|
||||
|| clientETag.equals("*"))) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -262,8 +283,7 @@ public class ServletWebRequest extends ServletRequestAttributes implements Nativ
|
||||
public boolean checkNotModified(String etag, long lastModifiedTimestamp) {
|
||||
HttpServletResponse response = getResponse();
|
||||
if (StringUtils.hasLength(etag) && !this.notModified) {
|
||||
if (response == null ||
|
||||
(!response.containsHeader(HEADER_ETAG) && !response.containsHeader(HEADER_LAST_MODIFIED))) {
|
||||
if (response == null || isResponseCompatibleWithConditional(response, HEADER_ETAG, HEADER_LAST_MODIFIED)) {
|
||||
etag = addEtagPadding(etag);
|
||||
this.notModified = isETagNotModified(etag) && isTimeStampNotModified(lastModifiedTimestamp);
|
||||
if (response != null) {
|
||||
@@ -271,13 +291,19 @@ public class ServletWebRequest extends ServletRequestAttributes implements Nativ
|
||||
response.setStatus(HttpServletResponse.SC_NOT_MODIFIED);
|
||||
}
|
||||
response.setHeader(HEADER_ETAG, etag);
|
||||
response.setDateHeader(HEADER_LAST_MODIFIED, lastModifiedTimestamp);
|
||||
response.setHeader(HEADER_LAST_MODIFIED, formatDate(lastModifiedTimestamp));
|
||||
}
|
||||
}
|
||||
}
|
||||
return this.notModified;
|
||||
}
|
||||
|
||||
private String formatDate(long date) {
|
||||
SimpleDateFormat dateFormat = new SimpleDateFormat(DATE_FORMAT, Locale.US);
|
||||
dateFormat.setTimeZone(GMT);
|
||||
return dateFormat.format(new Date(date));
|
||||
}
|
||||
|
||||
public boolean isNotModified() {
|
||||
return this.notModified;
|
||||
}
|
||||
|
||||
@@ -194,7 +194,7 @@ public interface WebRequest extends RequestAttributes {
|
||||
* Check whether the request qualifies as not modified given the
|
||||
* supplied {@code ETag} (entity tag) and last-modified timestamp,
|
||||
* as determined by the application.
|
||||
* <p>This will also transparently set the appropriate response headers,
|
||||
* <p>This will also transparently set the "Etag" and "Last-Modified" response headers,
|
||||
* for both the modified case and the not-modified case.
|
||||
* <p>Typical usage:
|
||||
* <pre class="code">
|
||||
|
||||
Reference in New Issue
Block a user