MockMvc re-uses response instance on async dispatch

MockMvc now properly detects the presence of an AsyncContext and
re-uses the response instance used to start it.

This commit also includes a minor fix in
ResponseBodyEmitterReturnValueHandler to ensure it does not disable
ETag related content buffering for reactive return values that do not
result in streaming (e.g. single value or collections).

Issue: SPR-16067
This commit is contained in:
Rossen Stoyanchev
2017-10-17 16:57:35 -04:00
parent 94c4a7f941
commit cd634633d8
3 changed files with 91 additions and 32 deletions

View File

@@ -18,9 +18,13 @@ package org.springframework.test.web.servlet;
import java.util.ArrayList;
import java.util.List;
import javax.servlet.AsyncContext;
import javax.servlet.DispatcherType;
import javax.servlet.Filter;
import javax.servlet.ServletContext;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpServletResponseWrapper;
import org.springframework.beans.Mergeable;
import org.springframework.lang.Nullable;
@@ -135,24 +139,35 @@ public final class MockMvc {
}
MockHttpServletRequest request = requestBuilder.buildRequest(this.servletContext);
MockHttpServletResponse response = new MockHttpServletResponse();
AsyncContext asyncContext = request.getAsyncContext();
MockHttpServletResponse mockResponse;
HttpServletResponse servletResponse;
if (asyncContext != null) {
servletResponse = (HttpServletResponse) asyncContext.getResponse();
mockResponse = unwrapResponseIfNecessary(servletResponse);
}
else {
mockResponse = new MockHttpServletResponse();
servletResponse = mockResponse;
}
if (requestBuilder instanceof SmartRequestBuilder) {
request = ((SmartRequestBuilder) requestBuilder).postProcessRequest(request);
}
final MvcResult mvcResult = new DefaultMvcResult(request, response);
final MvcResult mvcResult = new DefaultMvcResult(request, mockResponse);
request.setAttribute(MVC_RESULT_ATTRIBUTE, mvcResult);
RequestAttributes previousAttributes = RequestContextHolder.getRequestAttributes();
RequestContextHolder.setRequestAttributes(new ServletRequestAttributes(request, response));
RequestContextHolder.setRequestAttributes(new ServletRequestAttributes(request, servletResponse));
MockFilterChain filterChain = new MockFilterChain(this.servlet, this.filters);
filterChain.doFilter(request, response);
filterChain.doFilter(request, servletResponse);
if (DispatcherType.ASYNC.equals(request.getDispatcherType()) &&
request.getAsyncContext() != null & !request.isAsyncStarted()) {
request.getAsyncContext().complete();
asyncContext != null & !request.isAsyncStarted()) {
asyncContext.complete();
}
applyDefaultResultActions(mvcResult);
@@ -176,6 +191,14 @@ public final class MockMvc {
};
}
private MockHttpServletResponse unwrapResponseIfNecessary(ServletResponse servletResponse) {
while (servletResponse instanceof HttpServletResponseWrapper) {
servletResponse = ((HttpServletResponseWrapper) servletResponse).getResponse();
}
Assert.isInstanceOf(MockHttpServletResponse.class, servletResponse);
return (MockHttpServletResponse) servletResponse;
}
private void applyDefaultResultActions(MvcResult mvcResult) throws Exception {