Disable ShallowEtagHeaderFilter for HTTP streaming
Issue: SPR-12960
This commit is contained in:
@@ -18,8 +18,12 @@ 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;
|
||||
import javax.servlet.ServletRequest;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
|
||||
@@ -55,11 +59,12 @@ public class ShallowEtagHeaderFilter extends OncePerRequestFilter {
|
||||
|
||||
private static final String DIRECTIVE_NO_STORE = "no-store";
|
||||
|
||||
|
||||
/** Checking for Servlet 3.0+ HttpServletResponse.getHeader(String) */
|
||||
private static final boolean responseGetHeaderAvailable =
|
||||
ClassUtils.hasMethod(HttpServletResponse.class, "getHeader", String.class);
|
||||
|
||||
private static final String STREAMING_ATTRIBUTE = ShallowEtagHeaderFilter.class.getName() + ".STREAMING";
|
||||
|
||||
|
||||
/**
|
||||
* The default value is "false" so that the filter may delay the generation of
|
||||
@@ -76,12 +81,12 @@ public class ShallowEtagHeaderFilter extends OncePerRequestFilter {
|
||||
|
||||
HttpServletResponse responseToUse = response;
|
||||
if (!isAsyncDispatch(request) && !(response instanceof ContentCachingResponseWrapper)) {
|
||||
responseToUse = new ContentCachingResponseWrapper(response);
|
||||
responseToUse = new HttpStreamingAwareContentCachingResponseWrapper(response, request);
|
||||
}
|
||||
|
||||
filterChain.doFilter(request, responseToUse);
|
||||
|
||||
if (!isAsyncStarted(request)) {
|
||||
if (!isAsyncStarted(request) && !isContentCachingDisabled(request)) {
|
||||
updateResponse(request, responseToUse);
|
||||
}
|
||||
}
|
||||
@@ -90,7 +95,6 @@ public class ShallowEtagHeaderFilter extends OncePerRequestFilter {
|
||||
ContentCachingResponseWrapper responseWrapper =
|
||||
WebUtils.getNativeResponse(response, ContentCachingResponseWrapper.class);
|
||||
Assert.notNull(responseWrapper, "ShallowEtagResponseWrapper not found");
|
||||
|
||||
HttpServletResponse rawResponse = (HttpServletResponse) responseWrapper.getResponse();
|
||||
int statusCode = responseWrapper.getStatusCode();
|
||||
|
||||
@@ -169,4 +173,48 @@ public class ShallowEtagHeaderFilter extends OncePerRequestFilter {
|
||||
return builder.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* This method can be used to disable the content caching response wrapper
|
||||
* of the ShallowEtagHeaderFilter. This can be done before the start of HTTP
|
||||
* streaming for example where the response will be written to asynchronously
|
||||
* and not in the context of a Servlet container thread.
|
||||
* @since 4.2
|
||||
*/
|
||||
public static void disableContentCaching(ServletRequest request) {
|
||||
Assert.notNull(request);
|
||||
request.setAttribute(STREAMING_ATTRIBUTE, true);
|
||||
}
|
||||
|
||||
private static boolean isContentCachingDisabled(HttpServletRequest request) {
|
||||
return (request.getAttribute(STREAMING_ATTRIBUTE) != null);
|
||||
}
|
||||
|
||||
|
||||
private static class HttpStreamingAwareContentCachingResponseWrapper extends ContentCachingResponseWrapper {
|
||||
|
||||
private final HttpServletRequest request;
|
||||
|
||||
|
||||
public HttpStreamingAwareContentCachingResponseWrapper(HttpServletResponse response,
|
||||
HttpServletRequest request) {
|
||||
|
||||
super(response);
|
||||
this.request = request;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ServletOutputStream getOutputStream() throws IOException {
|
||||
return (useRawResponse() ? getResponse().getOutputStream() : super.getOutputStream());
|
||||
}
|
||||
|
||||
@Override
|
||||
public PrintWriter getWriter() throws IOException {
|
||||
return (useRawResponse() ? getResponse().getWriter() : super.getWriter());
|
||||
}
|
||||
|
||||
private boolean useRawResponse() {
|
||||
return isContentCachingDisabled(this.request);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -91,7 +91,7 @@ public class ContentCachingResponseWrapper extends HttpServletResponseWrapper {
|
||||
}
|
||||
|
||||
@Override
|
||||
public ServletOutputStream getOutputStream() {
|
||||
public ServletOutputStream getOutputStream() throws IOException {
|
||||
return this.outputStream;
|
||||
}
|
||||
|
||||
|
||||
@@ -69,14 +69,10 @@ public class ShallowEtagHeaderFilterTests {
|
||||
MockHttpServletResponse response = new MockHttpServletResponse();
|
||||
|
||||
final byte[] responseBody = "Hello World".getBytes("UTF-8");
|
||||
FilterChain filterChain = new FilterChain() {
|
||||
@Override
|
||||
public void doFilter(ServletRequest filterRequest, ServletResponse filterResponse)
|
||||
throws IOException, ServletException {
|
||||
assertEquals("Invalid request passed", request, filterRequest);
|
||||
((HttpServletResponse) filterResponse).setStatus(HttpServletResponse.SC_OK);
|
||||
FileCopyUtils.copy(responseBody, filterResponse.getOutputStream());
|
||||
}
|
||||
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);
|
||||
|
||||
@@ -93,15 +89,11 @@ public class ShallowEtagHeaderFilterTests {
|
||||
request.addHeader("If-None-Match", etag);
|
||||
MockHttpServletResponse response = new MockHttpServletResponse();
|
||||
|
||||
FilterChain filterChain = new FilterChain() {
|
||||
@Override
|
||||
public void doFilter(ServletRequest filterRequest, ServletResponse filterResponse)
|
||||
throws IOException, ServletException {
|
||||
assertEquals("Invalid request passed", request, filterRequest);
|
||||
byte[] responseBody = "Hello World".getBytes("UTF-8");
|
||||
FileCopyUtils.copy(responseBody, filterResponse.getOutputStream());
|
||||
filterResponse.setContentLength(responseBody.length);
|
||||
}
|
||||
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);
|
||||
|
||||
@@ -118,15 +110,11 @@ public class ShallowEtagHeaderFilterTests {
|
||||
request.addHeader("If-None-Match", etag);
|
||||
MockHttpServletResponse response = new MockHttpServletResponse();
|
||||
|
||||
FilterChain filterChain = new FilterChain() {
|
||||
@Override
|
||||
public void doFilter(ServletRequest filterRequest, ServletResponse filterResponse)
|
||||
throws IOException, ServletException {
|
||||
assertEquals("Invalid request passed", request, filterRequest);
|
||||
((HttpServletResponse) filterResponse).setStatus(HttpServletResponse.SC_OK);
|
||||
String responseBody = "Hello World";
|
||||
FileCopyUtils.copy(responseBody, filterResponse.getWriter());
|
||||
}
|
||||
FilterChain filterChain = (filterRequest, filterResponse) -> {
|
||||
assertEquals("Invalid request passed", request, filterRequest);
|
||||
((HttpServletResponse) filterResponse).setStatus(HttpServletResponse.SC_OK);
|
||||
String responseBody = "Hello World";
|
||||
FileCopyUtils.copy(responseBody, filterResponse.getWriter());
|
||||
};
|
||||
filter.doFilter(request, response, filterChain);
|
||||
|
||||
@@ -136,20 +124,38 @@ public class ShallowEtagHeaderFilterTests {
|
||||
assertArrayEquals("Invalid content", new byte[0], response.getContentAsByteArray());
|
||||
}
|
||||
|
||||
// SPR-12960
|
||||
|
||||
@Test
|
||||
public void filterWriterWithDisabledCaching() 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());
|
||||
};
|
||||
|
||||
ShallowEtagHeaderFilter.disableContentCaching(request);
|
||||
this.filter.doFilter(request, response, filterChain);
|
||||
|
||||
assertEquals(200, response.getStatus());
|
||||
assertNull(response.getHeader("ETag"));
|
||||
assertArrayEquals(responseBody, response.getContentAsByteArray());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void filterSendError() throws Exception {
|
||||
final MockHttpServletRequest request = new MockHttpServletRequest("GET", "/hotels");
|
||||
MockHttpServletResponse response = new MockHttpServletResponse();
|
||||
|
||||
final byte[] responseBody = "Hello World".getBytes("UTF-8");
|
||||
FilterChain filterChain = new FilterChain() {
|
||||
@Override
|
||||
public void doFilter(ServletRequest filterRequest, ServletResponse filterResponse)
|
||||
throws IOException, ServletException {
|
||||
assertEquals("Invalid request passed", request, filterRequest);
|
||||
FileCopyUtils.copy(responseBody, filterResponse.getOutputStream());
|
||||
((HttpServletResponse) filterResponse).sendError(HttpServletResponse.SC_FORBIDDEN);
|
||||
}
|
||||
FilterChain filterChain = (filterRequest, filterResponse) -> {
|
||||
assertEquals("Invalid request passed", request, filterRequest);
|
||||
FileCopyUtils.copy(responseBody, filterResponse.getOutputStream());
|
||||
((HttpServletResponse) filterResponse).sendError(HttpServletResponse.SC_FORBIDDEN);
|
||||
};
|
||||
filter.doFilter(request, response, filterChain);
|
||||
|
||||
@@ -165,14 +171,10 @@ public class ShallowEtagHeaderFilterTests {
|
||||
MockHttpServletResponse response = new MockHttpServletResponse();
|
||||
|
||||
final byte[] responseBody = "Hello World".getBytes("UTF-8");
|
||||
FilterChain filterChain = new FilterChain() {
|
||||
@Override
|
||||
public void doFilter(ServletRequest filterRequest, ServletResponse filterResponse)
|
||||
throws IOException, ServletException {
|
||||
assertEquals("Invalid request passed", request, filterRequest);
|
||||
FileCopyUtils.copy(responseBody, filterResponse.getOutputStream());
|
||||
((HttpServletResponse) filterResponse).sendError(HttpServletResponse.SC_FORBIDDEN, "ERROR");
|
||||
}
|
||||
FilterChain filterChain = (filterRequest, filterResponse) -> {
|
||||
assertEquals("Invalid request passed", request, filterRequest);
|
||||
FileCopyUtils.copy(responseBody, filterResponse.getOutputStream());
|
||||
((HttpServletResponse) filterResponse).sendError(HttpServletResponse.SC_FORBIDDEN, "ERROR");
|
||||
};
|
||||
filter.doFilter(request, response, filterChain);
|
||||
|
||||
@@ -189,14 +191,10 @@ public class ShallowEtagHeaderFilterTests {
|
||||
MockHttpServletResponse response = new MockHttpServletResponse();
|
||||
|
||||
final byte[] responseBody = "Hello World".getBytes("UTF-8");
|
||||
FilterChain filterChain = new FilterChain() {
|
||||
@Override
|
||||
public void doFilter(ServletRequest filterRequest, ServletResponse filterResponse)
|
||||
throws IOException, ServletException {
|
||||
assertEquals("Invalid request passed", request, filterRequest);
|
||||
FileCopyUtils.copy(responseBody, filterResponse.getOutputStream());
|
||||
((HttpServletResponse) filterResponse).sendRedirect("http://www.google.com");
|
||||
}
|
||||
FilterChain filterChain = (filterRequest, filterResponse) -> {
|
||||
assertEquals("Invalid request passed", request, filterRequest);
|
||||
FileCopyUtils.copy(responseBody, filterResponse.getOutputStream());
|
||||
((HttpServletResponse) filterResponse).sendRedirect("http://www.google.com");
|
||||
};
|
||||
filter.doFilter(request, response, filterChain);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user