diff --git a/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/annotation/ResponseBodyEmitter.java b/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/annotation/ResponseBodyEmitter.java index 9d4da7fe08f..732a677933b 100644 --- a/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/annotation/ResponseBodyEmitter.java +++ b/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/annotation/ResponseBodyEmitter.java @@ -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 { 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 { } } + 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 { * @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); } diff --git a/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/annotation/ResponseBodyEmitterReturnValueHandler.java b/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/annotation/ResponseBodyEmitterReturnValueHandler.java index ee02ad1700a..835116d425d 100644 --- a/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/annotation/ResponseBodyEmitterReturnValueHandler.java +++ b/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/annotation/ResponseBodyEmitterReturnValueHandler.java @@ -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); }