You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
514 lines
20 KiB
514 lines
20 KiB
[[mvc-ann-async]] |
|
= Asynchronous Requests |
|
|
|
Spring MVC has an extensive integration with Servlet asynchronous request |
|
xref:web/webmvc/mvc-ann-async.adoc#mvc-ann-async-processing[processing]: |
|
|
|
* xref:web/webmvc/mvc-ann-async.adoc#mvc-ann-async-deferredresult[`DeferredResult`] and xref:web/webmvc/mvc-ann-async.adoc#mvc-ann-async-callable[`Callable`] |
|
return values in controller methods provide basic support for a single asynchronous |
|
return value. |
|
* Controllers can xref:web/webmvc/mvc-ann-async.adoc#mvc-ann-async-http-streaming[stream] multiple values, including |
|
xref:web/webmvc/mvc-ann-async.adoc#mvc-ann-async-sse[SSE] and xref:web/webmvc/mvc-ann-async.adoc#mvc-ann-async-output-stream[raw data]. |
|
* Controllers can use reactive clients and return |
|
xref:web/webmvc/mvc-ann-async.adoc#mvc-ann-async-reactive-types[reactive types] for response handling. |
|
|
|
For an overview of how this differs from Spring WebFlux, see the xref:web/webmvc/mvc-ann-async.adoc#mvc-ann-async-vs-webflux[Async Spring MVC compared to WebFlux] section below. |
|
|
|
[[mvc-ann-async-deferredresult]] |
|
== `DeferredResult` |
|
|
|
Once the asynchronous request processing feature is xref:web/webmvc/mvc-ann-async.adoc#mvc-ann-async-configuration[enabled] |
|
in the Servlet container, controller methods can wrap any supported controller method |
|
return value with `DeferredResult`, as the following example shows: |
|
|
|
[tabs] |
|
====== |
|
Java:: |
|
+ |
|
[source,java,indent=0,subs="verbatim,quotes",role="primary"] |
|
---- |
|
@GetMapping("/quotes") |
|
@ResponseBody |
|
public DeferredResult<String> quotes() { |
|
DeferredResult<String> deferredResult = new DeferredResult<>(); |
|
// Save the deferredResult somewhere.. |
|
return deferredResult; |
|
} |
|
|
|
// From some other thread... |
|
deferredResult.setResult(result); |
|
---- |
|
|
|
Kotlin:: |
|
+ |
|
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] |
|
---- |
|
@GetMapping("/quotes") |
|
@ResponseBody |
|
fun quotes(): DeferredResult<String> { |
|
val deferredResult = DeferredResult<String>() |
|
// Save the deferredResult somewhere.. |
|
return deferredResult |
|
} |
|
|
|
// From some other thread... |
|
deferredResult.setResult(result) |
|
---- |
|
====== |
|
|
|
The controller can produce the return value asynchronously, from a different thread -- for |
|
example, in response to an external event (JMS message), a scheduled task, or other event. |
|
|
|
|
|
|
|
[[mvc-ann-async-callable]] |
|
== `Callable` |
|
|
|
A controller can wrap any supported return value with `java.util.concurrent.Callable`, |
|
as the following example shows: |
|
|
|
[tabs] |
|
====== |
|
Java:: |
|
+ |
|
[source,java,indent=0,subs="verbatim,quotes",role="primary"] |
|
---- |
|
@PostMapping |
|
public Callable<String> processUpload(final MultipartFile file) { |
|
return () -> "someView"; |
|
} |
|
---- |
|
|
|
Kotlin:: |
|
+ |
|
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] |
|
---- |
|
@PostMapping |
|
fun processUpload(file: MultipartFile) = Callable<String> { |
|
// ... |
|
"someView" |
|
} |
|
---- |
|
====== |
|
|
|
The return value can then be obtained by running the given task through the |
|
xref:web/webmvc/mvc-ann-async.adoc#mvc-ann-async-configuration-spring-mvc[configured] `AsyncTaskExecutor`. |
|
|
|
|
|
|
|
[[mvc-ann-async-processing]] |
|
== Processing |
|
|
|
Here is a very concise overview of Servlet asynchronous request processing: |
|
|
|
* A `ServletRequest` can be put in asynchronous mode by calling `request.startAsync()`. |
|
The main effect of doing so is that the Servlet (as well as any filters) can exit, but |
|
the response remains open to let processing complete later. |
|
* The call to `request.startAsync()` returns `AsyncContext`, which you can use for |
|
further control over asynchronous processing. For example, it provides the `dispatch` method, |
|
which is similar to a forward from the Servlet API, except that it lets an |
|
application resume request processing on a Servlet container thread. |
|
* The `ServletRequest` provides access to the current `DispatcherType`, which you can |
|
use to distinguish between processing the initial request, an asynchronous |
|
dispatch, a forward, and other dispatcher types. |
|
|
|
`DeferredResult` processing works as follows: |
|
|
|
* The controller returns a `DeferredResult` and saves it in some in-memory |
|
queue or list where it can be accessed. |
|
* Spring MVC calls `request.startAsync()`. |
|
* Meanwhile, the `DispatcherServlet` and all configured filters exit the request |
|
processing thread, but the response remains open. |
|
* The application sets the `DeferredResult` from some thread, and Spring MVC |
|
dispatches the request back to the Servlet container. |
|
* The `DispatcherServlet` is invoked again, and processing resumes with the |
|
asynchronously produced return value. |
|
|
|
`Callable` processing works as follows: |
|
|
|
* The controller returns a `Callable`. |
|
* Spring MVC calls `request.startAsync()` and submits the `Callable` to |
|
an `AsyncTaskExecutor` for processing in a separate thread. |
|
* Meanwhile, the `DispatcherServlet` and all filters exit the Servlet container thread, |
|
but the response remains open. |
|
* Eventually the `Callable` produces a result, and Spring MVC dispatches the request back |
|
to the Servlet container to complete processing. |
|
* The `DispatcherServlet` is invoked again, and processing resumes with the |
|
asynchronously produced return value from the `Callable`. |
|
|
|
For further background and context, you can also read |
|
https://spring.io/blog/2012/05/07/spring-mvc-3-2-preview-introducing-servlet-3-async-support[the |
|
blog posts] that introduced asynchronous request processing support in Spring MVC 3.2. |
|
|
|
|
|
[[mvc-ann-async-exceptions]] |
|
=== Exception Handling |
|
|
|
When you use a `DeferredResult`, you can choose whether to call `setResult` or |
|
`setErrorResult` with an exception. In both cases, Spring MVC dispatches the request back |
|
to the Servlet container to complete processing. It is then treated either as if the |
|
controller method returned the given value or as if it produced the given exception. |
|
The exception then goes through the regular exception handling mechanism (for example, invoking |
|
`@ExceptionHandler` methods). |
|
|
|
When you use `Callable`, similar processing logic occurs, the main difference being that |
|
the result is returned from the `Callable` or an exception is raised by it. |
|
|
|
|
|
[[mvc-ann-async-interception]] |
|
=== Interception |
|
|
|
`HandlerInterceptor` instances can be of type `AsyncHandlerInterceptor`, to receive the |
|
`afterConcurrentHandlingStarted` callback on the initial request that starts asynchronous |
|
processing (instead of `postHandle` and `afterCompletion`). |
|
|
|
`HandlerInterceptor` implementations can also register a `CallableProcessingInterceptor` |
|
or a `DeferredResultProcessingInterceptor`, to integrate more deeply with the |
|
lifecycle of an asynchronous request (for example, to handle a timeout event). See |
|
{api-spring-framework}/web/servlet/AsyncHandlerInterceptor.html[`AsyncHandlerInterceptor`] |
|
for more details. |
|
|
|
`DeferredResult` provides `onTimeout(Runnable)` and `onCompletion(Runnable)` callbacks. |
|
See the {api-spring-framework}/web/context/request/async/DeferredResult.html[javadoc of `DeferredResult`] |
|
for more details. `Callable` can be substituted for `WebAsyncTask` that exposes additional |
|
methods for timeout and completion callbacks. |
|
|
|
|
|
[[mvc-ann-async-vs-webflux]] |
|
=== Async Spring MVC compared to WebFlux |
|
|
|
The Servlet API was originally built for making a single pass through the Filter-Servlet |
|
chain. Asynchronous request processing lets applications exit the Filter-Servlet chain |
|
but leave the response open for further processing. The Spring MVC asynchronous support |
|
is built around that mechanism. When a controller returns a `DeferredResult`, the |
|
Filter-Servlet chain is exited, and the Servlet container thread is released. Later, when |
|
the `DeferredResult` is set, an `ASYNC` dispatch (to the same URL) is made, during which the |
|
controller is mapped again but, rather than invoking it, the `DeferredResult` value is used |
|
(as if the controller returned it) to resume processing. |
|
|
|
By contrast, Spring WebFlux is neither built on the Servlet API, nor does it need such an |
|
asynchronous request processing feature, because it is asynchronous by design. Asynchronous |
|
handling is built into all framework contracts and is intrinsically supported through all |
|
stages of request processing. |
|
|
|
From a programming model perspective, both Spring MVC and Spring WebFlux support |
|
asynchronous and xref:web/webmvc/mvc-ann-async.adoc#mvc-ann-async-reactive-types[Reactive Types] as return values in controller methods. |
|
Spring MVC even supports streaming, including reactive back pressure. However, individual |
|
writes to the response remain blocking (and are performed on a separate thread), unlike WebFlux, |
|
which relies on non-blocking I/O and does not need an extra thread for each write. |
|
|
|
Another fundamental difference is that Spring MVC does not support asynchronous or reactive |
|
types in controller method arguments (for example, `@RequestBody`, `@RequestPart`, and others), |
|
nor does it have any explicit support for asynchronous and reactive types as model attributes. |
|
Spring WebFlux does support all that. |
|
|
|
Finally, from a configuration perspective the asynchronous request processing feature must be |
|
xref:web/webmvc/mvc-ann-async.adoc#mvc-ann-async-configuration[enabled at the Servlet container level]. |
|
|
|
|
|
[[mvc-ann-async-http-streaming]] |
|
== HTTP Streaming |
|
[.small]#xref:web/webflux/reactive-spring.adoc#webflux-codecs-streaming[See equivalent in the Reactive stack]# |
|
|
|
You can use `DeferredResult` and `Callable` for a single asynchronous return value. |
|
What if you want to produce multiple asynchronous values and have those written to the |
|
response? This section describes how to do so. |
|
|
|
|
|
[[mvc-ann-async-objects]] |
|
=== Objects |
|
|
|
You can use the `ResponseBodyEmitter` return value to produce a stream of objects, where |
|
each object is serialized with an |
|
xref:integration/rest-clients.adoc#rest-message-conversion[`HttpMessageConverter`] and written to the |
|
response, as the following example shows: |
|
|
|
[tabs] |
|
====== |
|
Java:: |
|
+ |
|
[source,java,indent=0,subs="verbatim,quotes",role="primary"] |
|
---- |
|
@GetMapping("/events") |
|
public ResponseBodyEmitter handle() { |
|
ResponseBodyEmitter emitter = new ResponseBodyEmitter(); |
|
// Save the emitter somewhere.. |
|
return emitter; |
|
} |
|
|
|
// In some other thread |
|
emitter.send("Hello once"); |
|
|
|
// and again later on |
|
emitter.send("Hello again"); |
|
|
|
// and done at some point |
|
emitter.complete(); |
|
---- |
|
|
|
Kotlin:: |
|
+ |
|
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] |
|
---- |
|
@GetMapping("/events") |
|
fun handle() = ResponseBodyEmitter().apply { |
|
// Save the emitter somewhere.. |
|
} |
|
|
|
// In some other thread |
|
emitter.send("Hello once") |
|
|
|
// and again later on |
|
emitter.send("Hello again") |
|
|
|
// and done at some point |
|
emitter.complete() |
|
---- |
|
====== |
|
|
|
You can also use `ResponseBodyEmitter` as the body in a `ResponseEntity`, letting you |
|
customize the status and headers of the response. |
|
|
|
When an `emitter` throws an `IOException` (for example, 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. |
|
This call, in turn, performs one final `ASYNC` dispatch to the application, during which Spring MVC |
|
invokes the configured exception resolvers and completes the request. |
|
|
|
|
|
[[mvc-ann-async-sse]] |
|
=== SSE |
|
|
|
`SseEmitter` (a subclass of `ResponseBodyEmitter`) provides support for |
|
https://www.w3.org/TR/eventsource/[Server-Sent Events], where events sent from the server |
|
are formatted according to the W3C SSE specification. To produce an SSE |
|
stream from a controller, return `SseEmitter`, as the following example shows: |
|
|
|
[tabs] |
|
====== |
|
Java:: |
|
+ |
|
[source,java,indent=0,subs="verbatim,quotes",role="primary"] |
|
---- |
|
@GetMapping(path="/events", produces=MediaType.TEXT_EVENT_STREAM_VALUE) |
|
public SseEmitter handle() { |
|
SseEmitter emitter = new SseEmitter(); |
|
// Save the emitter somewhere.. |
|
return emitter; |
|
} |
|
|
|
// In some other thread |
|
emitter.send("Hello once"); |
|
|
|
// and again later on |
|
emitter.send("Hello again"); |
|
|
|
// and done at some point |
|
emitter.complete(); |
|
---- |
|
|
|
Kotlin:: |
|
+ |
|
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] |
|
---- |
|
@GetMapping("/events", produces = [MediaType.TEXT_EVENT_STREAM_VALUE]) |
|
fun handle() = SseEmitter().apply { |
|
// Save the emitter somewhere.. |
|
} |
|
|
|
// In some other thread |
|
emitter.send("Hello once") |
|
|
|
// and again later on |
|
emitter.send("Hello again") |
|
|
|
// and done at some point |
|
emitter.complete() |
|
---- |
|
====== |
|
|
|
While SSE is the main option for streaming into browsers, note that Internet Explorer |
|
does not support Server-Sent Events. Consider using Spring's |
|
xref:web/websocket.adoc[WebSocket messaging] with |
|
xref:web/websocket/fallback.adoc[SockJS fallback] transports (including SSE) that target |
|
a wide range of browsers. |
|
|
|
See also xref:web/webmvc/mvc-ann-async.adoc#mvc-ann-async-objects[previous section] for notes on exception handling. |
|
|
|
|
|
[[mvc-ann-async-output-stream]] |
|
=== Raw Data |
|
|
|
Sometimes, it is useful to bypass message conversion and stream directly to the response |
|
`OutputStream` (for example, for a file download). You can use the `StreamingResponseBody` |
|
return value type to do so, as the following example shows: |
|
|
|
[tabs] |
|
====== |
|
Java:: |
|
+ |
|
[source,java,indent=0,subs="verbatim,quotes",role="primary"] |
|
---- |
|
@GetMapping("/download") |
|
public StreamingResponseBody handle() { |
|
return new StreamingResponseBody() { |
|
@Override |
|
public void writeTo(OutputStream outputStream) throws IOException { |
|
// write... |
|
} |
|
}; |
|
} |
|
---- |
|
|
|
Kotlin:: |
|
+ |
|
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] |
|
---- |
|
@GetMapping("/download") |
|
fun handle() = StreamingResponseBody { |
|
// write... |
|
} |
|
---- |
|
====== |
|
|
|
You can use `StreamingResponseBody` as the body in a `ResponseEntity` to |
|
customize the status and headers of the response. |
|
|
|
|
|
|
|
[[mvc-ann-async-reactive-types]] |
|
== Reactive Types |
|
[.small]#xref:web/webflux/reactive-spring.adoc#webflux-codecs-streaming[See equivalent in the Reactive stack]# |
|
|
|
Spring MVC supports use of reactive client libraries in a controller (also read |
|
xref:web-reactive.adoc#webflux-reactive-libraries[Reactive Libraries] in the WebFlux section). |
|
This includes the `WebClient` from `spring-webflux` and others, such as Spring Data |
|
reactive data repositories. In such scenarios, it is convenient to be able to return |
|
reactive types from the controller method. |
|
|
|
Reactive return values are handled as follows: |
|
|
|
* A single-value promise is adapted to, similar to using `DeferredResult`. Examples |
|
include `Mono` (Reactor) or `Single` (RxJava). |
|
* A multi-value stream with a streaming media type (such as `application/x-ndjson` |
|
or `text/event-stream`) is adapted to, similar to using `ResponseBodyEmitter` or |
|
`SseEmitter`. Examples include `Flux` (Reactor) or `Observable` (RxJava). |
|
Applications can also return `Flux<ServerSentEvent>` or `Observable<ServerSentEvent>`. |
|
* A multi-value stream with any other media type (such as `application/json`) is adapted |
|
to, similar to using `DeferredResult<List<?>>`. |
|
|
|
TIP: Spring MVC supports Reactor and RxJava through the |
|
{api-spring-framework}/core/ReactiveAdapterRegistry.html[`ReactiveAdapterRegistry`] from |
|
`spring-core`, which lets it adapt from multiple reactive libraries. |
|
|
|
For streaming to the response, reactive back pressure is supported, but writes to the |
|
response are still blocking and are run on a separate thread through the |
|
xref:web/webmvc/mvc-ann-async.adoc#mvc-ann-async-configuration-spring-mvc[configured] |
|
`AsyncTaskExecutor`, to avoid blocking the upstream source such as a `Flux` returned |
|
from `WebClient`. |
|
|
|
|
|
|
|
|
|
[[mvc-ann-async-context-propagation]] |
|
== Context Propagation |
|
|
|
It is common to propagate context via `java.lang.ThreadLocal`. This works transparently |
|
for handling on the same thread, but requires additional work for asynchronous handling |
|
across multiple threads. The Micrometer |
|
https://github.com/micrometer-metrics/context-propagation#context-propagation-library[Context Propagation] |
|
library simplifies context propagation across threads, and across context mechanisms such |
|
as `ThreadLocal` values, |
|
Reactor https://projectreactor.io/docs/core/release/reference/#context[context], |
|
GraphQL Java https://www.graphql-java.com/documentation/concerns/#context-objects[context], |
|
and others. |
|
|
|
If Micrometer Context Propagation is present on the classpath, when a controller method |
|
returns a xref:web/webmvc/mvc-ann-async.adoc#mvc-ann-async-reactive-types[reactive type] such as `Flux` or `Mono`, all |
|
`ThreadLocal` values, for which there is a registered `io.micrometer.ThreadLocalAccessor`, |
|
are written to the Reactor `Context` as key-value pairs, using the key assigned by the |
|
`ThreadLocalAccessor`. |
|
|
|
For other asynchronous handling scenarios, you can use the Context Propagation library |
|
directly. For example: |
|
|
|
[source,java,indent=0,subs="verbatim,quotes"] |
|
.Java |
|
---- |
|
// Capture ThreadLocal values from the main thread ... |
|
ContextSnapshot snapshot = ContextSnapshot.captureAll(); |
|
|
|
// On a different thread: restore ThreadLocal values |
|
try (ContextSnapshot.Scope scope = snapshot.setThreadLocals()) { |
|
// ... |
|
} |
|
---- |
|
|
|
For more details, see the |
|
https://micrometer.io/docs/contextPropagation[documentation] of the Micrometer Context |
|
Propagation library. |
|
|
|
|
|
|
|
[[mvc-ann-async-disconnects]] |
|
== Disconnects |
|
[.small]#xref:web/webflux/reactive-spring.adoc#webflux-codecs-streaming[See equivalent in the Reactive stack]# |
|
|
|
The Servlet API does not provide any notification when a remote client goes away. |
|
Therefore, while streaming to the response, whether through xref:web/webmvc/mvc-ann-async.adoc#mvc-ann-async-sse[SseEmitter] |
|
or xref:web/webmvc/mvc-ann-async.adoc#mvc-ann-async-reactive-types[reactive types], it is important to send data periodically, |
|
since the write fails if the client has disconnected. The send could take the form of an |
|
empty (comment-only) SSE event or any other data that the other side would have to interpret |
|
as a heartbeat and ignore. |
|
|
|
Alternatively, consider using web messaging solutions (such as |
|
xref:web/websocket/stomp.adoc[STOMP over WebSocket] or WebSocket with xref:web/websocket/fallback.adoc[SockJS]) |
|
that have a built-in heartbeat mechanism. |
|
|
|
|
|
|
|
[[mvc-ann-async-configuration]] |
|
== Configuration |
|
|
|
The asynchronous request processing feature must be enabled at the Servlet container level. |
|
The MVC configuration also exposes several options for asynchronous requests. |
|
|
|
|
|
[[mvc-ann-async-configuration-servlet3]] |
|
=== Servlet Container |
|
|
|
Filter and Servlet declarations have an `asyncSupported` flag that needs to be set to `true` |
|
to enable asynchronous request processing. In addition, Filter mappings should be |
|
declared to handle the `ASYNC` `jakarta.servlet.DispatchType`. |
|
|
|
In Java configuration, when you use `AbstractAnnotationConfigDispatcherServletInitializer` |
|
to initialize the Servlet container, this is done automatically. |
|
|
|
In `web.xml` configuration, you can add `<async-supported>true</async-supported>` to the |
|
`DispatcherServlet` and to `Filter` declarations and add |
|
`<dispatcher>ASYNC</dispatcher>` to filter mappings. |
|
|
|
|
|
[[mvc-ann-async-configuration-spring-mvc]] |
|
=== Spring MVC |
|
|
|
The MVC configuration exposes the following options for asynchronous request processing: |
|
|
|
* Java configuration: Use the `configureAsyncSupport` callback on `WebMvcConfigurer`. |
|
* XML namespace: Use the `<async-support>` element under `<mvc:annotation-driven>`. |
|
|
|
You can configure the following: |
|
|
|
* Default timeout value for async requests, which if not set, depends |
|
on the underlying Servlet container. |
|
* `AsyncTaskExecutor` to use for blocking writes when streaming with |
|
xref:web/webmvc/mvc-ann-async.adoc#mvc-ann-async-reactive-types[Reactive Types] and for |
|
executing `Callable` instances returned from controller methods. |
|
The one used by default is not suitable for production under load. |
|
* `DeferredResultProcessingInterceptor` implementations and `CallableProcessingInterceptor` implementations. |
|
|
|
Note that you can also set the default timeout value on a `DeferredResult`, |
|
a `ResponseBodyEmitter`, and an `SseEmitter`. For a `Callable`, you can use |
|
`WebAsyncTask` to provide a timeout value. |
|
|
|
|