2

So I have a web-app in Spring Boot, and there is a part where I make many HTTP requests to an API, and it seems to timeout if too many requests are made. I heard that switching from Synchronous to Asynchronous requests might help this issue.

Using OkHttp, this is what my Synchronous GET request looks like:

private JSONObject run(String url) throws Exception {
    Request newRequest = new Request.Builder()
            .url(url)
            .addHeader("Authorization", token)
            .build();

    try (Response response = client.newCall(newRequest).execute()) {
        if (!response.isSuccessful()) throw new IOException("Unexpected code " + response);
        return new JSONObject(response.body().string());

    }
}

I return the response as a JSON object from parsing the response body. However, in trying to use an OkHttp asynchronous call, it seems that I can't use the same approach. This is what I have so far:

public void runAsync(String url) throws Exception {
    Request request = new Request.Builder()
            .url(url)
            .addHeader("Authorization", token)
            .build();

    client.newCall(request).enqueue(new Callback() {
        @Override public void onFailure(Call call, IOException e) {
            e.printStackTrace();
        }

        @Override public void onResponse(Call call, Response response) throws IOException {
            if (!response.isSuccessful()) throw new IOException("Unexpected code " + response);

            System.out.println(response.body().string());
        }
    });
}

I cannot simply return the result as a JSON, as the response is wrapped inside of the Callback method, which has a return value of void. Any ideas on how I might achieve similar results to my Synchronous GET of extracting the response?

Alex Hermstad
  • 330
  • 5
  • 14
  • This doesn't really get any of the benefits of changing to an asynchronous call. Ideally you would return something like a Future to the web framework that is calling you and it would handle the completion of the future asynchronously. If you have to return a completed result to your caller for a single request you are making, then no matter what its going to be a synchronous call. The other possibility is that your final result depends on many requests, in which case having those execute synchronously and concurrently would help. – Yuri Schimke Feb 18 '17 at 07:26
  • Hmm interesting. In my case, there are many HTTP requests being made with the goal of parsing and aggregating all the results and storing the contents in a string. It sounds like Synchronous Concurrent requests might be what I need to do. Would doing this asynchronous method not be a good fit for my problem then? – Alex Hermstad Feb 18 '17 at 08:11
  • Actually that still sounds ideal. You can have the asynchronous tasks fetching data concurrently and also parse concurrently then block synchronously until all individual results are complete – Yuri Schimke Feb 18 '17 at 08:19
  • I see! Well thanks for your help, I'm going to spend some time trying to wrap my head around futures and concurrency, and then I'll try and implement what you recommended in your post. – Alex Hermstad Feb 18 '17 at 08:50

1 Answers1

6

I'm not a user of Spring Boot, so this is not a complete answer. But if it supports returning a Future, then it's trivial to bridge from OkHttp Callback to a Future.

This may be relevant https://spring.io/guides/gs/async-method/

As for producing the future

public class OkHttpResponseFuture implements Callback {
  public final CompletableFuture<Response> future = new CompletableFuture<>();

  public OkHttpResponseFuture() {
  }

  @Override public void onFailure(Call call, IOException e) {
    future.completeExceptionally(e);
  }

  @Override public void onResponse(Call call, Response response) throws IOException {
    future.complete(response);
  }
}

And then enqueue the job something like

  OkHttpResponseFuture callback = new OkHttpResponseFuture();
  client.newCall(request).enqueue(callback);

  return callback.future.thenApply(response -> {
    try {
      return convertResponse(response);
    } catch (IOException e) {
      throw Throwables.propagate(e);
    } finally {
      response.close();
    }
  });

If you have multiple requests to process you can submit them separately and then wait on all results being available before combining and returning

  public static <T> CompletableFuture<List<T>> join(List<CompletableFuture<T>> futures) {
    CompletableFuture[] cfs = futures.toArray(new CompletableFuture[futures.size()]);

    return CompletableFuture.allOf(cfs)
        .thenApply(v -> combineIndividualResults(c));
  }
Yuri Schimke
  • 12,435
  • 3
  • 35
  • 69