Document async request processing enhancements
Generally update chapter and add documentation for 4.2 including the return value types ResponseBodyEmitter, SseEmitter, and StreamingResponseBody. Issue: SPR-12672
This commit is contained in:
parent
5b09723bb4
commit
209e8de188
|
|
@ -2054,12 +2054,12 @@ customized through `jsonpParameterNames` property.
|
|||
=== Asynchronous Request Processing
|
||||
Spring MVC 3.2 introduced Servlet 3 based asynchronous request processing. Instead of
|
||||
returning a value, as usual, a controller method can now return a
|
||||
`java.util.concurrent.Callable` and produce the return value from a separate thread.
|
||||
Meanwhile the main Servlet container thread is released and allowed to process other
|
||||
`java.util.concurrent.Callable` and produce the return value from a Spring MVC managed thread.
|
||||
Meanwhile the main Servlet container thread is exited and released and allowed to process other
|
||||
requests. Spring MVC invokes the `Callable` in a separate thread with the help of a
|
||||
`TaskExecutor` and when the `Callable` returns, the request is dispatched back to the
|
||||
Servlet container to resume processing with the value returned by the `Callable`. Here
|
||||
is an example controller method:
|
||||
Servlet container to resume processing using the value returned by the `Callable`. Here
|
||||
is an example of such a controller method:
|
||||
|
||||
[source,java,indent=0]
|
||||
[subs="verbatim,quotes"]
|
||||
|
|
@ -2077,11 +2077,11 @@ is an example controller method:
|
|||
}
|
||||
----
|
||||
|
||||
A second option is for the controller to return an instance of `DeferredResult`. In this
|
||||
case the return value will also be produced from a separate thread. However, that thread
|
||||
is not known to Spring MVC. For example the result may be produced in response to some
|
||||
external event such as a JMS message, a scheduled task, etc. Here is an example
|
||||
controller method:
|
||||
Another option is for the controller method to return an instance of `DeferredResult`. In this
|
||||
case the return value will also be produced from any thread, i.e. one that
|
||||
is not managed by Spring MVC. For example the result may be produced in response to some
|
||||
external event such as a JMS message, a scheduled task, and so on. Here is an example
|
||||
of such a controller method:
|
||||
|
||||
[source,java,indent=0]
|
||||
[subs="verbatim,quotes"]
|
||||
|
|
@ -2090,7 +2090,7 @@ controller method:
|
|||
@ResponseBody
|
||||
public DeferredResult<String> quotes() {
|
||||
DeferredResult<String> deferredResult = new DeferredResult<String>();
|
||||
// Save the deferredResult in in-memory queue ...
|
||||
// Save the deferredResult somewhere..
|
||||
return deferredResult;
|
||||
}
|
||||
|
||||
|
|
@ -2098,102 +2098,187 @@ controller method:
|
|||
deferredResult.setResult(data);
|
||||
----
|
||||
|
||||
This may be difficult to understand without any knowledge of the Servlet 3 async
|
||||
processing feature. It would certainly help to read up on it. At a very minimum consider
|
||||
the following basic facts:
|
||||
This may be difficult to understand without any knowledge of the Servlet 3.0
|
||||
asynchronous request processing features. It would certainly help to read up
|
||||
on that. Here are a few basic facts about the underlying mechanism:
|
||||
|
||||
* 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 will remain open allowing some other thread to complete processing.
|
||||
* The call to `request.startAsync()` returns an `AsyncContext`, which can be used for
|
||||
the response will remain open to allow processing to complete later.
|
||||
* The call to `request.startAsync()` returns `AsyncContext` which can be used for
|
||||
further control over async processing. For example it provides the method `dispatch`,
|
||||
which can be called from an application thread in order to "dispatch" the request back
|
||||
to the Servlet container. An async dispatch is similar to a forward except it is made
|
||||
from one (application) thread to another (Servlet container) thread whereas a forward
|
||||
occurs synchronously in the same (Servlet container) thread.
|
||||
* `ServletRequest` provides access to the current `DispatcherType`, which can be used to
|
||||
distinguish if a `Servlet` or a `Filter` is processing on the initial request
|
||||
processing thread and when it is processing in an async dispatch.
|
||||
that is similar to a forward from the Servlet API except it allows an
|
||||
application to resume request processing on a Servlet container thread.
|
||||
* The `ServletRequest` provides access to the current `DispatcherType` that can
|
||||
be used to distinguish between processing the initial request, an async
|
||||
dispatch, a forward, and other dispatcher types.
|
||||
|
||||
With the above in mind, the following is the sequence of events for async request
|
||||
processing with a `Callable`: (1) Controller returns a `Callable`, (2) Spring MVC starts
|
||||
async processing and submits the `Callable` to a `TaskExecutor` for processing in a
|
||||
separate thread, (3) the `DispatcherServlet` and all Filter's exit the request
|
||||
processing thread but the response remains open, (4) the `Callable` produces a result
|
||||
and Spring MVC dispatches the request back to the Servlet container, (5) the
|
||||
`DispatcherServlet` is invoked again and processing resumes with the asynchronously
|
||||
produced result from the `Callable`. The exact sequencing of (2), (3), and (4) may vary
|
||||
depending on the speed of execution of the concurrent threads.
|
||||
processing with a `Callable`:
|
||||
|
||||
The sequence of events for async request processing with a `DeferredResult` is the same
|
||||
in principal except it's up to the application to produce the asynchronous result from
|
||||
some thread: (1) Controller returns a `DeferredResult` and saves it in some in-memory
|
||||
queue or list where it can be accessed, (2) Spring MVC starts async processing, (3) the
|
||||
`DispatcherServlet` and all configured Filter's exit the request processing thread but
|
||||
the response remains open, (4) the application sets the `DeferredResult` from some
|
||||
thread and Spring MVC dispatches the request back to the Servlet container, (5) the
|
||||
`DispatcherServlet` is invoked again and processing resumes with the asynchronously
|
||||
produced result.
|
||||
* Controller returns a `Callable`.
|
||||
* Spring MVC starts asynchronous processing and submits the `Callable` to
|
||||
a `TaskExecutor` for processing in a separate thread.
|
||||
* The `DispatcherServlet` and all Filter's exit the Servlet container thread
|
||||
but the response remains open.
|
||||
* The `Callable` produces a result and Spring MVC dispatches the request back
|
||||
to the Servlet container to resume processing.
|
||||
* The `DispatcherServlet` is invoked again and processing resumes with the
|
||||
asynchronously produced result from the `Callable`.
|
||||
|
||||
Explaining the motivation for async request processing and when or why to use it are
|
||||
beyond the scope of this document. For further information you may wish to read
|
||||
The sequence for `DeferredResult` is very similar except it's up to the
|
||||
application to produce the asynchronous result from any thread:
|
||||
|
||||
* Controller returns a `DeferredResult` and saves it in some in-memory
|
||||
queue or list where it can be accessed.
|
||||
* Spring MVC starts async processing.
|
||||
* The `DispatcherServlet` and all configured Filter's 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 result.
|
||||
|
||||
For further background on the motivation for async request processing and
|
||||
when or why to use it please read
|
||||
https://spring.io/blog/2012/05/07/spring-mvc-3-2-preview-introducing-servlet-3-async-support[this
|
||||
blog post series].
|
||||
|
||||
|
||||
[[mvc-ann-async-exceptions]]
|
||||
==== Exception Handling for Async Requests
|
||||
What happens if a `Callable` returned from a controller method raises an Exception while
|
||||
being executed? The effect is similar to what happens when any controller method raises
|
||||
an exception. It is handled by a matching `@ExceptionHandler` method in the same
|
||||
controller or by one of the configured `HandlerExceptionResolver` instances.
|
||||
|
||||
[NOTE]
|
||||
====
|
||||
Under the covers, when a `Callable` raises an Exception, Spring MVC still dispatches to
|
||||
the Servlet container to resume processing. The only difference is that the result of
|
||||
executing the `Callable` is an `Exception` that must be processed with the configured
|
||||
`HandlerExceptionResolver` instances.
|
||||
====
|
||||
|
||||
When using a `DeferredResult`, you have a choice of calling its `setErrorResult(Object)`
|
||||
method and provide an `Exception` or any other Object you'd like to use as the result.
|
||||
If the result is an `Exception`, it will be processed with a matching
|
||||
`@ExceptionHandler` method in the same controller or with any configured
|
||||
`HandlerExceptionResolver` instance.
|
||||
What happens if a `Callable` returned from a controller method raises an
|
||||
Exception while being executed? The short answer is the same as what happens
|
||||
when a controller method raises an exception. It goes through the regular
|
||||
exception handling mechanism. The longer explanation is that when a `Callable`
|
||||
raises an Exception Spring MVC dispatches to the Servlet container with
|
||||
the `Exception` as the result and that leads to resume request processing
|
||||
with the `Exception` instead of a controller method return value.
|
||||
When using a `DeferredResult` you have a choice whether to call
|
||||
`setResult` or `setErrorResult` with an `Exception` instance.
|
||||
|
||||
|
||||
[[mvc-ann-async-interception]]
|
||||
==== Intercepting Async Requests
|
||||
An existing `HandlerInterceptor` can implement `AsyncHandlerInterceptor`, which provides
|
||||
one additional method `afterConcurrentHandlingStarted`. It is invoked after async
|
||||
processing starts and when the initial request processing thread is being exited. See
|
||||
the `AsyncHandlerInterceptor` javadocs for more details on that.
|
||||
A `HandlerInterceptor` can also implement `AsyncHandlerInterceptor` in order
|
||||
to implement the `afterConcurrentHandlingStarted` callback, which is called
|
||||
instead of `postHandle` and `afterCompletion` when asynchronous processing
|
||||
starts.
|
||||
|
||||
Further options for async request lifecycle callbacks are provided directly on
|
||||
`DeferredResult`, which has the methods `onTimeout(Runnable)` and
|
||||
`onCompletion(Runnable)`. Those are called when the async request is about to time out
|
||||
or has completed respectively. The timeout event can be handled by setting the
|
||||
`DeferredResult` to some value. The completion callback however is final and the result
|
||||
can no longer be set.
|
||||
A `HandlerInterceptor` can also register a `CallableProcessingInterceptor`
|
||||
or a `DeferredResultProcessingInterceptor` in order to integrate more
|
||||
deeply with the lifecycle of an asynchronous request and for example
|
||||
handle a timeout event. See the Javadoc of `AsyncHandlerInterceptor`
|
||||
for more details.
|
||||
|
||||
Similar callbacks are also available with a `Callable`. However, you will need to wrap
|
||||
the `Callable` in an instance of `WebAsyncTask` and then use that to register the
|
||||
timeout and completion callbacks. Just like with `DeferredResult`, the timeout event can
|
||||
be handled and a value can be returned while the completion event is final.
|
||||
The `DeferredResult` type also provides methods such as `onTimeout(Runnable)`
|
||||
and `onCompletion(Runnable)`. See the Javadoc of `DeferredResult` for more
|
||||
details.
|
||||
|
||||
You can also register a `CallableProcessingInterceptor` or a
|
||||
`DeferredResultProcessingInterceptor` globally through the MVC Java config or the MVC
|
||||
namespace. Those interceptors provide a full set of callbacks and apply every time a
|
||||
`Callable` or a `DeferredResult` is used.
|
||||
When using a `Callable` you can wrap it with an instance of `WebAsyncTask`
|
||||
which also provides registration methods for timeout and completion.
|
||||
|
||||
[[mvc-ann-async-http-streaming]]
|
||||
==== HTTP Streaming
|
||||
|
||||
A controller method can use `DeferredResult` and `Callable` to produce its
|
||||
return value asynchronously and that can be used to implement techniques such as
|
||||
http://spring.io/blog/2012/05/08/spring-mvc-3-2-preview-techniques-for-real-time-updates/[long polling]
|
||||
where the server can push an event to the client as soon as possible.
|
||||
|
||||
What if you wanted to push multiple events on a single HTTP response?
|
||||
This is a technique related to "Long Polling" that is known as "HTTP Streaming".
|
||||
Spring MVC makes this possible through the `ResponseBodyEmitter` return value
|
||||
type which can be used to send multiple Objects, instead of one as is normally
|
||||
the case with `@ResponseBody`, where each Object sent is written to the
|
||||
response with an `HttpMessageConverter`.
|
||||
|
||||
Here is an example of that:
|
||||
|
||||
[source,java,indent=0]
|
||||
[subs="verbatim,quotes"]
|
||||
----
|
||||
@RequestMapping("/events")
|
||||
public ResponseBodyEmitter<String> handle() {
|
||||
ResponseBodyEmitter<String> emitter = new ResponseBodyEmitter<String>();
|
||||
// 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();
|
||||
----
|
||||
|
||||
Note that `ResponseBodyEmitter` can also be used as the body in a
|
||||
`ResponseEntity` in order to customize the status and headers of
|
||||
the response.
|
||||
|
||||
[[mvc-ann-async-sse]]
|
||||
==== Server-Sent Events
|
||||
|
||||
`SseEmitter` is a sub-class of `ResponseBodyEmitter` providing support for
|
||||
http://www.w3.org/TR/eventsource/[Server-Sent Events].
|
||||
Server-sent events is a just another variation on the same "HTTP Streaming"
|
||||
technique except events pushed from the server are formatted according to
|
||||
the W3C Servet-Sent Events specification.
|
||||
|
||||
Server-Sent Events can be used for their intended purpose, that is to push
|
||||
events from the server to clients. It is quite easy to do in Spring MVC and
|
||||
requires simply returning a value of type `SseEmitter`.
|
||||
|
||||
Note however that Internet Explorer does not support Server-Sent Events and
|
||||
that for more advanced web application messaging scenarios such as online games,
|
||||
collaboration, financial applicatinos, and others it's better to consider
|
||||
Spring's WebSocket support that includes SockJS-style WebSocket emulation
|
||||
falling back to a very wide range of browsers (including Internet Explorer)
|
||||
and also higher-level messaging patterns for interacting with clients through
|
||||
a publish-subscribe model within a more messaging-centric architecture.
|
||||
For further background on this see
|
||||
http://blog.pivotal.io/pivotal/products/websocket-architecture-in-spring-4-0[the following blog post].
|
||||
|
||||
[[mvc-ann-async-output-stream]]
|
||||
==== HTTP Streaming Directly To The OutputStream
|
||||
|
||||
`ResponseBodyEmitter` allows sending events by writing Objects to the
|
||||
response through an `HttpMessageConverter`. This is probably the most common
|
||||
case, for example when writing JSON data. However sometimes it is useful to
|
||||
bypass message conversion and write directly to the response `OutputStream`
|
||||
for example for a file download. This can be done with the help of the
|
||||
`StreamingResponseBody` return value type.
|
||||
|
||||
Here is an example of that:
|
||||
|
||||
[source,java,indent=0]
|
||||
[subs="verbatim,quotes"]
|
||||
----
|
||||
@RequestMapping("/download")
|
||||
public StreamingResponseBody handle() {
|
||||
return new StreamingResponseBody() {
|
||||
@Override
|
||||
public void writeTo(OutputStream outputStream) throws IOException {
|
||||
// write...
|
||||
}
|
||||
};
|
||||
}
|
||||
----
|
||||
|
||||
Note that `StreamingResponseBody` can also be used as the body in a
|
||||
`ResponseEntity` in order to customize the status and headers of
|
||||
the response.
|
||||
|
||||
|
||||
[[mvc-ann-async-configuration]]
|
||||
==== Configuration for Async Request Processing
|
||||
==== Configuring Asynchronous Request Processing
|
||||
|
||||
[[mvc-ann-async-configuration-servlet3]]
|
||||
===== Servlet 3 Async Config
|
||||
To use Servlet 3 async request processing, you need to update `web.xml` to version 3.0:
|
||||
===== Servlet Container Configuration
|
||||
For applications configured with a `web.xml` be sure to update to version 3.0:
|
||||
|
||||
[source,xml,indent=0]
|
||||
[subs="verbatim,quotes"]
|
||||
|
|
@ -2209,22 +2294,15 @@ To use Servlet 3 async request processing, you need to update `web.xml` to versi
|
|||
</web-app>
|
||||
----
|
||||
|
||||
The `DispatcherServlet` and any `Filter` configuration need to have the
|
||||
`<async-supported>true</async-supported>` sub-element. Additionally, any `Filter` that
|
||||
also needs to get involved in async dispatches should also be configured to support the
|
||||
ASYNC dispatcher type. Note that it is safe to enable the ASYNC dispatcher type for all
|
||||
filters provided with the Spring Framework since they will not get involved in async
|
||||
dispatches unless needed.
|
||||
Asynchronous support must be enabled on the `DispatcherServlet` through the
|
||||
`<async-supported>true</async-supported>` web.xml sub-element. Additionally
|
||||
any `Filter` that participates in asyncrequest processing must be configured
|
||||
to support the ASYNC dispatcher type. It should be safe to enable the ASYNC
|
||||
dispatcher type for all filters provided with the Spring Framework since they
|
||||
usually extend `OncePerRequestFilter` and that has runtime checks for whether
|
||||
the filter needs to be involved in async dispatches or not.
|
||||
|
||||
[WARNING]
|
||||
====
|
||||
Note that for some Filters it is absolutely critical to ensure they are mapped to
|
||||
be invoked during asynchronous dispatches. For example if a filter such as the
|
||||
`OpenEntityManagerInViewFilter` is responsible for releasing database connection
|
||||
resources and must be invoked at the end of an async request.
|
||||
|
||||
Below is an example of a propertly configured filter:
|
||||
====
|
||||
Below is some example web.xml configuration:
|
||||
|
||||
[source,xml,indent=0]
|
||||
[subs="verbatim,quotes"]
|
||||
|
|
@ -2253,18 +2331,20 @@ Below is an example of a propertly configured filter:
|
|||
|
||||
----
|
||||
|
||||
If using Servlet 3, Java based configuration, e.g. via `WebApplicationInitializer`,
|
||||
If using Servlet 3, Java based configuration for example via `WebApplicationInitializer`,
|
||||
you'll also need to set the "asyncSupported" flag as well as the ASYNC dispatcher type
|
||||
just like with `web.xml`. To simplify all this configuration, consider extending
|
||||
`AbstractDispatcherServletInitializer` or
|
||||
`AbstractAnnotationConfigDispatcherServletInitializer`, which automatically set those
|
||||
options and make it very easy to register `Filter` instances.
|
||||
`AbstractAnnotationConfigDispatcherServletInitializer` which automatically
|
||||
set those options and make it very easy to register `Filter` instances.
|
||||
|
||||
[[mvc-ann-async-configuration-spring-mvc]]
|
||||
===== Spring MVC Async Config
|
||||
The MVC Java config and the MVC namespace both provide options for configuring async
|
||||
request processing. `WebMvcConfigurer` has the method `configureAsyncSupport` while
|
||||
<mvc:annotation-driven> has an <async-support> sub-element.
|
||||
===== Spring MVC Configuration
|
||||
|
||||
The MVC Java config and the MVC namespace provide options for configuring
|
||||
asynchronous request processing. `WebMvcConfigurer` has the method
|
||||
`configureAsyncSupport` while `<mvc:annotation-driven>` has an
|
||||
`<async-support>` sub-element.
|
||||
|
||||
Those allow you to configure the default timeout value to use for async requests, which
|
||||
if not set depends on the underlying Servlet container (e.g. 10 seconds on Tomcat). You
|
||||
|
|
|
|||
Loading…
Reference in New Issue