From 304aa7e793008c1443d024e023bd57aa2327bb25 Mon Sep 17 00:00:00 2001 From: Richard Marmorstein Date: Thu, 13 May 2021 15:54:04 -0400 Subject: [PATCH] Retries and telemetry --- src/main/java/com/stripe/net/HttpClient.java | 73 +++++++++++++++---- .../stripe/net/LiveStripeResponseGetter.java | 2 +- .../java/com/stripe/net/RequestTelemetry.java | 2 +- .../java/com/stripe/net/StripeResponse.java | 12 ++- .../com/stripe/net/StripeResponseStream.java | 11 ++- 5 files changed, 75 insertions(+), 25 deletions(-) diff --git a/src/main/java/com/stripe/net/HttpClient.java b/src/main/java/com/stripe/net/HttpClient.java index a8838d6239b..84618660afb 100644 --- a/src/main/java/com/stripe/net/HttpClient.java +++ b/src/main/java/com/stripe/net/HttpClient.java @@ -49,14 +49,13 @@ public StripeResponseStream requestStream(StripeRequest request) throws StripeEx "streamingRequest is unimplemented for this HttpClient"); } - /** - * Sends the given request to Stripe's API, handling telemetry if not disabled. - * - * @param request the request - * @return the response - * @throws StripeException If the request fails for any reason - */ - public StripeResponse requestWithTelemetry(StripeRequest request) throws StripeException { + @FunctionalInterface + private interface RequestSendFunction { + R apply(StripeRequest request) throws StripeException; + } + + private T sendWithTelemetry( + StripeRequest request, RequestSendFunction send) throws StripeException { Optional telemetryHeaderValue = requestTelemetry.getHeaderValue(request.headers()); if (telemetryHeaderValue.isPresent()) { request = @@ -65,7 +64,7 @@ public StripeResponse requestWithTelemetry(StripeRequest request) throws StripeE Stopwatch stopwatch = Stopwatch.startNew(); - StripeResponse response = this.request(request); + T response = send.apply(request); stopwatch.stop(); @@ -75,23 +74,40 @@ public StripeResponse requestWithTelemetry(StripeRequest request) throws StripeE } /** - * Sends the given request to Stripe's API, retrying the request in cases of intermittent - * problems. + * Sends the given request to Stripe's API, handling telemetry if not disabled. * * @param request the request * @return the response * @throws StripeException If the request fails for any reason */ - public StripeResponse requestWithRetries(StripeRequest request) throws StripeException { + public StripeResponse requestWithTelemetry(StripeRequest request) throws StripeException { + return sendWithTelemetry(request, (r) -> this.request(r)); + } + + /** + * Sends the given request to Stripe's API, streaming the response, and handling telemetry if not + * disabled. + * + * @param request the request + * @return the response + * @throws StripeException If the request fails for any reason + */ + public StripeResponseStream requestStreamWithTelemetry(StripeRequest request) + throws StripeException { + return sendWithTelemetry(request, (r) -> this.requestStream(r)); + } + + public T sendWithRetries( + StripeRequest request, RequestSendFunction send) throws StripeException { ApiConnectionException requestException = null; - StripeResponse response = null; + T response = null; int retry = 0; while (true) { requestException = null; try { - response = this.requestWithTelemetry(request); + response = send.apply(request); } catch (ApiConnectionException e) { requestException = e; } @@ -118,6 +134,31 @@ public StripeResponse requestWithRetries(StripeRequest request) throws StripeExc return response; } + /** + * Sends the given request to Stripe's API, retrying the request in cases of intermittent + * problems. + * + * @param request the request + * @return the response + * @throws StripeException If the request fails for any reason + */ + public StripeResponse requestWithRetries(StripeRequest request) throws StripeException { + return sendWithRetries(request, (r) -> this.requestWithTelemetry(r)); + } + + /** + * Sends the given request to Stripe's API, streaming the response, retrying the request in cases + * of intermittent problems. + * + * @param request the request + * @return the response + * @throws StripeException If the request fails for any reason + */ + public StripeResponseStream requestStreamWithRetries(StripeRequest request) + throws StripeException { + return sendWithRetries(request, (r) -> this.requestStreamWithTelemetry(r)); + } + /** * Builds the value of the {@code User-Agent} header. * @@ -177,8 +218,8 @@ private static String formatAppInfo(Map info) { return str; } - private boolean shouldRetry( - int numRetries, StripeException exception, StripeRequest request, StripeResponse response) { + private boolean shouldRetry( + int numRetries, StripeException exception, StripeRequest request, T response) { // Do not retry if we are out of retries. if (numRetries >= request.options().getMaxNetworkRetries()) { return false; diff --git a/src/main/java/com/stripe/net/LiveStripeResponseGetter.java b/src/main/java/com/stripe/net/LiveStripeResponseGetter.java index 8f4ff57a150..6f098e83a25 100644 --- a/src/main/java/com/stripe/net/LiveStripeResponseGetter.java +++ b/src/main/java/com/stripe/net/LiveStripeResponseGetter.java @@ -85,7 +85,7 @@ public InputStream requestStream( RequestOptions options) throws StripeException { StripeRequest request = new StripeRequest(method, url, params, options); - StripeResponseStream responseStream = httpClient.requestStream(request); + StripeResponseStream responseStream = httpClient.requestStreamWithRetries(request); int responseCode = responseStream.code(); diff --git a/src/main/java/com/stripe/net/RequestTelemetry.java b/src/main/java/com/stripe/net/RequestTelemetry.java index 26ce17174e7..679adc9c3cb 100644 --- a/src/main/java/com/stripe/net/RequestTelemetry.java +++ b/src/main/java/com/stripe/net/RequestTelemetry.java @@ -52,7 +52,7 @@ public Optional getHeaderValue(HttpHeaders headers) { * @param response the Stripe response * @param duration the request duration */ - public void maybeEnqueueMetrics(StripeResponse response, Duration duration) { + public void maybeEnqueueMetrics(StripeResponseInterface response, Duration duration) { if (!Stripe.enableTelemetry) { return; } diff --git a/src/main/java/com/stripe/net/StripeResponse.java b/src/main/java/com/stripe/net/StripeResponse.java index e354454145c..ae729d49770 100644 --- a/src/main/java/com/stripe/net/StripeResponse.java +++ b/src/main/java/com/stripe/net/StripeResponse.java @@ -6,7 +6,6 @@ import java.time.ZonedDateTime; import java.time.format.DateTimeFormatter; import java.util.Optional; -import lombok.AccessLevel; import lombok.Getter; import lombok.Setter; import lombok.Value; @@ -16,11 +15,13 @@ /** A response from Stripe's API. */ @Value @Accessors(fluent = true) -public class StripeResponse { +public class StripeResponse implements StripeResponseInterface { /** The HTTP status code of the response. */ + @Getter(onMethod_ = {@Override}) int code; /** The HTTP headers of the response. */ + @Getter(onMethod_ = {@Override}) HttpHeaders headers; /** The body of the response. */ @@ -28,8 +29,8 @@ public class StripeResponse { /** Number of times the request was retried. Used for internal tests only. */ @NonFinal - @Getter(AccessLevel.PACKAGE) - @Setter(AccessLevel.PACKAGE) + @Getter(onMethod_ = {@Override}) + @Setter(onMethod_ = {@Override}) int numRetries; /** @@ -54,6 +55,7 @@ public StripeResponse(int code, HttpHeaders headers, String body) { * * @return the date of the request, as returned by Stripe */ + @Override public Instant date() { Optional dateStr = this.headers.firstValue("Date"); if (!dateStr.isPresent()) { @@ -67,6 +69,7 @@ public Instant date() { * * @return the idempotency key of the request, as returned by Stripe */ + @Override public String idempotencyKey() { return this.headers.firstValue("Idempotency-Key").orElse(null); } @@ -76,6 +79,7 @@ public String idempotencyKey() { * * @return the ID of the request, as returned by Stripe */ + @Override public String requestId() { return this.headers.firstValue("Request-Id").orElse(null); } diff --git a/src/main/java/com/stripe/net/StripeResponseStream.java b/src/main/java/com/stripe/net/StripeResponseStream.java index 95d9522df65..795d0f7580a 100644 --- a/src/main/java/com/stripe/net/StripeResponseStream.java +++ b/src/main/java/com/stripe/net/StripeResponseStream.java @@ -19,11 +19,13 @@ /** A response from Stripe's API. */ @Value @Accessors(fluent = true) -public class StripeResponseStream { +public class StripeResponseStream implements StripeResponseInterface { /** The HTTP status code of the response. */ + @Getter(onMethod_ = {@Override}) int code; /** The HTTP headers of the response. */ + @Getter(onMethod_ = {@Override}) HttpHeaders headers; /** The body of the response. */ @@ -33,8 +35,8 @@ public class StripeResponseStream { /** Number of times the request was retried. Used for internal tests only. */ @NonFinal - @Getter(AccessLevel.PACKAGE) - @Setter(AccessLevel.PACKAGE) + @Getter(onMethod_ = {@Override}) + @Setter(onMethod_ = {@Override}) int numRetries; /** @@ -65,6 +67,7 @@ public StripeResponse unstream() throws IOException { * * @return the date of the request, as returned by Stripe */ + @Override public Instant date() { Optional dateStr = this.headers.firstValue("Date"); if (!dateStr.isPresent()) { @@ -78,6 +81,7 @@ public Instant date() { * * @return the idempotency key of the request, as returned by Stripe */ + @Override public String idempotencyKey() { return this.headers.firstValue("Idempotency-Key").orElse(null); } @@ -87,6 +91,7 @@ public String idempotencyKey() { * * @return the ID of the request, as returned by Stripe */ + @Override public String requestId() { return this.headers.firstValue("Request-Id").orElse(null); }