Allow multiple listeners on ResponseBodyEmitter

Prior to this commit, `ResponseBodyEmitter` woud accept a single
`Runnable` callback on each of its `onTimeout`, `onError` or
`onCompletion` methods. This would limit the developers' ability to
register multiple sets of callbacks: one for managing the publication of
streaming values, another one for managing other concerns like
keep-alive signals to maintain the connection.

This commit now allows multiple calls to `onTimeout`, `onError` and
`onCompletion` and will register all callbacks accordingly.

Closes gh-33356
This commit is contained in:
Brian Clozel
2024-09-06 15:30:18 +02:00
parent 2b6639e587
commit 761fb8f6c9
2 changed files with 80 additions and 16 deletions

View File

@@ -17,6 +17,7 @@
package org.springframework.web.servlet.mvc.method.annotation;
import java.io.IOException;
import java.util.function.Consumer;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
@@ -31,6 +32,7 @@ import static org.assertj.core.api.Assertions.assertThatIOException;
import static org.assertj.core.api.Assertions.assertThatIllegalStateException;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anySet;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.BDDMockito.willThrow;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;
@@ -41,6 +43,7 @@ import static org.mockito.Mockito.verifyNoMoreInteractions;
*
* @author Rossen Stoyanchev
* @author Tomasz Nurkiewicz
* @author Brian Clozel
*/
@ExtendWith(MockitoExtension.class)
public class ResponseBodyEmitterTests {
@@ -197,6 +200,25 @@ public class ResponseBodyEmitterTests {
verify(runnable).run();
}
@Test
void multipleOnTimeoutCallbacks() throws Exception {
this.emitter.initialize(this.handler);
ArgumentCaptor<Runnable> captor = ArgumentCaptor.forClass(Runnable.class);
verify(this.handler).onTimeout(captor.capture());
verify(this.handler).onCompletion(any());
Runnable first = mock();
Runnable second = mock();
this.emitter.onTimeout(first);
this.emitter.onTimeout(second);
assertThat(captor.getValue()).isNotNull();
captor.getValue().run();
verify(first).run();
verify(second).run();
}
@Test
void onCompletionBeforeHandlerInitialized() throws Exception {
Runnable runnable = mock();
@@ -228,4 +250,42 @@ public class ResponseBodyEmitterTests {
verify(runnable).run();
}
@Test
void multipleOnCompletionCallbacks() throws Exception {
this.emitter.initialize(this.handler);
ArgumentCaptor<Runnable> captor = ArgumentCaptor.forClass(Runnable.class);
verify(this.handler).onTimeout(any());
verify(this.handler).onCompletion(captor.capture());
Runnable first = mock();
Runnable second = mock();
this.emitter.onCompletion(first);
this.emitter.onCompletion(second);
assertThat(captor.getValue()).isNotNull();
captor.getValue().run();
verify(first).run();
verify(second).run();
}
@Test
void multipleOnErrorCallbacks() throws Exception {
this.emitter.initialize(this.handler);
ArgumentCaptor<Consumer<Throwable>> captor = ArgumentCaptor.<Consumer<Throwable>, Consumer>forClass(Consumer.class);
verify(this.handler).onError(captor.capture());
Consumer<Throwable> first = mock();
Consumer<Throwable> second = mock();
this.emitter.onError(first);
this.emitter.onError(second);
assertThat(captor.getValue()).isNotNull();
IllegalStateException illegalStateException = new IllegalStateException();
captor.getValue().accept(illegalStateException);
verify(first).accept(eq(illegalStateException));
verify(second).accept(eq(illegalStateException));
}
}