Coming Up for Air

Asynchronous JAX-RS

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.

— Marek Potociar

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.

Update #3

After talking to the Jersey team, they suggested this:

1
2
3
4
5
6
7
    @POST
    @Produces(MediaType.APPLICATION_JSON)
    @Consumes(MediaType.TEXT_PLAIN)
    @ManagedAsync
    public void async(final String text, @Suspended final AsyncResponse ar) {
        ar.resume(doSomethingReallySlow(text));
    }

That’s much smaller and simpler than the example above. The only caveat is that @ManagedAsync is a Jersey-specific feature, so this code is not portable to other JAX-RS implementations. If you’re OK with that, then feel free. If not, I’d suggest implementing getExecutorService() with something production-ready.

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

tags: Java

Quotes

Sample quote

Quote source