521 lines
20 KiB
Plaintext
521 lines
20 KiB
Plaintext
[[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"]
|
|
----
|
|
@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"]
|
|
----
|
|
@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"]
|
|
----
|
|
@PostMapping
|
|
public Callable<String> processUpload(final MultipartFile file) {
|
|
return () -> "someView";
|
|
}
|
|
----
|
|
|
|
Kotlin::
|
|
+
|
|
[source,kotlin,indent=0,subs="verbatim,quotes"]
|
|
----
|
|
@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
|
|
{spring-site-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
|
|
{spring-framework-api}/web/servlet/AsyncHandlerInterceptor.html[`AsyncHandlerInterceptor`]
|
|
for more details.
|
|
|
|
`DeferredResult` provides `onTimeout(Runnable)` and `onCompletion(Runnable)` callbacks.
|
|
See the {spring-framework-api}/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"]
|
|
----
|
|
@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"]
|
|
----
|
|
@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"]
|
|
----
|
|
@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"]
|
|
----
|
|
@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"]
|
|
----
|
|
@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"]
|
|
----
|
|
@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
|
|
{spring-framework-api}/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 {reactor-site}/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()) {
|
|
// ...
|
|
}
|
|
----
|
|
|
|
The following `ThreadLocalAccessor` implementations are provided out of the box:
|
|
|
|
* `LocaleContextThreadLocalAccessor` -- propagates `LocaleContext` via `LocaleContextHolder`
|
|
* `RequestAttributesThreadLocalAccessor` -- propagates `RequestAttributes` via `RequestContextHolder`
|
|
|
|
The above are not registered automatically. You need to register them via `ContextRegistry.getInstance()` on startup.
|
|
|
|
For more details, see the {micrometer-context-propagation-docs}/[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:
|
|
|
|
* The default timeout value for async requests depends
|
|
on the underlying Servlet container, unless it is set explicitly.
|
|
* `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.
|
|
|