Skip to content

Commit 9d9433a

Browse files
committed
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
1 parent bf1afdf commit 9d9433a

File tree

2 files changed

+33
-3
lines changed

2 files changed

+33
-3
lines changed

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

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
import java.io.OutputStreamWriter;
2222
import java.io.PrintWriter;
2323
import java.io.UnsupportedEncodingException;
24+
2425
import javax.servlet.ServletOutputStream;
2526
import javax.servlet.http.HttpServletResponse;
2627
import javax.servlet.http.HttpServletResponseWrapper;
@@ -121,6 +122,11 @@ public PrintWriter getWriter() throws IOException {
121122
return this.writer;
122123
}
123124

125+
@Override
126+
public void flushBuffer() throws IOException {
127+
// do not flush the underlying response as the content as not been copied to it yet
128+
}
129+
124130
@Override
125131
public void setContentLength(int len) {
126132
if (len > this.content.size()) {
@@ -178,15 +184,15 @@ public byte[] getContentAsByteArray() {
178184
* Return an {@link InputStream} to the cached content.
179185
* @since 4.2
180186
*/
181-
public InputStream getContentInputStream(){
187+
public InputStream getContentInputStream() {
182188
return this.content.getInputStream();
183189
}
184190

185191
/**
186192
* Return the current size of the cached content.
187193
* @since 4.2
188194
*/
189-
public int getContentSize(){
195+
public int getContentSize() {
190196
return this.content.size();
191197
}
192198

@@ -207,12 +213,15 @@ public void copyBodyToResponse() throws IOException {
207213
protected void copyBodyToResponse(boolean complete) throws IOException {
208214
if (this.content.size() > 0) {
209215
HttpServletResponse rawResponse = (HttpServletResponse) getResponse();
210-
if ((complete || this.contentLength != null) && !rawResponse.isCommitted()){
216+
if ((complete || this.contentLength != null) && !rawResponse.isCommitted()) {
211217
rawResponse.setContentLength(complete ? this.content.size() : this.contentLength);
212218
this.contentLength = null;
213219
}
214220
this.content.writeTo(rawResponse.getOutputStream());
215221
this.content.reset();
222+
if (complete) {
223+
super.flushBuffer();
224+
}
216225
}
217226
}
218227

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

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -197,4 +197,25 @@ public void filterSendRedirect() throws Exception {
197197
assertEquals("Invalid redirect URL", "http://www.google.com", response.getRedirectedUrl());
198198
}
199199

200+
// SPR-13717
201+
@Test
202+
public void filterFlushResponse() throws Exception {
203+
final MockHttpServletRequest request = new MockHttpServletRequest("GET", "/hotels");
204+
MockHttpServletResponse response = new MockHttpServletResponse();
205+
206+
final byte[] responseBody = "Hello World".getBytes("UTF-8");
207+
FilterChain filterChain = (filterRequest, filterResponse) -> {
208+
assertEquals("Invalid request passed", request, filterRequest);
209+
((HttpServletResponse) filterResponse).setStatus(HttpServletResponse.SC_OK);
210+
FileCopyUtils.copy(responseBody, filterResponse.getOutputStream());
211+
filterResponse.flushBuffer();
212+
};
213+
filter.doFilter(request, response, filterChain);
214+
215+
assertEquals("Invalid status", 200, response.getStatus());
216+
assertEquals("Invalid ETag header", "\"0b10a8db164e0754105b7a99be72e3fe5\"", response.getHeader("ETag"));
217+
assertTrue("Invalid Content-Length header", response.getContentLength() > 0);
218+
assertArrayEquals("Invalid content", responseBody, response.getContentAsByteArray());
219+
}
220+
200221
}

0 commit comments

Comments
 (0)