Skip to content

Commit db3d537

Browse files
committed
Protect SseEmitter against early I/O errors
Closes gh-25442
1 parent a072719 commit db3d537

File tree

2 files changed

+28
-8
lines changed

2 files changed

+28
-8
lines changed

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

Lines changed: 18 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2018 the original author or authors.
2+
* Copyright 2002-2020 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -127,10 +127,14 @@ public Long getTimeout() {
127127
synchronized void initialize(Handler handler) throws IOException {
128128
this.handler = handler;
129129

130-
for (DataWithMediaType sendAttempt : this.earlySendAttempts) {
131-
sendInternal(sendAttempt.getData(), sendAttempt.getMediaType());
130+
try {
131+
for (DataWithMediaType sendAttempt : this.earlySendAttempts) {
132+
sendInternal(sendAttempt.getData(), sendAttempt.getMediaType());
133+
}
134+
}
135+
finally {
136+
this.earlySendAttempts.clear();
132137
}
133-
this.earlySendAttempts.clear();
134138

135139
if (this.complete) {
136140
if (this.failure != null) {
@@ -147,6 +151,13 @@ synchronized void initialize(Handler handler) throws IOException {
147151
}
148152
}
149153

154+
synchronized void initializeWithError(Throwable ex) {
155+
this.complete = true;
156+
this.failure = ex;
157+
this.earlySendAttempts.clear();
158+
this.errorCallback.accept(ex);
159+
}
160+
150161
/**
151162
* Invoked after the response is updated with the status code and headers,
152163
* if the ResponseBodyEmitter is wrapped in a ResponseEntity, but before the
@@ -182,7 +193,9 @@ public void send(Object object) throws IOException {
182193
* @throws java.lang.IllegalStateException wraps any other errors
183194
*/
184195
public synchronized void send(Object object, @Nullable MediaType mediaType) throws IOException {
185-
Assert.state(!this.complete, "ResponseBodyEmitter is already set complete");
196+
Assert.state(!this.complete,
197+
"ResponseBodyEmitter has already completed" +
198+
(this.failure != null ? " with error: " + this.failure : ""));
186199
sendInternal(object, mediaType);
187200
}
188201

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

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -176,10 +176,17 @@ public void handleReturnValue(@Nullable Object returnValue, MethodParameter retu
176176
// Headers will be flushed at the first write
177177
outputMessage = new StreamingServletServerHttpResponse(outputMessage);
178178

179-
DeferredResult<?> deferredResult = new DeferredResult<>(emitter.getTimeout());
180-
WebAsyncUtils.getAsyncManager(webRequest).startDeferredResultProcessing(deferredResult, mavContainer);
179+
HttpMessageConvertingHandler handler;
180+
try {
181+
DeferredResult<?> deferredResult = new DeferredResult<>(emitter.getTimeout());
182+
WebAsyncUtils.getAsyncManager(webRequest).startDeferredResultProcessing(deferredResult, mavContainer);
183+
handler = new HttpMessageConvertingHandler(outputMessage, deferredResult);
184+
}
185+
catch (Throwable ex) {
186+
emitter.initializeWithError(ex);
187+
throw ex;
188+
}
181189

182-
HttpMessageConvertingHandler handler = new HttpMessageConvertingHandler(outputMessage, deferredResult);
183190
emitter.initialize(handler);
184191
}
185192

0 commit comments

Comments
 (0)