| <?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<...></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> |