diff --git a/org.springframework.web/src/main/java/org/springframework/web/filter/ShallowEtagHeaderFilter.java b/org.springframework.web/src/main/java/org/springframework/web/filter/ShallowEtagHeaderFilter.java index 65d2c3101e7..a05e05c350d 100644 --- a/org.springframework.web/src/main/java/org/springframework/web/filter/ShallowEtagHeaderFilter.java +++ b/org.springframework.web/src/main/java/org/springframework/web/filter/ShallowEtagHeaderFilter.java @@ -57,26 +57,61 @@ public class ShallowEtagHeaderFilter extends OncePerRequestFilter { filterChain.doFilter(request, responseWrapper); byte[] body = responseWrapper.toByteArray(); - String responseETag = generateETagHeaderValue(body); - response.setHeader(HEADER_ETAG, responseETag); + int statusCode = responseWrapper.getStatusCode(); - String requestETag = request.getHeader(HEADER_IF_NONE_MATCH); - if (responseETag.equals(requestETag)) { - if (logger.isTraceEnabled()) { - logger.trace("ETag [" + responseETag + "] equal to If-None-Match, sending 304"); + if (isEligibleForEtag(request, responseWrapper, statusCode, body)) { + String responseETag = generateETagHeaderValue(body); + response.setHeader(HEADER_ETAG, responseETag); + + String requestETag = request.getHeader(HEADER_IF_NONE_MATCH); + if (responseETag.equals(requestETag)) { + if (logger.isTraceEnabled()) { + logger.trace("ETag [" + responseETag + "] equal to If-None-Match, sending 304"); + } + response.setContentLength(0); + response.setStatus(HttpServletResponse.SC_NOT_MODIFIED); + } + else { + if (logger.isTraceEnabled()) { + logger.trace("ETag [" + responseETag + "] not equal to If-None-Match [" + requestETag + + "], sending normal response"); + } + copyBodyToResponse(body, response); } - response.setStatus(HttpServletResponse.SC_NOT_MODIFIED); } else { if (logger.isTraceEnabled()) { - logger.trace("ETag [" + responseETag + "] not equal to If-None-Match [" + requestETag + - "], sending normal response"); + logger.trace("Response with status code [" + statusCode + "] not eligible for ETag"); } - response.setContentLength(body.length); + copyBodyToResponse(body, response); + } + } + + private void copyBodyToResponse(byte[] body, HttpServletResponse response) throws IOException { + response.setContentLength(body.length); + if (body.length > 0) { FileCopyUtils.copy(body, response.getOutputStream()); } } + /** + * Indicates whether the given request and response are eligible for ETag generation. + * + *

Default implementation returns {@code true} for response status codes in the {@code 2xx} series. + * + * @param request the HTTP request + * @param response the HTTP response + * @param responseStatusCode the HTTP response status code + * @param responseBody the response body + * @return {@code true} if eligible for ETag generation; {@code false} otherwise + */ + protected boolean isEligibleForEtag(HttpServletRequest request, + HttpServletResponse response, + int responseStatusCode, + byte[] responseBody) { + return (responseStatusCode >= 200 && responseStatusCode < 300); + } + /** * Generate the ETag header value from the given response body byte array.

The default implementation generates an * MD5 hash. @@ -105,10 +140,36 @@ public class ShallowEtagHeaderFilter extends OncePerRequestFilter { private PrintWriter writer; + private int statusCode = -1; + private ShallowEtagResponseWrapper(HttpServletResponse response) { super(response); } + @Override + public void setStatus(int sc) { + super.setStatus(sc); + this.statusCode = sc; + } + + @Override + public void setStatus(int sc, String sm) { + super.setStatus(sc, sm); + this.statusCode = sc; + } + + @Override + public void sendError(int sc) throws IOException { + super.sendError(sc); + this.statusCode = sc; + } + + @Override + public void sendError(int sc, String msg) throws IOException { + super.sendError(sc, msg); + this.statusCode = sc; + } + @Override public ServletOutputStream getOutputStream() { return this.outputStream; @@ -135,6 +196,10 @@ public class ShallowEtagHeaderFilter extends OncePerRequestFilter { resetBuffer(); } + private int getStatusCode() { + return statusCode; + } + private byte[] toByteArray() { return this.content.toByteArray(); } diff --git a/org.springframework.web/src/test/java/org/springframework/web/filter/ShallowEtagHeaderFilterTest.java b/org.springframework.web/src/test/java/org/springframework/web/filter/ShallowEtagHeaderFilterTest.java index a088908e661..a3f56ecfbb9 100644 --- a/org.springframework.web/src/test/java/org/springframework/web/filter/ShallowEtagHeaderFilterTest.java +++ b/org.springframework.web/src/test/java/org/springframework/web/filter/ShallowEtagHeaderFilterTest.java @@ -40,6 +40,15 @@ public class ShallowEtagHeaderFilterTest { filter = new ShallowEtagHeaderFilter(); } + @Test + public void isEligibleForEtag() { + MockHttpServletRequest request = new MockHttpServletRequest("GET", "/hotels"); + MockHttpServletResponse response = new MockHttpServletResponse(); + + assertTrue(filter.isEligibleForEtag(request, response, 200, new byte[0])); + assertFalse(filter.isEligibleForEtag(request, response, 300, new byte[0])); + } + @Test public void filterNoMatch() throws Exception { final MockHttpServletRequest request = new MockHttpServletRequest("GET", "/hotels");