Asynchronous JAX-RS
Wednesday, December 19, 2012 |Recently, I had to add support for asynchronous REST calls to the GlassFish REST interface to satisfy some customer requirements. In process of doing so, I learned something pretty interesting: while asynchronous REST may mean different things to different people (e.g., I’m pretty sure Atmosphere provides some sort of REST asynchrony, but I’m not sure what UPDATE #1: As noted in the comments, I know next to nothing about Atmosphere. I mention it here only as some weak attempt at completeness that is, in hind sight, a really bad choice), implementing an async REST resource with JAX-RS is really quite simple. In this post, we’ll take a look at two different approaches to "asynchronous" REST.
For the second post in a row, I have scare quotes in my teaser. For the second post in a row, let me explain why. :) In terms of JAX-RS, "asynchronous" really has two different…meanings, depending on the context in which it’s used. There’s server-side, and there’s client-side, and they’re not quite the same thing.
Let me quote from a conversation I had with the Jersey team:
The type of asynchrony supported in JAX-RS is not something observable on the wire (e.g. if your resource method is asynchronous, it does not result in a client connection being closed with an HTTP 201 response which would force client to actively poll for the actual response later). The asynchrony is only an "implementation" detail of either party - client or server and relates to the threading model of that party. Since threading models of client and server do not directly influence the programming model of the other party, it does not matter whether you consume asynchronous service with a synchronous client and vice versa - the communication between the two is not affected.
Makes sense? Let’s dive in and clear things, with a look at server-side first:
Server-side
To start, let’s look at an "asynchronous" REST resource:
1
2
3
4
5
6
7
8
9
10
11
12
13
@POST
@Produces(MediaType.APPLICATION_JSON)
@Consumes(MediaType.TEXT_PLAIN)
public void async(final String text,
@Suspended final AsyncResponse ar) {
getExecutorService().submit(new Runnable() {
@Override
public void run() {
String result = doSomethingReallySlow(text);
ar.resume(result);
}
});
}
UPDATE #1: Please see Gerard’s comments below about performance regarding the ExecutorService
UPDATE #2: Based on reader feedback, I have hidden the details of the ExecutorService
creation as I don’t want to distract from the main point.
Despite what it may sound like, server-side asynchrony does not mean (at least in the JAX-RS context) that the server disconnects from the client, then pushes the result to it eventually. What this resource does, though, is accept the request, then, through the use of the java.util.concurrent.Executors
framework, pushes the request processing to a background thread. This allows the selector thread, the one handling network requests, to wait for an answer another request. Once the processing is finished, the Runnable
we created will return the response to the client using the AsyncResponse
object we injected as a method parameter. In a nutshell, the REST resource does its work on a separate thread, then tells JAX-RS that it has a response. The client, though, continues to block. There is no on the wire difference.
That was pretty easy. What about client-side? Is that more like what we usually think of when we say "asynchronous"?
Client-side
The short answer is, "Yes". :) Like what we saw on the server-side, though, there’s not on-the-wire difference here, and the asynchronous nature is really a…trick of the JAX-RS Client API. Let’s see some code, then I’ll explain:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public void asyncRestClient() throws JSONException, InterruptedException {
getClient()
.target(restUrl)
.request(MediaType.APPLICATION_JSON)
.async()
.post(Entity.entity("Here is some text", MediaType.TEXT_PLAIN),
new InvocationCallback<Response>() {
@Override
public void completed(Response response) {
processResponse(response.readEntity(JSONObject.class));
}
@Override
public void failed(ClientException ce) {
// Do something
}
});
}
In this simple example, we have a couple of changes to how we use the JAX-RS Client API. First, we make a call to async()
, and, second, we pass an instance of InvocationCallback
to post()
. What happens here, then, is the Client creates a background thread to handle the request. This thread sends the request, then blocks, waiting for the response. Once the response is received, it calls completed()
on our InvocationCallback
object. At that point, we read the entity off the Response
, and pass it along to a business method for processing. If an error occurs, the Client will call failure()
, at which point we would handle the error in a manner appropriate for our context.
In both of these case, server-side and client-side, adding asynchrony is pretty simple. While frameworks like Atmosphere (which calls JAX-RS' asynchronous API "strange" :) may provide much more sophisticated asynchronous support (and it seems to me, from what little I know of Atmosphere, to be more focused on SSE, though please correct me if I’m wrong. UPDATE #1: which JFA does in the comments), unless you really need it, you need not do much extra work. JAX-RS has nice (and easy) support built right into the framework. Give it a whirl and see if it fits your needs.
And speaking of SSE, my next post will show a non-GlassFish-specific implementation of server-sent events and JAX-RS. Stay tuned. :P