From e20652009dca44363e7296c7bf7510def391885f Mon Sep 17 00:00:00 2001 From: Rossen Stoyanchev Date: Wed, 7 Mar 2018 14:59:18 -0500 Subject: [PATCH] Improve docs on SseEmitter onComplete/onError Issue: SPR-16548 --- .../annotation/ResponseBodyEmitter.java | 38 ++++++++++++------- .../mvc/method/annotation/SseEmitter.java | 8 ++++ src/docs/asciidoc/web/webmvc.adoc | 9 +++++ 3 files changed, 42 insertions(+), 13 deletions(-) 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 d5096cb9e4..96eaaecd66 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 @@ -66,13 +66,16 @@ public class ResponseBodyEmitter { @Nullable private final Long timeout; - private final Set earlySendAttempts = new LinkedHashSet<>(8); - @Nullable private Handler handler; + /** Store send data before handler is initialized */ + private final Set earlySendAttempts = new LinkedHashSet<>(8); + + /** Store complete invocation before handler is initialized */ private boolean complete; + /** Store completeWithError invocation before handler is initialized */ @Nullable private Throwable failure; @@ -147,6 +150,11 @@ public class ResponseBodyEmitter { * Write the given object to the response. *

If any exception occurs a dispatch is made back to the app server where * Spring MVC will pass the exception through its exception handling mechanism. + *

Note: if the send fails with an IOException, you do + * not need to call {@link #completeWithError(Throwable)} in order to clean + * up. Instead the Servlet container creates a notification that results in a + * dispatch where Spring MVC invokes exception resolvers and completes + * processing. * @param object the object to write * @throws IOException raised when an I/O error occurs * @throws java.lang.IllegalStateException wraps any other errors @@ -156,9 +164,8 @@ public class ResponseBodyEmitter { } /** - * Write the given object to the response also using a MediaType hint. - *

If any exception occurs a dispatch is made back to the app server where - * Spring MVC will pass the exception through its exception handling mechanism. + * Overloaded variant of {@link #send(Object)} that also accepts a MediaType + * hint for how to serialize the given Object. * @param object the object to write * @param mediaType a MediaType hint for selecting an HttpMessageConverter * @throws IOException raised when an I/O error occurs @@ -187,13 +194,12 @@ public class ResponseBodyEmitter { } /** - * Complete request processing. - *

A dispatch is made into the app server where Spring MVC completes - * asynchronous request processing. - *

Note: you do not need to call this method after an - * {@link IOException} from any of the {@code send} methods. The Servlet - * container will generate an error notification that Spring MVC will process - * and handle through the exception resolver mechanism and then complete. + * Complete request processing by performing a dispatch into the servlet + * container, where Spring MVC is invoked once more, and completes the + * request processing lifecycle. + *

Note: this method should be called by the application + * to complete request processing. It should not be used after container + * related events such as an error while {@link #send(Object) sending}. */ public synchronized void complete() { this.complete = true; @@ -205,7 +211,13 @@ public class ResponseBodyEmitter { /** * Complete request processing with an error. *

A dispatch is made into the app server where Spring MVC will pass the - * exception through its exception handling mechanism. + * exception through its exception handling mechanism. Note however that + * at this stage of request processing, the response is committed and the + * response status can no longer be changed. + *

Note: this method should be called by the application + * to complete request processing with an error. It should not be used after + * container related events such as an error while + * {@link #send(Object) sending}. */ public synchronized void completeWithError(Throwable ex) { this.complete = true; diff --git a/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/annotation/SseEmitter.java b/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/annotation/SseEmitter.java index 5b585c5e2f..ec1d5ea3bf 100644 --- a/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/annotation/SseEmitter.java +++ b/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/annotation/SseEmitter.java @@ -82,6 +82,10 @@ public class SseEmitter extends ResponseBodyEmitter { * SseEmitter emitter = new SseEmitter(); * emitter.send(event().data(myObject)); * + * + *

Please, see {@link ResponseBodyEmitter#send(Object) parent Javadoc} + * for important notes on exception handling. + * * @param object the object to write * @throws IOException raised when an I/O error occurs * @throws java.lang.IllegalStateException wraps any other errors @@ -99,6 +103,10 @@ public class SseEmitter extends ResponseBodyEmitter { * SseEmitter emitter = new SseEmitter(); * emitter.send(event().data(myObject, MediaType.APPLICATION_JSON)); * + * + *

Please, see {@link ResponseBodyEmitter#send(Object) parent Javadoc} + * for important notes on exception handling. + * * @param object the object to write * @param mediaType a MediaType hint for selecting an HttpMessageConverter * @throws IOException raised when an I/O error occurs diff --git a/src/docs/asciidoc/web/webmvc.adoc b/src/docs/asciidoc/web/webmvc.adoc index 21369b7046..aa0373e1ed 100644 --- a/src/docs/asciidoc/web/webmvc.adoc +++ b/src/docs/asciidoc/web/webmvc.adoc @@ -3304,6 +3304,13 @@ response. For example: `ResponseBodyEmitter` can also be used as the body in a `ResponseEntity` allowing you to customize the status and headers of the response. +When an `emitter` throws an `IOException` (e.g. if the remote client went away) applications +are not responsible for cleaning up the connection, and should not invoke `emitter.complete` +or `emitter.completeWithError`. Instead the servlet container automatically initiates an +`AsyncListener` error notification in which Spring MVC makes a `completeWithError` call, +which in turn performs one a final ASYNC dispatch to the application during which Spring MVC +invokes the configured exception resolvers and completes the request. + [[mvc-ann-async-sse]] ==== SSE @@ -3339,6 +3346,8 @@ does not support Server-Sent Events. Consider using Spring's <> transports (including SSE) that target a wide range of browsers. +Also see <> for notes on exception handling. + [[mvc-ann-async-output-stream]] ==== Raw data