Add weak ETag support in ShallowEtagHeaderFilter

This commit adds weak ETag support in ShallowEtagHeaderFilter.
This improves the behavior of the filter in tow ways:

* weak ETags in request headers such as `W/"0badc0ffee"` will be
compared with a "weak comparison" (matching both weak and strong ETags
of the same value)
* when enabled with the "writeWeakETag" init param, the filter will
write weak Etags in its HTTP responses

Issue: SPR-13778
This commit is contained in:
Brian Clozel
2016-02-18 16:06:38 +01:00
parent 923534595c
commit 1c2ac49f2a
3 changed files with 85 additions and 11 deletions

View File

@@ -19,6 +19,7 @@ package org.springframework.web.filter;
import java.io.IOException;
import java.io.InputStream;
import java.io.PrintWriter;
import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.ServletOutputStream;
@@ -60,11 +61,28 @@ public class ShallowEtagHeaderFilter extends OncePerRequestFilter {
private static final String STREAMING_ATTRIBUTE = ShallowEtagHeaderFilter.class.getName() + ".STREAMING";
/** Checking for Servlet 3.0+ HttpServletResponse.getHeader(String) */
private static final boolean servlet3Present =
ClassUtils.hasMethod(HttpServletResponse.class, "getHeader", String.class);
private boolean writeWeakETag = false;
/**
* Set whether the ETag value written to the response should be weak, as per rfc7232.
* <p>Should be configured using an {@code <init-param>} for parameter name
* "writeWeakETag" in the filter definition in {@code web.xml}.
* @see <a href="https://tools.ietf.org/html/rfc7232#section-2.3">rfc7232 section-2.3</a>
*/
public boolean isWriteWeakETag() {
return writeWeakETag;
}
/**
* Return whether the ETag value written to the response should be weak, as per rfc7232.
*/
public void setWriteWeakETag(boolean writeWeakETag) {
this.writeWeakETag = writeWeakETag;
}
/**
* The default value is "false" so that the filter may delay the generation of
@@ -102,10 +120,13 @@ public class ShallowEtagHeaderFilter extends OncePerRequestFilter {
responseWrapper.copyBodyToResponse();
}
else if (isEligibleForEtag(request, responseWrapper, statusCode, responseWrapper.getContentInputStream())) {
String responseETag = generateETagHeaderValue(responseWrapper.getContentInputStream());
String responseETag = generateETagHeaderValue(responseWrapper.getContentInputStream(), this.writeWeakETag);
rawResponse.setHeader(HEADER_ETAG, responseETag);
String requestETag = request.getHeader(HEADER_IF_NONE_MATCH);
if (responseETag.equals(requestETag)) {
if (requestETag != null
&& (responseETag.equals(requestETag)
|| responseETag.replaceFirst("^W/", "").equals(requestETag.replaceFirst("^W/", ""))
|| "*".equals(requestETag))) {
if (logger.isTraceEnabled()) {
logger.trace("ETag [" + responseETag + "] equal to If-None-Match, sending 304");
}
@@ -163,11 +184,17 @@ public class ShallowEtagHeaderFilter extends OncePerRequestFilter {
* Generate the ETag header value from the given response body byte array.
* <p>The default implementation generates an MD5 hash.
* @param inputStream the response body as an InputStream
* @param isWeak whether the generated ETag should be weak
* @return the ETag header value
* @see org.springframework.util.DigestUtils
*/
protected String generateETagHeaderValue(InputStream inputStream) throws IOException {
StringBuilder builder = new StringBuilder("\"0");
protected String generateETagHeaderValue(InputStream inputStream, boolean isWeak) throws IOException {
// length of W/ + 0 + " + 32bits md5 hash + "
StringBuilder builder = new StringBuilder(37);
if (isWeak) {
builder.append("W/");
}
builder.append("\"0");
DigestUtils.appendMd5DigestAsHex(inputStream, builder);
builder.append('"');
return builder.toString();