From 9d9433a6eb340f6c2a235588b9be5c5249de8d2f Mon Sep 17 00:00:00 2001 From: Brian Clozel Date: Tue, 24 Nov 2015 17:19:16 +0100 Subject: [PATCH] Flush of underlying response in ContentCachingResponseWrapper Prior to this commit, when adding a ShallowEtagHeaderFilter to an application, the ServletResponse would be wrapped by a ContentCachingResponseWrapper. When any part of the Spring infrastructure calls `flushBuffer` on the wrapped response, the call is delegated to the actual response, which is committed. It's not possible to alter the response (headers, content) anymore - the ETag filter can't act. This change prevents the `flushBuffer` call to be delegated and only commits the underlying response once the cached content is copied to the actual response stream. Issue: SPR-13717 --- .../util/ContentCachingResponseWrapper.java | 15 ++++++++++--- .../filter/ShallowEtagHeaderFilterTests.java | 21 +++++++++++++++++++ 2 files changed, 33 insertions(+), 3 deletions(-) diff --git a/spring-web/src/main/java/org/springframework/web/util/ContentCachingResponseWrapper.java b/spring-web/src/main/java/org/springframework/web/util/ContentCachingResponseWrapper.java index ee0643f82d..7bc239494f 100644 --- a/spring-web/src/main/java/org/springframework/web/util/ContentCachingResponseWrapper.java +++ b/spring-web/src/main/java/org/springframework/web/util/ContentCachingResponseWrapper.java @@ -21,6 +21,7 @@ import java.io.InputStream; import java.io.OutputStreamWriter; import java.io.PrintWriter; import java.io.UnsupportedEncodingException; + import javax.servlet.ServletOutputStream; import javax.servlet.http.HttpServletResponse; import javax.servlet.http.HttpServletResponseWrapper; @@ -121,6 +122,11 @@ public class ContentCachingResponseWrapper extends HttpServletResponseWrapper { return this.writer; } + @Override + public void flushBuffer() throws IOException { + // do not flush the underlying response as the content as not been copied to it yet + } + @Override public void setContentLength(int len) { if (len > this.content.size()) { @@ -178,7 +184,7 @@ public class ContentCachingResponseWrapper extends HttpServletResponseWrapper { * Return an {@link InputStream} to the cached content. * @since 4.2 */ - public InputStream getContentInputStream(){ + public InputStream getContentInputStream() { return this.content.getInputStream(); } @@ -186,7 +192,7 @@ public class ContentCachingResponseWrapper extends HttpServletResponseWrapper { * Return the current size of the cached content. * @since 4.2 */ - public int getContentSize(){ + public int getContentSize() { return this.content.size(); } @@ -207,12 +213,15 @@ public class ContentCachingResponseWrapper extends HttpServletResponseWrapper { protected void copyBodyToResponse(boolean complete) throws IOException { if (this.content.size() > 0) { HttpServletResponse rawResponse = (HttpServletResponse) getResponse(); - if ((complete || this.contentLength != null) && !rawResponse.isCommitted()){ + if ((complete || this.contentLength != null) && !rawResponse.isCommitted()) { rawResponse.setContentLength(complete ? this.content.size() : this.contentLength); this.contentLength = null; } this.content.writeTo(rawResponse.getOutputStream()); this.content.reset(); + if (complete) { + super.flushBuffer(); + } } } 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 c6428a7752..0c5af485eb 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 @@ -197,4 +197,25 @@ public class ShallowEtagHeaderFilterTests { assertEquals("Invalid redirect URL", "http://www.google.com", response.getRedirectedUrl()); } + // SPR-13717 + @Test + public void filterFlushResponse() throws Exception { + 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()); + filterResponse.flushBuffer(); + }; + filter.doFilter(request, response, filterChain); + + assertEquals("Invalid status", 200, response.getStatus()); + assertEquals("Invalid ETag header", "\"0b10a8db164e0754105b7a99be72e3fe5\"", response.getHeader("ETag")); + assertTrue("Invalid Content-Length header", response.getContentLength() > 0); + assertArrayEquals("Invalid content", responseBody, response.getContentAsByteArray()); + } + }