Browse Source

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
pull/929/head
Brian Clozel 9 years ago
parent
commit
9d9433a6eb
  1. 15
      spring-web/src/main/java/org/springframework/web/util/ContentCachingResponseWrapper.java
  2. 21
      spring-web/src/test/java/org/springframework/web/filter/ShallowEtagHeaderFilterTests.java

15
spring-web/src/main/java/org/springframework/web/util/ContentCachingResponseWrapper.java

@ -21,6 +21,7 @@ import java.io.InputStream; @@ -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 { @@ -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 { @@ -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 { @@ -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 { @@ -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();
}
}
}

21
spring-web/src/test/java/org/springframework/web/filter/ShallowEtagHeaderFilterTests.java

@ -197,4 +197,25 @@ public class ShallowEtagHeaderFilterTests { @@ -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());
}
}

Loading…
Cancel
Save