Skip to content

Commit bd0e347

Browse files
committed
feat(communication): support mapped retried HTTP calls
1 parent 2c0ee6c commit bd0e347

3 files changed

Lines changed: 89 additions & 4 deletions

File tree

communication/src/main/java/datadog/communication/http/HttpRetryPolicy.java

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,13 @@ public class HttpRetryPolicy implements AutoCloseable {
5454
private final double delayFactor;
5555
private final boolean suppressInterrupts;
5656

57-
private HttpRetryPolicy(
57+
/**
58+
* Creates a retry policy.
59+
*
60+
* <p>Protected so products with a stricter cross-SDK retry contract can reuse the shared HTTP
61+
* retry loop while supplying their own response, exception, and backoff rules.
62+
*/
63+
protected HttpRetryPolicy(
5864
int retriesLeft, long delay, double delayFactor, boolean suppressInterrupts) {
5965
this.retriesLeft = retriesLeft;
6066
this.delay = delay;

communication/src/main/java/datadog/communication/http/OkHttpUtils.java

Lines changed: 29 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
import java.util.List;
2121
import java.util.Map;
2222
import javax.annotation.Nullable;
23+
import okhttp3.Call;
2324
import okhttp3.ConnectionPool;
2425
import okhttp3.ConnectionSpec;
2526
import okhttp3.Credentials;
@@ -405,19 +406,39 @@ public void writeTo(BufferedSink sink) throws IOException {
405406
public static Response sendWithRetries(
406407
OkHttpClient httpClient, HttpRetryPolicy.Factory retryPolicyFactory, Request request)
407408
throws IOException {
409+
return sendWithRetries((Call.Factory) httpClient, retryPolicyFactory, request);
410+
}
411+
412+
public static Response sendWithRetries(
413+
Call.Factory callFactory, HttpRetryPolicy.Factory retryPolicyFactory, Request request)
414+
throws IOException {
415+
return sendWithRetries(callFactory, retryPolicyFactory, request, response -> response);
416+
}
417+
418+
public static <T> T sendWithRetries(
419+
Call.Factory callFactory,
420+
HttpRetryPolicy.Factory retryPolicyFactory,
421+
Request request,
422+
ResponseMapper<T> responseMapper)
423+
throws IOException {
408424
try (HttpRetryPolicy retryPolicy = retryPolicyFactory.create()) {
409425
while (true) {
426+
Response response = null;
410427
try {
411-
Response response = httpClient.newCall(request).execute();
428+
response = callFactory.newCall(request).execute();
412429
if (response.isSuccessful()) {
413-
return response;
430+
return responseMapper.map(response);
414431
}
415432
if (!retryPolicy.shouldRetry(response)) {
416-
return response;
433+
return responseMapper.map(response);
417434
} else {
418435
closeQuietly(response);
436+
response = null;
419437
}
420438
} catch (Exception ex) {
439+
if (response != null) {
440+
closeQuietly(response);
441+
}
421442
if (!retryPolicy.shouldRetry(ex)) {
422443
throw ex;
423444
}
@@ -428,6 +449,11 @@ public static Response sendWithRetries(
428449
}
429450
}
430451

452+
@FunctionalInterface
453+
public interface ResponseMapper<T> {
454+
T map(Response response) throws IOException;
455+
}
456+
431457
private static void closeQuietly(Response response) {
432458
try {
433459
response.close();
Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
package datadog.communication.http;
2+
3+
import static datadog.communication.http.OkHttpUtils.sendWithRetries;
4+
import static org.junit.jupiter.api.Assertions.assertEquals;
5+
6+
import java.net.ConnectException;
7+
import java.util.concurrent.atomic.AtomicInteger;
8+
import okhttp3.Call;
9+
import okhttp3.OkHttpClient;
10+
import okhttp3.Request;
11+
import okhttp3.ResponseBody;
12+
import okhttp3.mockwebserver.MockResponse;
13+
import okhttp3.mockwebserver.MockWebServer;
14+
import org.junit.jupiter.api.Test;
15+
16+
class OkHttpUtilsRetryTest {
17+
18+
@Test
19+
void retriesResponseMappingFailureThroughCallFactory() throws Exception {
20+
final MockWebServer server = new MockWebServer();
21+
final OkHttpClient client = new OkHttpClient();
22+
final AtomicInteger mappingAttempts = new AtomicInteger();
23+
server.enqueue(new MockResponse().setBody("first"));
24+
server.enqueue(new MockResponse().setBody("second"));
25+
server.start();
26+
27+
try {
28+
final Request request = new Request.Builder().url(server.url("/configuration")).build();
29+
30+
final String body =
31+
sendWithRetries(
32+
(Call.Factory) client,
33+
new HttpRetryPolicy.Factory(1, 0, 1),
34+
request,
35+
response -> {
36+
try (ResponseBody responseBody = response.body()) {
37+
if (mappingAttempts.getAndIncrement() == 0) {
38+
throw new ConnectException("response body could not be mapped");
39+
}
40+
return responseBody.string();
41+
}
42+
});
43+
44+
assertEquals("second", body);
45+
assertEquals(2, mappingAttempts.get());
46+
assertEquals(2, server.getRequestCount());
47+
} finally {
48+
client.dispatcher().executorService().shutdownNow();
49+
client.connectionPool().evictAll();
50+
server.shutdown();
51+
}
52+
}
53+
}

0 commit comments

Comments
 (0)