重试 HTTP 请求(Java 11 - HttpClient)

Retry HTTP request (Java 11 - HttpClient)

问题

使用 Java 11 中的 HttpClient(JDK,不是 Apache),如何重试请求?

假设我想重试请求最多 10 次 如果它没有 return 状态代码 200 或抛出异常。


尝试

目前我正在编写 returned 未来循环中的重新安排,我想知道是否有更好或更优雅的方法。

CompletableFuture<HttpResponse<Foo>> task = client.sendAsync(request, bodyHandler);

for (int i = 0; i < 10; i++) {
    task = task.thenComposeAsync(response -> response.statusCode() == 200 ?
        CompletableFuture.completedFuture(response) :
        client.sendAsync(request, bodyHandler));
}

// Do something with 'task' ...

如果我们也为异常情况添加重试,我最终会得到

CompletableFuture<HttpResponse<Foo>> task = client.sendAsync(request, bodyHandler);

for (int i = 0; i < 10; i++) {
    task = task.thenComposeAsync(response ->
            response.statusCode() == 200 ?
            CompletableFuture.completedFuture(response) :
            client.sendAsync(request, bodyHandler))
        .exceptionallyComposeAsync(e ->
            client.sendAsync(request, bodyHandler));
}

// Do something with 'task' ...

不幸的是,似乎没有任何 composeAsync 可以同时触发常规完成和异常完成。有 handleAsync 但没有 compose,lambda 需要 return U 而不是 CompletionStage<U>


其他框架

为了 QA,我也对显示如何使用其他框架实现此目的的答案感兴趣,但我不会接受它们。

例如,我见过一个名为 Failsafe 的库,它可能会为此提供一个优雅的解决方案(参见 jodah.net/failsafe)。


参考

作为参考,这里有一些相关的Java文档链接:

你可以使用导入 org.eclipse.microprofile.faulttolerance.Retry;

如果您使用 microprofiler 或搜索等效项

@重试

我建议按照这些思路做一些事情(假设没有安全管理器):

public static int MAX_RESEND = 10;

public static void main(String[] args) {
    HttpClient client = HttpClient.newHttpClient();
    HttpResponse.BodyHandler<String> handler = HttpResponse.BodyHandlers.ofString();
    HttpRequest request = HttpRequest.newBuilder()
            .uri(URI.create("http://example.com/")).build();
    var response = client.sendAsync(request, handler)
            .thenComposeAsync(r -> tryResend(client, request, handler, 1, r));
    // do something with response...
}

public static <T> CompletableFuture<HttpResponse<T>>
        tryResend(HttpClient client, HttpRequest request, BodyHandler<T> handler,
                 int count, HttpResponse<T> resp) {
    if (resp.statusCode() == 200 || count >= MAX_RESEND) {
        return CompletableFuture.completedFuture(resp);
    } else {
        return client.sendAsync(request, handler)
                .thenComposeAsync(r -> tryResend(client, request, handler, count+1, r));
    }
}

如果您想同时处理正常情况和特殊情况,您可以这样做:

public static int MAX_RESEND = 5;

public static void main(String[] args) {
    HttpClient client = HttpClient.newHttpClient();
    BodyHandler<String> handler = BodyHandlers.ofString();
    HttpRequest request = HttpRequest.newBuilder()
            .uri(URI.create("http://example.com/")).build();
    CompletableFuture<HttpResponse<String>> response = 
        client.sendAsync(request, handler)
            .handleAsync((r, t) -> tryResend(client, request, handler, 1, r, t))
            .thenCompose(Function.identity());
    // do something with response ...
}

public static boolean shouldRetry(HttpResponse<?> r, Throwable t, int count) {
    if (r != null && r.statusCode() == 200 || count >= MAX_RESEND) return false;
    if (t instanceof ... ) return false;
    return true;
}

public static <T> CompletableFuture<HttpResponse<T>>
        tryResend(HttpClient client, HttpRequest request,
                  BodyHandler<T> handler, int count,
                  HttpResponse<T> resp, Throwable t) {
    if (shouldRetry(resp, t, count)) {
        return client.sendAsync(request, handler)
                .handleAsync((r, x) -> tryResend(client, request, handler, count + 1, r, x))
                .thenCompose(Function.identity());
    } else if (t != null) {
        return CompletableFuture.failedFuture(t);
    } else {
        return CompletableFuture.completedFuture(resp);
    }
}