Asynchronous HTTP Client in Play Framework

Published on 29 June 2010

I just finished migrating Play’s http client to Ning’s own asynchronous library. What does it mean? A lot.

When you want to retrieve data from a different server, your web application behaves like a web client. Rather than serving a resource to a client, it requests one from a different server. For example, that’s what you would do if you want to interact with the Twitter API or if you want to build a web based feed reader.

HTTP Client Calls in Play 1.0

Play has a pretty cool http client library - pretty cool in the sense that it’s very simple to use compared to what you usually get in Java. I can get the content of a resource by calling:

String body = WS.url("http://erwan.jp/").get().getString();

In the same fashion, I can do a post and retrieve the JSON result:

JsonElement response = WS.url("http://api.server.tld/new").body("content").post().getJson();

As a comparison, see how it works with Apache HttpClient.

However there’s a big flaw in this API: it can only be used synchronously. That means that your Java thread will be blocked until the response from the server is received. Depending on the server it could take several seconds, so it is a real issue. If you need to do 5 calls then work on the result, you will have to wait for the previous call to be done before you can launch the second. In Play you can use jobs to have your calls executed in a separate thread, but it’s a burder to have to create jobs for that and kind of defeats the purpose of using play.libs.WS.

Play 1.1: Introducing Asynchronous Calls

Since a commit I did last week, additional methods are available on the request object. Now you can do:

Future<HttpResponse> response = WS.url("http://erwan.jp/").getAsync();

Now your call is done in a thread - you can manipulate your Future object to test if the result is available, to cancel the call or to block the thread until the result is ready. Let’s say you need to do 3 API calls, and then use the results together. With the synchronous library you have to serialize the calls, but now you can do the calls in parallel:

// Launch all three calls in parallel
Future<HttpResponse> future1 = WS.url("http://server/api/one").getAsync();
Future<HttpResponse> future2 = WS.url("http://server/api/two").getAsync();
Future<HttpResponse> future3 = WS.url("http://server/api/three").getAsync();
// Now that the calls are launched in separate threads, wait for the results
Document xml1 = future1.get().getXml();
Document xml2 = future2.get().getXml();
Document xml3 = future3.get().getXml();

Launch the calls in parallel means a much quicker response, so that’s an improvement in the case we have several calls to do. But we are still blocking one of Play’s threads until the three calls are done.

Avoid blocking Play’s threads

Before we go on, it’s important to understand Play’s threads pool. It’s configurable but usually there is one IO thread and 2 execution threads. When the IO thread gets a request it passes it to one of the execution thread, the execution thread calculates it and passes it back to the IO thread that will queue it to serve it to the client who originated the request.

That means that the execution of the action on the controller will block one thread. So if you do a synchronous web service call from your action, you’ll block a precious thread for all the time you wait for the remote server to respond to your server. When all threads are blocked waiting for a result, other requests get queued. Your site’s response is slow, and your users are unhappy.

So here is how you can free the thread while you wait for the remote server to respond. The following code is an action, within a controller:

private static Future<HttpResponse> response;

public static void mirrorFeed() throws Exception {
    if (request.isNew) {
        response = WS.url("http://planet.playframework.org/feed").getAsync();
        waitFor(response);
    } else {
        renderXml(response.get().getXml());
    }
}

This can be tricky to understand at a first glance, so here is the process.

  • Your action gets called a first time: request.isNew is true. An asynchronous HTTP call is made to the playframework.org server.
  • The “waitFor(Future<?>)” (static method on the Controller class) tells Play to wait until the response is received.
  • When the answer is ready, Play will call the action again. This time, request.isNew is false. We know that the Future is ready so we can do a get() to retrieve the HttpResponse instance. Here we’re just serving it to the user, but you see how we could parse the feed to use just some information in our response.

So here you go: the use of an asynchronous call prevents to block an execution thread, and the performance of your application will not be affected by the response time of the remote server.

When you should still use a job

While the waitFor trick prevents you from blocking a Play thread, your user still has to wait for the web service call to be back before he gets his response. In other words, your application may feel slow to the user.

When the information you need is general enough, for example when it comes from a public feed, it can still be a better approach to keep the information up-to-date using a job. At the time the user will fetch the information it will not be the most recent, but the page will be served to the user much quicker.

This is what I am doing for the Twitter box on Planet Play: I don’t pull it for each visitor at request time (that would be insane), but I have a Job refreshing the information every 5 minutes. Any page rendered will never get anything older than 5 minutes.

@Every("5mn")
public class TwitterJob extends Job {
    public void doJob() {
        try {
            TwitterSearch.refresh("playframework");
        } catch (UnsupportedEncodingException e) {
            Logger.error("Error refreshing Twitter search");
        }
    }
}

The information is then stored in cache and retrieved to render the front page.

TAGS: api  hacking  play framework