Handling asynchronous results
Why asynchronous results?
Until now, we have been able to compute the result to send to the web client directly. This is not always the case: the result may depend on an expensive computation or on a long web service call.
Because of the way Wisdom works, action code must be as fast as possible (i.e. non blocking). So what should we return from our action if we are not yet able to compute the result? We should return an asynchronous result, i.e. a result that will be computed in the background and sent to the client when it’s done!
An asynchronous result will eventually be redeemed with a value of type Result. By using a AsyncResult instead of
a normal Result, we are able to return from our action quickly without blocking anything. Wisdom will then serve
the result as soon as the promise is redeemed.
The web client will be blocked while waiting for the response but nothing will be blocked on the server, and server resources can be used to serve other clients.
NOTE:*Why is it important to not block the server?* Wisdom is based on a NIO model and uses very few threads. Blocking one of them drastically reduces the velocity of the server.
How to create an Asynchronous Result ?
To create an AsyncResult, you just need to write the heavy computation in a Callable<Result> object:
include::{sourcedir}/controllers/AsyncExample.java[tags=async]
|
Important
|
The method signature must still return a Result
|
Streaming HTTP responses
Standard responses and Content-Length header
Since HTTP 1.1, to keep a single connection open to serve several HTTP requests and responses,
the server must send the appropriate Content-Length HTTP header along with the response.
By default, when you send a simple result, such as:
include::{sourcedir}/controllers/AsyncExample.java[tags=hello]
You are not specifying a Content-Length header. Of course, because the content you are sending is well known,
Wisdom is able to compute the content size for you and to generate the appropriate header.
Note that for text-based content this is not as simple as it looks, since the Content-Length header must be computed
according the encoding used to translate characters to bytes.
To be able to compute the Content-Length header properly, Wisdom must consume the whole response data and load its
content into memory. Obviously it has some drastic limitations.
Serving files
If it’s not a problem to load the whole content into memory for simple content what about a large data set? Let’s say we want to send back a large file to the web client.
Wisdom provides methods to ease the manipulation of file:
include::{sourcedir}/controllers/AsyncExample.java[tags=file]
|
Note
|
Files are sent chunk by chunk avoiding copying the whole file in memory. This means you can serve very large files. See Chunked Transfer Encoding for more details. |
Additionally this helper will also compute the Content-Type header from the file name.
If you want to force the browser to download the file (and not just display it), use the following version to set the
Content-Disposition header.
include::{sourcedir}/controllers/AsyncExample.java[tags=file-attachment]
When you passe true as second argument, Wisdom sets the Content-Disposition header to Content-Disposition:
attachment; filename=wisdom.pdf.
Serving URLs
Wisdom also supports serving urls directly. Handling URLs is a bit more complicated as the size in unknown, and so chunk transfer is required. Don’t worry Wisdom handles everything for you:
include::{sourcedir}/controllers/AsyncExample.java[tags=url]
Serving URLs is useful when loading resources packaged with the application:
include::{sourcedir}/controllers/AsyncExample.java[tags=url-manifest]
As for files, Wisdom tries to deduce the Content-Type from the URL. However, as shown, it can be overridden.
|
Note
|
As the content size in unknown, the web browser is not able to display a proper download progress bar. |
Serving Input Streams
You can also return input streams directly. As for URLs, the content length is unknown and so it will be transferred
chunk by chunk. Unlike URLs and Files, Wisdom does not try to determine the Content-Type so,
you must set it explicitly:
include::{sourcedir}/controllers/AsyncExample.java[tags=url-manifest-as-stream]
Avoiding chunks
If you don’t want to transfer the data using chunks, you can use the following variants:
include::{sourcedir}/controllers/AsyncExample.java[tags=no-chunks]
|
Important
|
Avoiding chunks means copying the whole data into memory, this has a major impact on the memory used by your
server and may lead to OutOfMemory issues.
|