Skip to content

Commit 713fc5c

Browse files
committed
Add onTimeout() and onCompletion() callbacks to ResponseBodyEmitter
Issue: SPR-12939
1 parent 5cbe4b9 commit 713fc5c

File tree

4 files changed

+94
-4
lines changed

4 files changed

+94
-4
lines changed

spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/annotation/ResponseBodyEmitter.java

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,10 @@ public class ResponseBodyEmitter {
6767

6868
private Throwable failure;
6969

70+
private Runnable timeoutCallback;
71+
72+
private Runnable completionCallback;
73+
7074

7175
/**
7276
* Invoked after the response is updated with the status code and headers,
@@ -96,6 +100,12 @@ void initialize(Handler handler) throws IOException {
96100
this.handler.complete();
97101
}
98102
}
103+
if (this.timeoutCallback != null) {
104+
this.handler.onTimeout(this.timeoutCallback);
105+
}
106+
if (this.completionCallback != null) {
107+
this.handler.onCompletion(this.completionCallback);
108+
}
99109
}
100110
}
101111

@@ -179,6 +189,34 @@ public void completeWithError(Throwable ex) {
179189
}
180190
}
181191

192+
/**
193+
* Register code to invoke when the async request times out. This method is
194+
* called from a container thread when an async request times out.
195+
*/
196+
public void onTimeout(Runnable callback) {
197+
synchronized (this) {
198+
this.timeoutCallback = callback;
199+
if (this.handler != null) {
200+
this.handler.onTimeout(callback);
201+
}
202+
}
203+
}
204+
205+
/**
206+
* Register code to invoke when the async request completes. This method is
207+
* called from a container thread when an async request completed for any
208+
* reason including timeout and network error. This method is useful for
209+
* detecting that a {@code ResponseBodyEmitter} instance is no longer usable.
210+
*/
211+
public void onCompletion(Runnable callback) {
212+
synchronized (this) {
213+
this.completionCallback = callback;
214+
if (this.handler != null) {
215+
this.handler.onCompletion(callback);
216+
}
217+
}
218+
}
219+
182220

183221
/**
184222
* Handle sent objects and complete request processing.
@@ -190,6 +228,10 @@ interface Handler {
190228
void complete();
191229

192230
void completeWithError(Throwable failure);
231+
232+
void onTimeout(Runnable callback);
233+
234+
void onCompletion(Runnable callback);
193235
}
194236

195237
}

spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/annotation/ResponseBodyEmitterReturnValueHandler.java

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -154,6 +154,16 @@ public void complete() {
154154
public void completeWithError(Throwable failure) {
155155
this.deferredResult.setErrorResult(failure);
156156
}
157+
158+
@Override
159+
public void onTimeout(Runnable callback) {
160+
this.deferredResult.onTimeout(callback);
161+
}
162+
163+
@Override
164+
public void onCompletion(Runnable callback) {
165+
this.deferredResult.onCompletion(callback);
166+
}
157167
}
158168

159169

spring-webmvc/src/test/java/org/springframework/web/servlet/mvc/method/annotation/ResponseBodyEmitterTests.java

Lines changed: 34 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -20,15 +20,13 @@
2020
import org.junit.Before;
2121
import org.junit.Test;
2222
import org.mockito.Mock;
23+
import static org.mockito.Mockito.*;
24+
import static org.mockito.Mockito.verify;
2325
import org.mockito.MockitoAnnotations;
2426

2527
import org.springframework.http.MediaType;
2628

2729
import static org.junit.Assert.fail;
28-
import static org.mockito.Mockito.doThrow;
29-
import static org.mockito.Mockito.verify;
30-
import static org.mockito.Mockito.verifyNoMoreInteractions;
31-
3230

3331
/**
3432
* Unit tests for {@link ResponseBodyEmitter}.
@@ -134,4 +132,36 @@ public void sendWithError() throws Exception {
134132
verifyNoMoreInteractions(this.handler);
135133
}
136134

135+
@Test
136+
public void onTimeoutBeforeHandlerInitialized() throws Exception {
137+
Runnable runnable = mock(Runnable.class);
138+
this.emitter.onTimeout(runnable);
139+
this.emitter.initialize(this.handler);
140+
verify(this.handler).onTimeout(runnable);
141+
}
142+
143+
@Test
144+
public void onTimeoutAfterHandlerInitialized() throws Exception {
145+
Runnable runnable = mock(Runnable.class);
146+
this.emitter.initialize(this.handler);
147+
this.emitter.onTimeout(runnable);
148+
verify(this.handler).onTimeout(runnable);
149+
}
150+
151+
@Test
152+
public void onCompletionBeforeHandlerInitialized() throws Exception {
153+
Runnable runnable = mock(Runnable.class);
154+
this.emitter.onCompletion(runnable);
155+
this.emitter.initialize(this.handler);
156+
verify(this.handler).onCompletion(runnable);
157+
}
158+
159+
@Test
160+
public void onCompletionAfterHandlerInitialized() throws Exception {
161+
Runnable runnable = mock(Runnable.class);
162+
this.emitter.initialize(this.handler);
163+
this.emitter.onCompletion(runnable);
164+
verify(this.handler).onCompletion(runnable);
165+
}
166+
137167
}

spring-webmvc/src/test/java/org/springframework/web/servlet/mvc/method/annotation/SseEmitterTests.java

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -147,6 +147,14 @@ public void complete() {
147147
@Override
148148
public void completeWithError(Throwable failure) {
149149
}
150+
151+
@Override
152+
public void onTimeout(Runnable callback) {
153+
}
154+
155+
@Override
156+
public void onCompletion(Runnable callback) {
157+
}
150158
}
151159

152160
}

0 commit comments

Comments
 (0)