blob: f496bab6eaa7907dc0af2c57621d4b0ee2a60320 [file] [log] [blame]
<?xml version="1.0"?>
<!--
Copyright (c) 2012, 2021 Oracle and/or its affiliates. All rights reserved.
This program and the accompanying materials are made available under the
terms of the Eclipse Public License v. 2.0, which is available at
http://www.eclipse.org/legal/epl-2.0.
This Source Code may also be made available under the following Secondary
Licenses when the conditions for such availability set forth in the
Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
version 2 with the GNU Classpath Exception, which is available at
https://www.gnu.org/software/classpath/license.html.
SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
-->
<!DOCTYPE chapter [<!ENTITY % ents SYSTEM "jersey.ent" > %ents;]>
<chapter xmlns="http://docbook.org/ns/docbook"
version="5.0"
xml:lang="en"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:xi="http://www.w3.org/2001/XInclude"
xmlns:xlink="http://www.w3.org/1999/xlink"
xsi:schemaLocation="http://docbook.org/ns/docbook http://docbook.org/xml/5.0/xsd/docbook.xsd
http://www.w3.org/1999/xlink http://www.w3.org/1999/xlink.xsd"
xml:id="async">
<title>Asynchronous Services and Clients</title>
<para>
This chapter describes the usage of asynchronous API on the client and server side. The term <emphasis>async</emphasis>
will be sometimes used interchangeably with the term <emphasis>asynchronous</emphasis> in this chapter.
</para>
<section>
<title>Asynchronous Server API</title>
<para>
Request processing on the server works by default in a synchronous processing mode, which means that a client
connection of a request is processed in a single I/O container thread. Once the thread processing the request
returns to the I/O container, the container can safely assume that the request processing is finished and that
the client connection can be safely released including all the resources associated with the connection. This model
is typically sufficient for processing of requests for which the processing resource method execution takes
a relatively short time. However, in cases where a resource method execution is known to take a long time to compute
the result, server-side asynchronous processing model should be used. In this model, the association between a
request processing thread and client connection is broken. I/O container that handles incoming request may no longer
assume that a client connection can be safely closed when a request processing thread returns. Instead a facility for
explicitly suspending, resuming and closing client connections needs to be exposed.
Note that the use of server-side asynchronous processing model will not improve the request processing time perceived
by the client. It will however increase the throughput of the server, by releasing the
initial request processing thread back to the I/O container while the request may still be waiting in a queue for
processing or the processing may still be running on another dedicated thread. The released I/O container thread
can be used to accept and process new incoming request connections.
</para>
<para>
The following example shows a simple asynchronous resource method defined using the new JAX-RS async API:
<example>
<title>Simple async resource</title>
<programlisting language="java" linenumbering="numbered">@Path("/resource")
public class AsyncResource {
@GET
public void asyncGet(@Suspended final AsyncResponse asyncResponse) {
new Thread(new Runnable() {
@Override
public void run() {
String result = veryExpensiveOperation();
asyncResponse.resume(result);
}
private String veryExpensiveOperation() {
// ... very expensive operation
}
}).start();
}
}</programlisting>
</example>
In the example above, a resource <literal>AsyncResource</literal>
with one <literal>GET</literal> method <literal>asyncGet</literal> is defined. The <literal>asyncGet</literal> method
injects a JAX-RS &jaxrs.container.AsyncResponse; instance using a JAX-RS &jaxrs.container.Suspended; annotation.
Please note that &lit.jaxrs.container.AsyncResponse; must be injected by the &lit.jaxrs.container.Suspended;
annotation and not by &jaxrs.core.Context; as &lit.jaxrs.container.Suspended; does not only inject response but also
says that the method is executed in the asynchronous mode. By the &lit.jaxrs.container.AsyncResponse; parameter into
a resource method we tell the Jersey runtime that the method is supposed to be invoked using the asynchronous
processing mode, that is the client connection should not be automatically closed by the underlying I/O container
when the method returns. Instead, the injected &lit.jaxrs.container.AsyncResponse; instance (that represents the
suspended client request connection) will be used to explicitly send the response back to the client using some other
thread. In other words, Jersey runtime knows that when the <literal>asyncGet</literal> method completes, the response
to the client may not be ready yet and the processing must be suspended and wait to be explicitly resumed with a
response once it becomes available. Note that the method <literal>asyncGet</literal> returns <literal>void</literal>
in our example. This is perfectly valid in case of an asynchronous JAX-RS resource method, even for a &jaxrs.GET;
method, as the response is never returned directly from the resource method as its return value. Instead, the response
is later returned using &lit.jaxrs.container.AsyncResponse; instance as it is demonstrated in the example. The
<literal>asyncGet</literal> resource method starts a new thread and exits from the method. In that state the
request processing is suspended and the container thread (the one which entered the resource method) is returned back
to the container's thread pool and it can process other requests. New thread started in the resource method may
execute an expensive operation which might take a long time to finish. Once a result is ready it is resumed using
the <literal>resume()</literal> method on the &lit.jaxrs.container.AsyncResponse; instance.
The resumed response is then processed in the new thread by Jersey in a same way as any other synchronous response,
including execution of filters and interceptors, use of exception mappers as necessary and sending the response
back to the client.
</para>
<para>
It is important to note that the asynchronous response (<literal>asyncResponse</literal> in the example)
does not need to be resumed from the thread started from the resource method. The asynchronous
response can be resumed even from different request processing thread as it is shown in the
the example of the &jaxrs.container.AsyncResponse; javadoc. In the javadoc example the
async response suspended from the &lit.http.GET; method is resumed later on from
the &lit.http.POST; method. The suspended async response is passed between requests using
a static field and is resumed from the other resource method running on a different request processing thread.
</para>
<para>
Imagine now a situation when there is a long delay between two requests and you would not like to let
the client wait for the response "forever" or at least for an unacceptable long time. In asynchronous processing
model, occurrences of such situations should be carefully considered with client connections not being automatically
closed when the processing method returns and the response needs to be resumed explicitly based on an event that
may actually even never happen. To tackle these situations asynchronous <emphasis>timeouts</emphasis> can be used.
</para>
<para>
The following example shows the usage of timeouts:
<example>
<title>Simple async method with timeout</title>
<programlisting language="java" linenumbering="numbered">@GET
public void asyncGetWithTimeout(@Suspended final AsyncResponse asyncResponse) {
asyncResponse.setTimeoutHandler(new TimeoutHandler() {
@Override
public void handleTimeout(AsyncResponse asyncResponse) {
asyncResponse.resume(Response.status(Response.Status.SERVICE_UNAVAILABLE)
.entity("Operation time out.").build());
}
});
asyncResponse.setTimeout(20, TimeUnit.SECONDS);
new Thread(new Runnable() {
@Override
public void run() {
String result = veryExpensiveOperation();
asyncResponse.resume(result);
}
private String veryExpensiveOperation() {
// ... very expensive operation that typically finishes within 20 seconds
}
}).start();
}</programlisting>
</example>
By default, there is no timeout defined on the suspended &lit.jaxrs.container.AsyncResponse; instance.
A custom timeout and timeout event handler may be defined using <literal>setTimeoutHandler(TimeoutHandler)</literal>
and <literal>setTimeout(long, TimeUnit)</literal> methods. The <literal>setTimeoutHandler(TimeoutHandler)</literal>
method defines the handler that will be invoked when timeout is reached. The handler resumes the response with the
response code 503 (from Response.Status.<literal>SERVICE_UNAVAILABLE</literal>).
A timeout interval can be also defined without specifying a custom timeout handler (using just the
<literal>setTimeout(long, TimeUnit)</literal> method).
In such case the default behaviour of Jersey runtime is to throw a <literal>ServiceUnavailableException</literal>
that gets mapped into 503, "Service Unavailable" HTTP error response, as defined by the JAX-RS specification.
</para>
<section>
<title>Asynchronous Server-side Callbacks</title>
<para>
As operations in asynchronous cases might take long time and they are not always finished within
a single resource method invocation, JAX-RS offers facility to register callbacks to be invoked
based on suspended async response state changes. In Jersey you can register two JAX-RS callbacks:
<itemizedlist>
<listitem><simpara>
&jaxrs.container.CompletionCallback; that is executed when request finishes or fails, and
</simpara></listitem>
<listitem><simpara>
&jaxrs.container.ConnectionCallback; executed when a connection to a client is closed or lost.
</simpara></listitem>
</itemizedlist>
<example>
<title>CompletionCallback example</title>
<programlisting language="java" linenumbering="numbered">@Path("/resource")
public class AsyncResource {
private static int numberOfSuccessResponses = 0;
private static int numberOfFailures = 0;
private static Throwable lastException = null;
@GET
public void asyncGetWithTimeout(@Suspended final AsyncResponse asyncResponse) {
asyncResponse.register(new CompletionCallback() {
@Override
public void onComplete(Throwable throwable) {
if (throwable == null) {
// no throwable - the processing ended successfully
// (response already written to the client)
numberOfSuccessResponses++;
} else {
numberOfFailures++;
lastException = throwable;
}
}
});
new Thread(new Runnable() {
@Override
public void run() {
String result = veryExpensiveOperation();
asyncResponse.resume(result);
}
private String veryExpensiveOperation() {
// ... very expensive operation
}
}).start();
}
}</programlisting>
</example>
A completion callback is registered using <literal>register(...)</literal> method
on the &lit.jaxrs.container.AsyncResponse; instance. A registered completion callback is bound only
to the response(s) to which it has been registered. In the example the &lit.jaxrs.container.CompletionCallback;
is used to calculate successfully processed responses, failures and to store last exception. This is only a
simple case demonstrating the usage of the callback. You can use completion callback to release the resources,
change state of internal resources or representations or handle failures. The method has an argument
<literal>Throwable</literal> which is set only in case of an error. Otherwise the parameter
will be <literal>null</literal>, which means that the response was successfully written. The callback is executed
only after the response is written to the client (not immediately after the response is resumed).
</para>
<para>
The &lit.jaxrs.container.AsyncResponse; <literal>register(...)</literal> method is overloaded and offers options
to register a single callback as an <literal>Object</literal> (in the example), as a <literal>Class</literal> or
multiple callbacks using varags.
</para>
<para>
As some async requests may take long time to process the client may decide to terminate its connection to the
server before the response has been resumed or before it has been fully written to the client. To deal with these
use cases a &lit.jaxrs.container.ConnectionCallback; can be used. This callback will be executed only if the
connection was prematurely terminated or lost while the response is being written to the back client. Note that
this callback will not be invoked when a response is written successfully and the client connection is closed
as expected. See javadoc of &jaxrs.container.ConnectionCallback; for more information.
</para>
</section>
<section xml:id="chunked-output">
<title>Chunked Output</title>
<para>Jersey offers a facility for sending response to the client in multiple more-or-less independent chunks using a
<emphasis>chunked output</emphasis>. Each response chunk usually takes some (longer) time to prepare before
sending it to the client. The most important fact about response chunks is that you want
to send them to the client immediately as they become available without waiting for the remaining chunks to become
available too. The first bytes of each chunked response consists of the HTTP headers that are sent to the client.
As noted above, the entity of the response is then sent in chunks as they become available.
Client knows that the response is going to be chunked, so it reads each chunk of the response separately,
processes it, and waits for more chunks to arrive on the same connection. After some time, the server generates
another response chunk and send it again to the client. Server keeps on sending response chunks until
it closes the connection after sending the last chunk when the response processing is finished.
</para>
<para>
In Jersey you can use &jersey.server.ChunkedOutput; to send response to a client in chunks. Chunks are strictly
defined pieces of a response body can be marshalled as a separate entities using Jersey/JAX-RS
&jaxrs.ext.MessageBodyWriter; providers. A chunk can be String, Long or JAXB bean serialized to XML or JSON or
any other defined custom type for which a &lit.jaxrs.ext.MessageBodyWriter; is available.
</para>
<para>
The resource method
that returns &lit.jersey.server.ChunkedOutput; informs the Jersey runtime that the response will be chunked
and that the processing works asynchronously as such. You do not need to inject
&lit.jaxrs.container.AsyncResponse; to start the asynchronous processing mode in this case.
Returning a &lit.jersey.server.ChunkedOutput; instance from the method is enough to indicate the asynchronous
processing. Response headers will be sent to a client when the resource method returns and the client will wait
for the stream of chunked data which you will be able to write from different thread using the same
&lit.jersey.server.ChunkedOutput; instance returned from the resource method earlier. The following example
demonstrates this use case:
<example>
<title>ChunkedOutput example</title>
<programlisting language="java" linenumbering="numbered"><![CDATA[@Path("/resource")
public class AsyncResource {
@GET
public ChunkedOutput<String> getChunkedResponse() {
final ChunkedOutput<String> output = new ChunkedOutput<String>(String.class);
new Thread() {
public void run() {
try {
String chunk;
while ((chunk = getNextString()) != null) {
output.write(chunk);
}
} catch (IOException e) {
// IOException thrown when writing the
// chunks of response: should be handled
} finally {
output.close();
// simplified: IOException thrown from
// this close() should be handled here...
}
}
}.start();
// the output will be probably returned even before
// a first chunk is written by the new thread
return output;
}
private String getNextString() {
// ... long running operation that returns
// next string or null if no other string is accessible
}
}]]></programlisting>
</example>
The example above defines a &lit.http.GET; method that returns a &lit.jersey.server.ChunkedOutput; instance.
The generic type of &lit.jersey.server.ChunkedOutput; defines the chunk types (in this case chunks are Strings).
Before the instance is returned a new thread is started that writes individual chunks into
the chunked output instance named <literal>output</literal>. Once the original
thread returns from the resource method, Jersey runtime writes headers to the container response but does not
close the client connection yet and waits for the response data to be written to the chunked
<literal>output</literal>.
New thread in a loop calls the method <literal>getNextString()</literal> which returns a
next String or &lit.null; if no other String exists (the method could for example load latest data
from the database). Returned Strings are written to the chunked <literal>output</literal>. Such a written
chunks are internally written to the container response and client can read them. At the end the
chunked output is closed which determines the end of the chunked response. Please note that you must close
the output explicitly in order to close the client connection as Jersey does not implicitly know when
you are finished with writing the chunks.
</para>
<para>
A chunked output can be processed also from threads created from another request as it is explained in the
sections above. This means that one resource method may e.g. only return a &lit.jersey.server.ChunkedOutput;
instance and other resource method(s) invoked from another request thread(s) can write data into the chunked
output and/or close the chunked response.
</para>
</section>
</section>
<section>
<title>Client API</title>
<para>The client API supports asynchronous processing too. Simple usage of asynchronous client API is shown in the
following example:
<example>
<title>Simple client async invocation</title>
<programlisting language="java" linenumbering="numbered"><![CDATA[final AsyncInvoker asyncInvoker = target().path("http://example.com/resource/")
.request().async();
final Future<Response> responseFuture = asyncInvoker.get();
System.out.println("Request is being processed asynchronously.");
final Response response = responseFuture.get();
// get() waits for the response to be ready
System.out.println("Response received.");]]></programlisting>
</example>
The difference against synchronous invocation is that the http method call <literal>get()</literal>
is not called on &jaxrs.client.SyncInvoker; but on &jaxrs.client.AsyncInvoker;. The
&lit.jaxrs.client.AsyncInvoker; is returned from the call of method
<literal>Invocation.Builder.async()</literal> as shown above. &lit.jaxrs.client.AsyncInvoker;
offers methods similar to &lit.jaxrs.client.SyncInvoker; only these methods do not return a response
synchronously. Instead a <literal>Future&lt;...&gt;</literal> representing response data is returned.
These method calls also return immediately without waiting for the actual request to complete.
In order to get the response of the invoked <literal>get()</literal> method, the
<literal>responseFuture.get()</literal> is invoked which waits for the response to be finished
(this call is blocking as defined by the Java SE <literal>Future</literal> contract).
</para>
<para>
Asynchronous Client API in JAX-RS is fully integrated in the fluent JAX-RS Client API flow, so that
the async client-side invocations can be written fluently just like in the following example:
<example>
<title>Simple client fluent async invocation</title>
<programlisting language="java" linenumbering="numbered"><![CDATA[final Future<Response> responseFuture = target().path("http://example.com/resource/")
.request().async().get();]]></programlisting>
</example>
</para>
<para>
To work with asynchronous results on the client-side, all standard <literal>Future</literal> API facilities
can be used. For example, you can use the <literal>isDone()</literal> method
to determine whether a response has finished to avoid the use of a blocking call to <literal>Future.get()</literal>.
</para>
<section>
<title>Asynchronous Client Callbacks</title>
<para>Similarly to the server side, in the client API you can register asynchronous callbacks too. You can use
these callbacks to be notified when a response arrives instead of waiting for the
response on <literal>Future.get()</literal> or checking the status by <literal>Future.isDone()</literal> in
a loop.
A client-side asynchronous invocation callback can be registered as shown in the following example:
<example>
<title>Client async callback</title>
<programlisting language="java" linenumbering="numbered"><![CDATA[final Future<Response> responseFuture = target().path("http://example.com/resource/")
.request().async().get(new InvocationCallback<Response>() {
@Override
public void completed(Response response) {
System.out.println("Response status code "
+ response.getStatus() + " received.");
}
@Override
public void failed(Throwable throwable) {
System.out.println("Invocation failed.");
throwable.printStackTrace();
}
});]]></programlisting>
</example>
The registered callback is expected to implement the &jaxrs.client.InvocationCallback; interface that defines
two methods.
First method <literal>completed(Response)</literal> gets invoked when an invocation successfully
finishes. The result response is passed as a parameter to the callback method. The second method
<literal>failed(Throwable)</literal> is invoked in case the invocation fails and the exception describing
the failure is passed to the method as a parameter. In this case since the callback generic type is
&lit.jaxrs.core.Response;, the <literal>failed(Throwable)</literal> method would only invoked in case
the invocation fails because of an internal client-side processing error. It would not be invoked
in case a server responds with an HTTP error code, for example if the requested resource
is not found on the server and HTTP <literal>404</literal> response code is returned. In such case
<literal>completed(Response)</literal> callback method would be invoked and the response passed to the method
would contain the returned error response with HTTP <literal>404</literal> error code. This is a special
behavior in case the generic callback return type is &lit.jaxrs.core.Response;. In the next example an
exception is thrown (or <literal>failed(Throwable)</literal> method on the invocation callback is invoked)
even in case a non-<literal>2xx</literal> HTTP error code is returned.
</para>
<para>
As with the synchronous client API, you can retrieve the response entity as a Java type directly without
requesting a &lit.jaxrs.core.Response; first. In case of an &lit.jaxrs.client.InvocationCallback;, you need
to set its generic type to the expected response entity type instead of using the &lit.jaxrs.core.Response;
type as demonstrated in the example below:
<example>
<title>Client async callback for specific entity</title>
<programlisting language="java" linenumbering="numbered"><![CDATA[final Future<String> entityFuture = target().path("http://example.com/resource/")
.request().async().get(new InvocationCallback<String>() {
@Override
public void completed(String response) {
System.out.println("Response entity '" + response + "' received.");
}
@Override
public void failed(Throwable throwable) {
System.out.println("Invocation failed.");
throwable.printStackTrace();
}
});
System.out.println(entityFuture.get());]]></programlisting>
</example>
Here, the generic type of the invocation callback information is used to unmarshall the HTTP response content
into a desired Java type.
</para>
<important>
<para>
Please note that in this case the method <literal>failed(Throwable throwable)</literal> would be invoked even
for cases when a server responds with a non HTTP-<literal>2xx</literal> HTTP error code. This is because in this
case the user does not have any other means of finding out that the server returned an error response.
</para>
</important>
</section>
<section>
<title>Chunked input</title>
<para>
In an <link linkend="chunked-output">earlier section</link> the &lit.jersey.server.ChunkedOutput; was
described. It was shown how to use a chunked output on the server. In order to read chunks on the client the
&jersey.client.ChunkedInput; can be used to complete the story.
</para>
<para>
You can, of course, process input on the client as a standard input stream but if you would like to
leverage Jersey infrastructure to provide support of translating message chunk data into Java types
using a &lit.jersey.client.ChunkedInput; is much more straightforward. See the usage of the
&lit.jersey.client.ChunkedInput; in the following example:
<example>
<title>ChunkedInput example</title>
<programlisting language="java" linenumbering="numbered"><![CDATA[final Response response = target().path("http://example.com/resource/")
.request().get();
final ChunkedInput<String> chunkedInput =
response.readEntity(new GenericType<ChunkedInput<String>>() {});
String chunk;
while ((chunk = chunkedInput.read()) != null) {
System.out.println("Next chunk received: " + chunk);
}
]]></programlisting>
</example>
The response is retrieved in a standard way from the server. The entity is read as a
&lit.jersey.client.ChunkedInput; entity. In order to do that the &jaxrs.core.GenericEntity; is used to preserve
a generic information at run time. If you would not use &lit.jaxrs.core.GenericEntity;, Java language generic type
erasure would cause that the generic information would get lost at compile time and an exception would be thrown
at run time complaining about the missing chunk type definition.
</para>
<para>
In the next lines in the example, individual chunks are being read from the response. Chunks can come with some
delay, so they will be written to the console as they come from the server. After receiving last chunk the
&lit.null; will be returned from the <literal>read()</literal> method. This will mean that the server has sent
the last chunk and closed the connection. Note that the <literal>read()</literal> is a blocking operation and the
invoking thread is blocked until a new chunk comes.
</para>
<para>
Writing chunks with &lit.jersey.server.ChunkedOutput; is simple, you only call method <literal>write()</literal>
which writes exactly one chunk to the output. With the input reading it is slightly more complicated. The
&lit.jersey.client.ChunkedInput; does not know how to distinguish chunks in the byte stream unless being told by
the developer. In order to define custom chunks boundaries,
the &lit.jersey.client.ChunkedInput; offers possibility to register a &jersey.client.ChunkParser; which
reads chunks from the input stream and separates them. Jersey provides several chunk parser implementations and
you can implement your own parser to separate your chunks if you need. In our example above the default parser
provided by Jersey is used that separates chunks based on presence of a <literal>\r\n</literal> delimiting
character sequence.
</para>
<para>
Each incoming input stream is firstly parsed by the &lit.jersey.client.ChunkParser;, then each chunk is processed
by the proper &jaxrs.ext.MessageBodyReader;.
You can define the media type of chunks to aid the selection of a proper &lit.jaxrs.ext.MessageBodyReader; in
order to read chunks correctly into the requested entity types (in our case into Strings).
</para>
</section>
</section>
</chapter>