Browse Source

Protect SseEmitter against early I/O errors

Closes gh-25442
pull/26273/head
Rossen Stoyanchev 5 years ago
parent
commit
db3d537e72
  1. 23
      spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/annotation/ResponseBodyEmitter.java
  2. 13
      spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/annotation/ResponseBodyEmitterReturnValueHandler.java

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

@ -1,5 +1,5 @@ @@ -1,5 +1,5 @@
/*
* Copyright 2002-2018 the original author or authors.
* Copyright 2002-2020 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@ -127,10 +127,14 @@ public class ResponseBodyEmitter { @@ -127,10 +127,14 @@ public class ResponseBodyEmitter {
synchronized void initialize(Handler handler) throws IOException {
this.handler = handler;
for (DataWithMediaType sendAttempt : this.earlySendAttempts) {
sendInternal(sendAttempt.getData(), sendAttempt.getMediaType());
try {
for (DataWithMediaType sendAttempt : this.earlySendAttempts) {
sendInternal(sendAttempt.getData(), sendAttempt.getMediaType());
}
}
finally {
this.earlySendAttempts.clear();
}
this.earlySendAttempts.clear();
if (this.complete) {
if (this.failure != null) {
@ -147,6 +151,13 @@ public class ResponseBodyEmitter { @@ -147,6 +151,13 @@ public class ResponseBodyEmitter {
}
}
synchronized void initializeWithError(Throwable ex) {
this.complete = true;
this.failure = ex;
this.earlySendAttempts.clear();
this.errorCallback.accept(ex);
}
/**
* Invoked after the response is updated with the status code and headers,
* if the ResponseBodyEmitter is wrapped in a ResponseEntity, but before the
@ -182,7 +193,9 @@ public class ResponseBodyEmitter { @@ -182,7 +193,9 @@ public class ResponseBodyEmitter {
* @throws java.lang.IllegalStateException wraps any other errors
*/
public synchronized void send(Object object, @Nullable MediaType mediaType) throws IOException {
Assert.state(!this.complete, "ResponseBodyEmitter is already set complete");
Assert.state(!this.complete,
"ResponseBodyEmitter has already completed" +
(this.failure != null ? " with error: " + this.failure : ""));
sendInternal(object, mediaType);
}

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

@ -176,10 +176,17 @@ public class ResponseBodyEmitterReturnValueHandler implements HandlerMethodRetur @@ -176,10 +176,17 @@ public class ResponseBodyEmitterReturnValueHandler implements HandlerMethodRetur
// Headers will be flushed at the first write
outputMessage = new StreamingServletServerHttpResponse(outputMessage);
DeferredResult<?> deferredResult = new DeferredResult<>(emitter.getTimeout());
WebAsyncUtils.getAsyncManager(webRequest).startDeferredResultProcessing(deferredResult, mavContainer);
HttpMessageConvertingHandler handler;
try {
DeferredResult<?> deferredResult = new DeferredResult<>(emitter.getTimeout());
WebAsyncUtils.getAsyncManager(webRequest).startDeferredResultProcessing(deferredResult, mavContainer);
handler = new HttpMessageConvertingHandler(outputMessage, deferredResult);
}
catch (Throwable ex) {
emitter.initializeWithError(ex);
throw ex;
}
HttpMessageConvertingHandler handler = new HttpMessageConvertingHandler(outputMessage, deferredResult);
emitter.initialize(handler);
}

Loading…
Cancel
Save