Skip to content
Merged
Show file tree
Hide file tree
Changes from 12 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -11,20 +11,18 @@
import com.azure.core.http.policy.HttpPipelinePolicy;
import com.azure.core.http.policy.RequestIdPolicy;
import com.azure.core.http.policy.RetryPolicy;
import com.azure.core.http.policy.RetryPolicyOptions;
import com.azure.core.http.policy.ExponentialBackoff;
import com.azure.core.http.policy.AddDatePolicy;
import com.azure.core.http.policy.UserAgentPolicy;
import com.azure.core.http.policy.HttpPolicyProviders;
import com.azure.core.http.policy.HttpLogOptions;
import com.azure.core.util.logging.ClientLogger;
import com.azure.core.http.HttpPipeline;
import com.azure.core.http.HttpHeaders;
import com.azure.core.http.HttpClient;
import com.azure.data.appconfiguration.implementation.ConfigurationClientCredentials;
import com.azure.data.appconfiguration.implementation.ConfigurationCredentialsPolicy;
import com.azure.data.appconfiguration.models.ConfigurationSetting;
import com.azure.core.util.Configuration;
import com.azure.core.http.HttpClient;
import com.azure.core.http.HttpHeaders;
import com.azure.core.http.HttpPipeline;
import com.azure.core.util.CoreUtils;

import java.net.MalformedURLException;
Expand Down Expand Up @@ -79,9 +77,7 @@ public final class ConfigurationClientBuilder {
private static final String APP_CONFIG_PROPERTIES = "azure-appconfig.properties";
private static final String NAME = "name";
private static final String VERSION = "version";
private static final String RETRY_AFTER_MS_HEADER = "retry-after-ms";
private static final RetryPolicy DEFAULT_RETRY_POLICY = new RetryPolicy(
new RetryPolicyOptions(new ExponentialBackoff(), RETRY_AFTER_MS_HEADER, ChronoUnit.MILLIS));
private static final RetryPolicy DEFAULT_RETRY_POLICY = new RetryPolicy("retry-after-ms", ChronoUnit.MILLIS);

private final ClientLogger logger = new ClientLogger(ConfigurationClientBuilder.class);
private final List<HttpPipelinePolicy> policies;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,22 +3,61 @@

package com.azure.core.http.policy;

import com.azure.core.http.HttpHeaders;
import com.azure.core.http.HttpRequest;
import com.azure.core.http.HttpPipelineCallContext;
import com.azure.core.http.HttpPipelineNextPolicy;
import com.azure.core.http.HttpResponse;
import reactor.core.publisher.Mono;

import java.util.Objects;
import java.util.UUID;
import java.util.function.Supplier;

/**
* The pipeline policy that puts a UUID in the request header. Azure uses the request id as
* The pipeline policy that puts a UUID or user provided id in the request header. Azure uses the request id as
Comment thread
hemanttanwar marked this conversation as resolved.
Outdated
* the unique identifier for the request.
*/
public class RequestIdPolicy implements HttpPipelinePolicy {
private static final String REQUEST_ID_HEADER = "x-ms-client-request-id";
private final Supplier<HttpHeaders> requestIdSupplier;

/**
* Creates default {@link RequestIdPolicy}.
*/
public RequestIdPolicy() {
requestIdSupplier = null;
Comment thread
hemanttanwar marked this conversation as resolved.
Outdated
}

/**
* Creates {@link RequestIdPolicy} with provided {@link Supplier} to dynamically generate request id for each
* {@link HttpRequest}.
*
* @param requestIdSupplier to dynamically generate to request id for each {@link HttpRequest}. {@code null} is
* valid value. It is suggested that this {@link Supplier} should provide unique value every time
* it is called. Example of these headers are 'x-ms-client-request-id', 'x-ms-correlation-request-id'.
*/
public RequestIdPolicy(Supplier<HttpHeaders> requestIdSupplier) {
this.requestIdSupplier = requestIdSupplier;

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We are using HttpHeaders because, There can be multiple headers which are of ID nature. We already have been asked for multiple id related headers by app config
"x-ms-client-request-id"
"x-ms-correlation-request-id"

Thus HttpHeaders represent multiple Headers which client can set.

Comment thread
hemanttanwar marked this conversation as resolved.
Outdated
}

@Override
public Mono<HttpResponse> process(HttpPipelineCallContext context, HttpPipelineNextPolicy next) {

if (Objects.nonNull(requestIdSupplier)) {
HttpHeaders httpHeaders = requestIdSupplier.get();
if (Objects.nonNull(httpHeaders) && httpHeaders.getSize() > 0) {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Continuing from a previous comment, if the supplier is made non-nullable and we add a default header in the base constructor (and possible default to this is null is passed into the overloaded constructor), should we do nothing if the supplier returns nothing. x-ms-client-request-id is optional by many services, so this would be allowed, but it would hurt tracking issues.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You answered , I think, In order to track request, we do need request id. And if client does not provide or give us empty .. we should provide it.

httpHeaders.stream().forEach(httpHeader -> {
Comment thread
hemanttanwar marked this conversation as resolved.
Outdated
String requestIdHeaderValue = context.getHttpRequest().getHeaders().getValue(httpHeader.getName());
if (requestIdHeaderValue == null) {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is a general question for all policies which mutate the request, should this just insert the header value?

@JonathanGiles @srnagar

context.getHttpRequest().getHeaders().put(httpHeader.getName(), httpHeader.getValue());
}
});
return next.process();
}
}

// If we were not able to set client provided Request ID header, we will set default 'REQUEST_ID_HEADER'.
String requestId = context.getHttpRequest().getHeaders().getValue(REQUEST_ID_HEADER);
if (requestId == null) {
context.getHttpRequest().getHeaders().put(REQUEST_ID_HEADER, UUID.randomUUID().toString());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,59 +3,88 @@

package com.azure.core.http.policy;

import static com.azure.core.util.CoreUtils.isNullOrEmpty;

import com.azure.core.http.HttpPipelineCallContext;
import com.azure.core.http.HttpPipelineNextPolicy;
import com.azure.core.http.HttpRequest;
import com.azure.core.http.HttpResponse;

import java.time.temporal.ChronoUnit;
import java.util.Objects;
import com.azure.core.util.logging.ClientLogger;
import reactor.core.publisher.Mono;

import static com.azure.core.util.CoreUtils.isNullOrEmpty;

import java.time.Duration;

/**
* A pipeline policy that retries when a recoverable HTTP error occurs.
* @see RetryPolicyOptions
*/
public class RetryPolicy implements HttpPipelinePolicy {

private final ClientLogger logger = new ClientLogger(RetryPolicy.class);
private static final String RETRY_AFTER_MS_HEADER = "retry-after-ms";

Comment thread
hemanttanwar marked this conversation as resolved.
private final RetryPolicyOptions retryPolicyOptions;
private final RetryStrategy retryStrategy;
private final String retryAfterHeader;
private final ChronoUnit retryAfterTimeUnit;

/**
* Creates {@link RetryPolicy} with default {@link ExponentialBackoff} as {@link RetryStrategy}and use
* Creates {@link RetryPolicy} with default {@link ExponentialBackoff} as {@link RetryStrategy} and use
* 'retry-after-ms' in {@link HttpResponse} header for calculating retry delay.
*/
public RetryPolicy() {
this(new RetryPolicyOptions(new ExponentialBackoff()));
this(new ExponentialBackoff(), RETRY_AFTER_MS_HEADER, ChronoUnit.MILLIS);
Comment thread
hemanttanwar marked this conversation as resolved.
Outdated
}

/**
* Creates a RetryPolicy with the provided {@link RetryStrategy}.
* Creates {@link RetryPolicy} with default {@link ExponentialBackoff} as {@link RetryStrategy} and use
* provided {@code retryAfterHeader} in {@link HttpResponse} headers for calculating retry delay.
*
* @param retryAfterHeader The HTTP header, such as 'Retry-After' or 'x-ms-retry-after-ms', to lookup for the
* retry delay. If the value is {@code null}, {@link RetryPolicy} will use the retry strategy to compute the delay
* and ignore the delay provided in response header.
* @param retryAfterTimeUnit The time unit to use when applying the retry delay. {@code null} is valid if, and only
* if, {@code retryAfterHeader} is {@code null}.
* @throws NullPointerException When {@code retryAfterTimeUnit} is {@code null} and {@code retryAfterHeader} is
* not {@code null}.
*/
public RetryPolicy(String retryAfterHeader, ChronoUnit retryAfterTimeUnit) {
this(new ExponentialBackoff(), retryAfterHeader, retryAfterTimeUnit);
}

/**
* Creates {@link RetryPolicy} with the provided {@link RetryStrategy} and default {@link ExponentialBackoff}
* as {@link RetryStrategy}. It will use provided {@code retryAfterHeader} in {@link HttpResponse} headers for
* calculating retry delay.
*
* @param retryStrategy The {@link RetryStrategy} used for retries.
* @param retryAfterHeader The HTTP header, such as 'Retry-After' or 'x-ms-retry-after-ms', to lookup for the
* retry delay. If the value is {@code null}, {@link RetryPolicy} will use the retry strategy to compute the delay
* and ignore the delay provided in response header.
* @param retryAfterTimeUnit The time unit to use when applying the retry delay. {@code null} is valid if, and only
* if, {@code retryAfterHeader} is {@code null}.
* @throws NullPointerException When {@code retryStrategy} is {@code null}. Also when {@code retryAfterTimeUnit}
* is {@code null} and {@code retryAfterHeader} is not {@code null}.
*/
public RetryPolicy(RetryStrategy retryStrategy) {
Objects.requireNonNull(retryStrategy, "'retryStrategy' cannot be null");
this.retryPolicyOptions = new RetryPolicyOptions(retryStrategy);
public RetryPolicy(RetryStrategy retryStrategy, String retryAfterHeader, ChronoUnit retryAfterTimeUnit) {
this.retryStrategy = Objects.requireNonNull(retryStrategy, "'retryStrategy' cannot be null");
this.retryAfterHeader = retryAfterHeader;
this.retryAfterTimeUnit = retryAfterTimeUnit;
if (!isNullOrEmpty(retryAfterHeader)) {
Objects.requireNonNull(retryAfterTimeUnit, "'retryAfterTimeUnit' cannot be null.");
}
}

/**
* Creates a {@link RetryPolicy} with the provided {@link RetryPolicyOptions}.
* Creates a {@link RetryPolicy} with the provided {@link RetryStrategy} and use 'retry-after-ms' in
* {@link HttpResponse} header for calculating retry delay.
*
* @param retryPolicyOptions with given {@link RetryPolicyOptions}.
* @throws NullPointerException if {@code retryPolicyOptions} or {@code retryPolicyOptions getRetryStrategy }
* is {@code null}.
* @param retryStrategy The {@link RetryStrategy} used for retries.
* @throws NullPointerException When {@code retryStrategy} is {@code null}.
*/
public RetryPolicy(RetryPolicyOptions retryPolicyOptions) {
this.retryPolicyOptions = Objects.requireNonNull(retryPolicyOptions,
"'retryPolicyOptions' cannot be null.");
Objects.requireNonNull(retryPolicyOptions.getRetryStrategy(),
"'retryPolicyOptions.retryStrategy' cannot be null.");
public RetryPolicy(RetryStrategy retryStrategy) {
this(retryStrategy, RETRY_AFTER_MS_HEADER, ChronoUnit.MILLIS);
}

@Override
Expand All @@ -79,11 +108,11 @@ private Mono<HttpResponse> attemptAsync(final HttpPipelineCallContext context, f
}
})
.onErrorResume(err -> {
int maxRetries = retryPolicyOptions.getRetryStrategy().getMaxRetries();
int maxRetries = retryStrategy.getMaxRetries();
if (tryCount < maxRetries) {
logger.verbose("[Error Resume] Try count: {}, Error: {}", tryCount, err);
return attemptAsync(context, next, originalHttpRequest, tryCount + 1)
.delaySubscription(retryPolicyOptions.getRetryStrategy().calculateRetryDelay(tryCount));
.delaySubscription(retryStrategy.calculateRetryDelay(tryCount));
} else {
return Mono.error(new RuntimeException(
String.format("Max retries %d times exceeded. Error Details: %s", maxRetries, err.getMessage()),
Expand All @@ -93,37 +122,37 @@ private Mono<HttpResponse> attemptAsync(final HttpPipelineCallContext context, f
}

private boolean shouldRetry(HttpResponse response, int tryCount) {
return tryCount < retryPolicyOptions.getRetryStrategy().getMaxRetries()
&& retryPolicyOptions.getRetryStrategy().shouldRetry(response);
return tryCount < retryStrategy.getMaxRetries()
&& retryStrategy.shouldRetry(response);
}

/**
* Determines the delay duration that should be waited before retrying.
* @param response HTTP response
* @return If the HTTP response has a retry-after-ms header that will be returned,
* otherwise the duration used during the construction of the policy.
* otherwise the duration used during the construction of the policy.
*/
private Duration determineDelayDuration(HttpResponse response, int tryCount) {
int code = response.getStatusCode();

// Response will not have a retry-after-ms header.
if (code != 429 // too many requests
&& code != 503) { // service unavailable
Comment thread
hemanttanwar marked this conversation as resolved.
return retryPolicyOptions.getRetryStrategy().calculateRetryDelay(tryCount);
return retryStrategy.calculateRetryDelay(tryCount);
}

String retryHeaderValue = null;

if (!isNullOrEmpty(retryPolicyOptions.getRetryAfterHeader())) {
retryHeaderValue = response.getHeaderValue(retryPolicyOptions.getRetryAfterHeader());
if (!isNullOrEmpty(this.retryAfterHeader)) {
retryHeaderValue = response.getHeaderValue(this.retryAfterHeader);
}

// Retry header is missing or empty, return the default delay duration.
if (isNullOrEmpty(retryHeaderValue)) {
return retryPolicyOptions.getRetryStrategy().calculateRetryDelay(tryCount);
return this.retryStrategy.calculateRetryDelay(tryCount);
}

// Use the response delay duration, the server returned it for a reason.
return Duration.of(Integer.parseInt(retryHeaderValue), retryPolicyOptions.getRetryAfterTimeUnit());
return Duration.of(Integer.parseInt(retryHeaderValue), this.retryAfterTimeUnit);
}
}

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -108,7 +108,7 @@ public Mono<HttpResponse> send(HttpRequest request) {
return Mono.just(mockResponse);
}
})
.policies(new RequestIdPolicy(), new RetryPolicy(new RetryPolicyOptions(new FixedDelay(1, Duration.of(0, ChronoUnit.SECONDS)))))
.policies(new RequestIdPolicy(), new RetryPolicy(new FixedDelay(1, Duration.of(0, ChronoUnit.SECONDS))))
Comment thread
hemanttanwar marked this conversation as resolved.
.build();

pipeline.send(new HttpRequest(HttpMethod.GET, new URL("http://localhost/"))).block();
Expand Down
Loading