|
|
|
@ -15,7 +15,6 @@ |
|
|
|
*/ |
|
|
|
*/ |
|
|
|
package org.springframework.web.servlet.mvc.method.annotation; |
|
|
|
package org.springframework.web.servlet.mvc.method.annotation; |
|
|
|
|
|
|
|
|
|
|
|
import java.lang.reflect.Method; |
|
|
|
|
|
|
|
import java.util.Arrays; |
|
|
|
import java.util.Arrays; |
|
|
|
import java.util.Collections; |
|
|
|
import java.util.Collections; |
|
|
|
import java.util.List; |
|
|
|
import java.util.List; |
|
|
|
@ -25,9 +24,6 @@ import org.junit.Before; |
|
|
|
import org.junit.Test; |
|
|
|
import org.junit.Test; |
|
|
|
|
|
|
|
|
|
|
|
import org.springframework.core.MethodParameter; |
|
|
|
import org.springframework.core.MethodParameter; |
|
|
|
import org.springframework.core.ReactiveAdapterRegistry; |
|
|
|
|
|
|
|
import org.springframework.core.task.SyncTaskExecutor; |
|
|
|
|
|
|
|
import org.springframework.core.task.TaskExecutor; |
|
|
|
|
|
|
|
import org.springframework.http.ResponseEntity; |
|
|
|
import org.springframework.http.ResponseEntity; |
|
|
|
import org.springframework.http.converter.HttpMessageConverter; |
|
|
|
import org.springframework.http.converter.HttpMessageConverter; |
|
|
|
import org.springframework.http.converter.StringHttpMessageConverter; |
|
|
|
import org.springframework.http.converter.StringHttpMessageConverter; |
|
|
|
@ -35,7 +31,6 @@ import org.springframework.http.converter.json.MappingJackson2HttpMessageConvert |
|
|
|
import org.springframework.mock.web.test.MockAsyncContext; |
|
|
|
import org.springframework.mock.web.test.MockAsyncContext; |
|
|
|
import org.springframework.mock.web.test.MockHttpServletRequest; |
|
|
|
import org.springframework.mock.web.test.MockHttpServletRequest; |
|
|
|
import org.springframework.mock.web.test.MockHttpServletResponse; |
|
|
|
import org.springframework.mock.web.test.MockHttpServletResponse; |
|
|
|
import org.springframework.web.accept.ContentNegotiationManager; |
|
|
|
|
|
|
|
import org.springframework.web.context.request.NativeWebRequest; |
|
|
|
import org.springframework.web.context.request.NativeWebRequest; |
|
|
|
import org.springframework.web.context.request.ServletWebRequest; |
|
|
|
import org.springframework.web.context.request.ServletWebRequest; |
|
|
|
import org.springframework.web.context.request.async.AsyncWebRequest; |
|
|
|
import org.springframework.web.context.request.async.AsyncWebRequest; |
|
|
|
@ -43,12 +38,20 @@ import org.springframework.web.context.request.async.StandardServletAsyncWebRequ |
|
|
|
import org.springframework.web.context.request.async.WebAsyncUtils; |
|
|
|
import org.springframework.web.context.request.async.WebAsyncUtils; |
|
|
|
import org.springframework.web.method.support.ModelAndViewContainer; |
|
|
|
import org.springframework.web.method.support.ModelAndViewContainer; |
|
|
|
|
|
|
|
|
|
|
|
import static org.junit.Assert.*; |
|
|
|
import static org.junit.Assert.assertEquals; |
|
|
|
import static org.mockito.Mockito.*; |
|
|
|
import static org.junit.Assert.assertFalse; |
|
|
|
|
|
|
|
import static org.junit.Assert.assertNotNull; |
|
|
|
|
|
|
|
import static org.junit.Assert.assertNull; |
|
|
|
|
|
|
|
import static org.junit.Assert.assertTrue; |
|
|
|
|
|
|
|
import static org.mockito.Mockito.any; |
|
|
|
|
|
|
|
import static org.mockito.Mockito.mock; |
|
|
|
|
|
|
|
import static org.mockito.Mockito.times; |
|
|
|
|
|
|
|
import static org.mockito.Mockito.verify; |
|
|
|
|
|
|
|
import static org.springframework.core.ResolvableType.forClassWithGenerics; |
|
|
|
|
|
|
|
import static org.springframework.web.method.ResolvableMethod.on; |
|
|
|
|
|
|
|
|
|
|
|
/** |
|
|
|
/** |
|
|
|
* Unit tests for ResponseBodyEmitterReturnValueHandler. |
|
|
|
* Unit tests for ResponseBodyEmitterReturnValueHandler. |
|
|
|
* |
|
|
|
|
|
|
|
* @author Rossen Stoyanchev |
|
|
|
* @author Rossen Stoyanchev |
|
|
|
*/ |
|
|
|
*/ |
|
|
|
public class ResponseBodyEmitterReturnValueHandlerTests { |
|
|
|
public class ResponseBodyEmitterReturnValueHandlerTests { |
|
|
|
@ -61,9 +64,12 @@ public class ResponseBodyEmitterReturnValueHandlerTests { |
|
|
|
|
|
|
|
|
|
|
|
private NativeWebRequest webRequest; |
|
|
|
private NativeWebRequest webRequest; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
private final ModelAndViewContainer mavContainer = new ModelAndViewContainer(); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@Before |
|
|
|
@Before |
|
|
|
public void setup() throws Exception { |
|
|
|
public void setup() throws Exception { |
|
|
|
|
|
|
|
|
|
|
|
List<HttpMessageConverter<?>> converters = Arrays.asList( |
|
|
|
List<HttpMessageConverter<?>> converters = Arrays.asList( |
|
|
|
new StringHttpMessageConverter(), new MappingJackson2HttpMessageConverter()); |
|
|
|
new StringHttpMessageConverter(), new MappingJackson2HttpMessageConverter()); |
|
|
|
|
|
|
|
|
|
|
|
@ -79,20 +85,37 @@ public class ResponseBodyEmitterReturnValueHandlerTests { |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@Test |
|
|
|
@Test |
|
|
|
public void supportsReturnType() throws Exception { |
|
|
|
public void supportsReturnTypes() throws Exception { |
|
|
|
assertTrue(this.handler.supportsReturnType(returnType("handle"))); |
|
|
|
|
|
|
|
assertTrue(this.handler.supportsReturnType(returnType("handleSse"))); |
|
|
|
assertTrue(this.handler.supportsReturnType( |
|
|
|
assertTrue(this.handler.supportsReturnType(returnType("handleResponseEntity"))); |
|
|
|
on(TestController.class).resolveReturnType(ResponseBodyEmitter.class))); |
|
|
|
assertFalse(this.handler.supportsReturnType(returnType("handleResponseEntityString"))); |
|
|
|
|
|
|
|
assertFalse(this.handler.supportsReturnType(returnType("handleResponseEntityParameterized"))); |
|
|
|
assertTrue(this.handler.supportsReturnType( |
|
|
|
assertFalse(this.handler.supportsReturnType(returnType("handleRawResponseEntity"))); |
|
|
|
on(TestController.class).resolveReturnType(SseEmitter.class))); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
assertTrue(this.handler.supportsReturnType( |
|
|
|
|
|
|
|
on(TestController.class).resolveReturnType(ResponseEntity.class, ResponseBodyEmitter.class))); |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@Test |
|
|
|
|
|
|
|
public void doesNotSupportReturnTypes() throws Exception { |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
assertFalse(this.handler.supportsReturnType( |
|
|
|
|
|
|
|
on(TestController.class).resolveReturnType(ResponseEntity.class, String.class))); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
assertFalse(this.handler.supportsReturnType(on(TestController.class) |
|
|
|
|
|
|
|
.resolveReturnType(forClassWithGenerics(ResponseEntity.class, |
|
|
|
|
|
|
|
forClassWithGenerics(AtomicReference.class, String.class))))); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
assertFalse(this.handler.supportsReturnType( |
|
|
|
|
|
|
|
on(TestController.class).resolveReturnType(ResponseEntity.class))); |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
@Test |
|
|
|
@Test |
|
|
|
public void responseBodyEmitter() throws Exception { |
|
|
|
public void responseBodyEmitter() throws Exception { |
|
|
|
MethodParameter returnType = returnType("handle"); |
|
|
|
MethodParameter type = on(TestController.class).resolveReturnType(ResponseBodyEmitter.class); |
|
|
|
ResponseBodyEmitter emitter = new ResponseBodyEmitter(); |
|
|
|
ResponseBodyEmitter emitter = new ResponseBodyEmitter(); |
|
|
|
handleReturnValue(emitter, returnType); |
|
|
|
this.handler.handleReturnValue(emitter, type, this.mavContainer, this.webRequest); |
|
|
|
|
|
|
|
|
|
|
|
assertTrue(this.request.isAsyncStarted()); |
|
|
|
assertTrue(this.request.isAsyncStarted()); |
|
|
|
assertEquals("", this.response.getContentAsString()); |
|
|
|
assertEquals("", this.response.getContentAsString()); |
|
|
|
@ -114,8 +137,7 @@ public class ResponseBodyEmitterReturnValueHandlerTests { |
|
|
|
|
|
|
|
|
|
|
|
assertEquals("{\"id\":1,\"name\":\"Joe\"}\n" + |
|
|
|
assertEquals("{\"id\":1,\"name\":\"Joe\"}\n" + |
|
|
|
"{\"id\":2,\"name\":\"John\"}\n" + |
|
|
|
"{\"id\":2,\"name\":\"John\"}\n" + |
|
|
|
"{\"id\":3,\"name\":\"Jason\"}", |
|
|
|
"{\"id\":3,\"name\":\"Jason\"}", this.response.getContentAsString()); |
|
|
|
this.response.getContentAsString()); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
MockAsyncContext asyncContext = (MockAsyncContext) this.request.getAsyncContext(); |
|
|
|
MockAsyncContext asyncContext = (MockAsyncContext) this.request.getAsyncContext(); |
|
|
|
assertNull(asyncContext.getDispatchedPath()); |
|
|
|
assertNull(asyncContext.getDispatchedPath()); |
|
|
|
@ -125,7 +147,7 @@ public class ResponseBodyEmitterReturnValueHandlerTests { |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
@Test |
|
|
|
@Test |
|
|
|
public void timeoutValueAndCallback() throws Exception { |
|
|
|
public void responseBodyEmitterWithTimeoutValue() throws Exception { |
|
|
|
|
|
|
|
|
|
|
|
AsyncWebRequest asyncWebRequest = mock(AsyncWebRequest.class); |
|
|
|
AsyncWebRequest asyncWebRequest = mock(AsyncWebRequest.class); |
|
|
|
WebAsyncUtils.getAsyncManager(this.request).setAsyncWebRequest(asyncWebRequest); |
|
|
|
WebAsyncUtils.getAsyncManager(this.request).setAsyncWebRequest(asyncWebRequest); |
|
|
|
@ -134,8 +156,8 @@ public class ResponseBodyEmitterReturnValueHandlerTests { |
|
|
|
emitter.onTimeout(mock(Runnable.class)); |
|
|
|
emitter.onTimeout(mock(Runnable.class)); |
|
|
|
emitter.onCompletion(mock(Runnable.class)); |
|
|
|
emitter.onCompletion(mock(Runnable.class)); |
|
|
|
|
|
|
|
|
|
|
|
MethodParameter returnType = returnType("handle"); |
|
|
|
MethodParameter type = on(TestController.class).resolveReturnType(ResponseBodyEmitter.class); |
|
|
|
handleReturnValue(emitter, returnType); |
|
|
|
this.handler.handleReturnValue(emitter, type, this.mavContainer, this.webRequest); |
|
|
|
|
|
|
|
|
|
|
|
verify(asyncWebRequest).setTimeout(19000L); |
|
|
|
verify(asyncWebRequest).setTimeout(19000L); |
|
|
|
verify(asyncWebRequest).addTimeoutHandler(any(Runnable.class)); |
|
|
|
verify(asyncWebRequest).addTimeoutHandler(any(Runnable.class)); |
|
|
|
@ -145,9 +167,9 @@ public class ResponseBodyEmitterReturnValueHandlerTests { |
|
|
|
|
|
|
|
|
|
|
|
@Test |
|
|
|
@Test |
|
|
|
public void sseEmitter() throws Exception { |
|
|
|
public void sseEmitter() throws Exception { |
|
|
|
MethodParameter returnType = returnType("handleSse"); |
|
|
|
MethodParameter type = on(TestController.class).resolveReturnType(SseEmitter.class); |
|
|
|
SseEmitter emitter = new SseEmitter(); |
|
|
|
SseEmitter emitter = new SseEmitter(); |
|
|
|
handleReturnValue(emitter, returnType); |
|
|
|
this.handler.handleReturnValue(emitter, type, this.mavContainer, this.webRequest); |
|
|
|
|
|
|
|
|
|
|
|
assertTrue(this.request.isAsyncStarted()); |
|
|
|
assertTrue(this.request.isAsyncStarted()); |
|
|
|
assertEquals(200, this.response.getStatus()); |
|
|
|
assertEquals(200, this.response.getStatus()); |
|
|
|
@ -170,15 +192,14 @@ public class ResponseBodyEmitterReturnValueHandlerTests { |
|
|
|
"retry:5000\n" + |
|
|
|
"retry:5000\n" + |
|
|
|
"data:{\"id\":1,\"name\":\"Joe\"}\n" + |
|
|
|
"data:{\"id\":1,\"name\":\"Joe\"}\n" + |
|
|
|
"data:{\"id\":2,\"name\":\"John\"}\n" + |
|
|
|
"data:{\"id\":2,\"name\":\"John\"}\n" + |
|
|
|
"\n", |
|
|
|
"\n", this.response.getContentAsString()); |
|
|
|
this.response.getContentAsString()); |
|
|
|
|
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
@Test |
|
|
|
@Test |
|
|
|
public void responseEntitySse() throws Exception { |
|
|
|
public void responseEntitySse() throws Exception { |
|
|
|
MethodParameter returnType = returnType("handleResponseEntitySse"); |
|
|
|
MethodParameter type = on(TestController.class).resolveReturnType(ResponseEntity.class, SseEmitter.class); |
|
|
|
ResponseEntity<SseEmitter> entity = ResponseEntity.ok().header("foo", "bar").body(new SseEmitter()); |
|
|
|
ResponseEntity<SseEmitter> entity = ResponseEntity.ok().header("foo", "bar").body(new SseEmitter()); |
|
|
|
handleReturnValue(entity, returnType); |
|
|
|
this.handler.handleReturnValue(entity, type, this.mavContainer, this.webRequest); |
|
|
|
|
|
|
|
|
|
|
|
assertTrue(this.request.isAsyncStarted()); |
|
|
|
assertTrue(this.request.isAsyncStarted()); |
|
|
|
assertEquals(200, this.response.getStatus()); |
|
|
|
assertEquals(200, this.response.getStatus()); |
|
|
|
@ -188,9 +209,9 @@ public class ResponseBodyEmitterReturnValueHandlerTests { |
|
|
|
|
|
|
|
|
|
|
|
@Test |
|
|
|
@Test |
|
|
|
public void responseEntitySseNoContent() throws Exception { |
|
|
|
public void responseEntitySseNoContent() throws Exception { |
|
|
|
MethodParameter returnType = returnType("handleResponseEntitySse"); |
|
|
|
MethodParameter type = on(TestController.class).resolveReturnType(ResponseEntity.class, SseEmitter.class); |
|
|
|
ResponseEntity<?> entity = ResponseEntity.noContent().header("foo", "bar").build(); |
|
|
|
ResponseEntity<?> entity = ResponseEntity.noContent().header("foo", "bar").build(); |
|
|
|
handleReturnValue(entity, returnType); |
|
|
|
this.handler.handleReturnValue(entity, type, this.mavContainer, this.webRequest); |
|
|
|
|
|
|
|
|
|
|
|
assertFalse(this.request.isAsyncStarted()); |
|
|
|
assertFalse(this.request.isAsyncStarted()); |
|
|
|
assertEquals(204, this.response.getStatus()); |
|
|
|
assertEquals(204, this.response.getStatus()); |
|
|
|
@ -198,47 +219,23 @@ public class ResponseBodyEmitterReturnValueHandlerTests { |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
private void handleReturnValue(Object returnValue, MethodParameter returnType) throws Exception { |
|
|
|
|
|
|
|
ModelAndViewContainer mavContainer = new ModelAndViewContainer(); |
|
|
|
|
|
|
|
this.handler.handleReturnValue(returnValue, returnType, mavContainer, this.webRequest); |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
private MethodParameter returnType(String methodName) throws NoSuchMethodException { |
|
|
|
|
|
|
|
Method method = TestController.class.getDeclaredMethod(methodName); |
|
|
|
|
|
|
|
return new MethodParameter(method, -1); |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@SuppressWarnings("unused") |
|
|
|
@SuppressWarnings("unused") |
|
|
|
private static class TestController { |
|
|
|
private static class TestController { |
|
|
|
|
|
|
|
|
|
|
|
private ResponseBodyEmitter handle() { |
|
|
|
private ResponseBodyEmitter h1() { return null; } |
|
|
|
return null; |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
private ResponseEntity<ResponseBodyEmitter> handleResponseEntity() { |
|
|
|
private ResponseEntity<ResponseBodyEmitter> h2() { return null; } |
|
|
|
return null; |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
private SseEmitter handleSse() { |
|
|
|
private SseEmitter h3() { return null; } |
|
|
|
return null; |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
private ResponseEntity<SseEmitter> handleResponseEntitySse() { |
|
|
|
private ResponseEntity<SseEmitter> h4() { return null; } |
|
|
|
return null; |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
private ResponseEntity<String> handleResponseEntityString() { |
|
|
|
private ResponseEntity<String> h5() { return null; } |
|
|
|
return null; |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
private ResponseEntity<AtomicReference<String>> handleResponseEntityParameterized() { |
|
|
|
private ResponseEntity<AtomicReference<String>> h6() { return null; } |
|
|
|
return null; |
|
|
|
|
|
|
|
} |
|
|
|
private ResponseEntity h7() { return null; } |
|
|
|
|
|
|
|
|
|
|
|
private ResponseEntity handleRawResponseEntity() { |
|
|
|
|
|
|
|
return null; |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|