diff --git a/spring-web/src/main/java/org/springframework/web/filter/ShallowEtagHeaderFilter.java b/spring-web/src/main/java/org/springframework/web/filter/ShallowEtagHeaderFilter.java index c5a89c8a742..c94791a5763 100644 --- a/spring-web/src/main/java/org/springframework/web/filter/ShallowEtagHeaderFilter.java +++ b/spring-web/src/main/java/org/springframework/web/filter/ShallowEtagHeaderFilter.java @@ -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. + *
Should be configured using an {@code 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();
diff --git a/spring-web/src/test/java/org/springframework/web/filter/ShallowEtagHeaderFilterTests.java b/spring-web/src/test/java/org/springframework/web/filter/ShallowEtagHeaderFilterTests.java
index 0c5af485eb0..893501b23ee 100644
--- a/spring-web/src/test/java/org/springframework/web/filter/ShallowEtagHeaderFilterTests.java
+++ b/spring-web/src/test/java/org/springframework/web/filter/ShallowEtagHeaderFilterTests.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2015 the original author or authors.
+ * Copyright 2002-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -73,6 +73,26 @@ public class ShallowEtagHeaderFilterTests {
assertArrayEquals("Invalid content", responseBody, response.getContentAsByteArray());
}
+ @Test
+ public void filterNoMatchWeakETag() throws Exception {
+ this.filter.setWriteWeakETag(true);
+ final MockHttpServletRequest request = new MockHttpServletRequest("GET", "/hotels");
+ MockHttpServletResponse response = new MockHttpServletResponse();
+
+ final byte[] responseBody = "Hello World".getBytes("UTF-8");
+ FilterChain filterChain = (filterRequest, filterResponse) -> {
+ assertEquals("Invalid request passed", request, filterRequest);
+ ((HttpServletResponse) filterResponse).setStatus(HttpServletResponse.SC_OK);
+ FileCopyUtils.copy(responseBody, filterResponse.getOutputStream());
+ };
+ filter.doFilter(request, response, filterChain);
+
+ assertEquals("Invalid status", 200, response.getStatus());
+ assertEquals("Invalid ETag header", "W/\"0b10a8db164e0754105b7a99be72e3fe5\"", response.getHeader("ETag"));
+ assertTrue("Invalid Content-Length header", response.getContentLength() > 0);
+ assertArrayEquals("Invalid content", responseBody, response.getContentAsByteArray());
+ }
+
@Test
public void filterMatch() throws Exception {
final MockHttpServletRequest request = new MockHttpServletRequest("GET", "/hotels");
@@ -94,6 +114,27 @@ public class ShallowEtagHeaderFilterTests {
assertArrayEquals("Invalid content", new byte[0], response.getContentAsByteArray());
}
+ @Test
+ public void filterMatchWeakEtag() throws Exception {
+ final MockHttpServletRequest request = new MockHttpServletRequest("GET", "/hotels");
+ String etag = "\"0b10a8db164e0754105b7a99be72e3fe5\"";
+ request.addHeader("If-None-Match", "W/" + etag);
+ MockHttpServletResponse response = new MockHttpServletResponse();
+
+ FilterChain filterChain = (filterRequest, filterResponse) -> {
+ assertEquals("Invalid request passed", request, filterRequest);
+ byte[] responseBody = "Hello World".getBytes("UTF-8");
+ FileCopyUtils.copy(responseBody, filterResponse.getOutputStream());
+ filterResponse.setContentLength(responseBody.length);
+ };
+ filter.doFilter(request, response, filterChain);
+
+ assertEquals("Invalid status", 304, response.getStatus());
+ assertEquals("Invalid ETag header", "\"0b10a8db164e0754105b7a99be72e3fe5\"", response.getHeader("ETag"));
+ assertFalse("Response has Content-Length header", response.containsHeader("Content-Length"));
+ assertArrayEquals("Invalid content", new byte[0], response.getContentAsByteArray());
+ }
+
@Test
public void filterWriter() throws Exception {
final MockHttpServletRequest request = new MockHttpServletRequest("GET", "/hotels");
diff --git a/src/asciidoc/web-mvc.adoc b/src/asciidoc/web-mvc.adoc
index cdc952f65fc..2f82b89af83 100644
--- a/src/asciidoc/web-mvc.adoc
+++ b/src/asciidoc/web-mvc.adoc
@@ -4302,7 +4302,6 @@ responsible for this, along with conditional headers such as `'Last-Modified'` a
The `'Cache-Control'` HTTP response header advises private caches (e.g. browsers) and
public caches (e.g. proxies) on how they can cache HTTP responses for further reuse.
-mvc-config-static-resources
An http://en.wikipedia.org/wiki/HTTP_ETag[ETag] (entity tag) is an HTTP response header
returned by an HTTP/1.1 compliant web server used to determine change in content at a
given URL. It can be considered to be the more sophisticated successor to the
@@ -4473,14 +4472,15 @@ ETags, more about that later).The filter caches the content of the rendered JSP
other content), generates an MD5 hash over that, and returns that as an ETag header in
the response. The next time a client sends a request for the same resource, it uses that
hash as the `If-None-Match` value. The filter detects this, renders the view again, and
-compares the two hashes. If they are equal, a `304` is returned. This filter will not
-save processing power, as the view is still rendered. The only thing it saves is
-bandwidth, as the rendered response is not sent back over the wire.
+compares the two hashes. If they are equal, a `304` is returned.
Note that this strategy saves network bandwidth but not CPU, as the full response must be
computed for each request. Other strategies at the controller level (described above) can
save network bandwidth and avoid computation.
-mvc-config-static-resources
+
+This filter has a `writeWeakETag` parameter that configures the filter to write Weak ETags,
+like this: `W/"02a2d595e6ed9a0b24f027f2b63b134d6"`, as defined in
+https://tools.ietf.org/html/rfc7232#section-2.3[RFC 7232 Section 2.3].
You configure the `ShallowEtagHeaderFilter` in `web.xml`:
@@ -4490,6 +4490,12 @@ You configure the `ShallowEtagHeaderFilter` in `web.xml`: