/// <summary>
        /// Configures Polly Policy Handlers using options defined in <typeparamref name="T"/>.
        /// Adds retry, circuit breaker and bulkhead policies depending on the configured <see cref="HttpOptions"/> values.
        /// </summary>
        /// <typeparam name="T">The option type.</typeparam>
        /// <param name="builder">The <see cref="IHttpClientBuilder"/>.</param>
        /// <returns>The updated <see cref="IHttpClientBuilder"/>.</returns>
        public static IHttpClientBuilder AddPoliciesFromOptions <T>(this IHttpClientBuilder builder)
            where T : HttpOptions, new()
        {
            if (builder == null)
            {
                throw new ArgumentNullException(nameof(builder));
            }

            var options = builder.Services.BuildServiceProvider().GetRequiredService <IOptions <T> >().Value;

            if (options.ErrorsAllowedBeforeBreaking > 0)
            {
                builder = builder.AddTransientHttpErrorPolicy(p => p.CircuitBreakerAsync(options.ErrorsAllowedBeforeBreaking, options.BreakDuration));
            }

            if (options.NumberOfRetries > 0)
            {
                if (options.RetriesMaximumSleepDuration == TimeSpan.FromTicks(0))
                {
                    builder = builder.AddTransientHttpErrorPolicy(p => p.WaitAndRetryAsync(options.NumberOfRetries, _ => options.RetriesSleepDuration));
                }
                else
                {
                    builder = builder.AddTransientHttpErrorPolicy(
                        p => p.WaitAndRetryAsync(DecorrelatedJitter(options.NumberOfRetries, options.RetriesSleepDuration, options.RetriesMaximumSleepDuration)));
                }
            }

            if (options.MaxParallelization > 0)
            {
                builder = builder.AddPolicyHandler(Policy.BulkheadAsync(options.MaxParallelization).AsAsyncPolicy <HttpResponseMessage>());
            }

            return(builder);
        }
示例#2
0
        public IHttpServiceBuilder WaitAndRetry(WaitAndRetryOptions options)
        {
            _httpClientBuilder.AddTransientHttpErrorPolicy(builder => builder.WaitAndRetryAsync(
                                                               options.WaitList
                                                               ));

            return(this);
        }
示例#3
0
        public S3Client(S3Config config, HttpMessageHandler messageHandler)
        {
            ServiceCollection services = new ServiceCollection();

            services.AddSingleton(x => Options.Create(config));

            IS3ClientBuilder   builder     = services.AddSimpleS3Core();
            IHttpClientBuilder httpBuilder = builder.UseHttpClientFactory();

            if (messageHandler != null)
            {
                httpBuilder.ConfigurePrimaryHttpMessageHandler(x => messageHandler);
            }

            httpBuilder.SetHandlerLifetime(TimeSpan.FromMinutes(5));

            Random random = new Random();

            // Policy is:
            // Retries: 3
            // Timeout: 2^attempt seconds (2, 4, 8 seconds) + -100 to 100 ms jitter
            httpBuilder.AddTransientHttpErrorPolicy(p => p.WaitAndRetryAsync(3,
                                                                             retryAttempt => TimeSpan.FromSeconds(Math.Pow(2, retryAttempt))
                                                                             + TimeSpan.FromMilliseconds(random.Next(-100, 100))));

            _provider      = services.BuildServiceProvider();
            _objectClient  = _provider.GetRequiredService <IS3ObjectClient>();
            _bucketClient  = _provider.GetRequiredService <IS3BucketClient>();
            _serviceClient = _provider.GetRequiredService <IS3ServiceClient>();
        }
示例#4
0
        public static IHttpClientBuilder AddResilience(this IHttpClientBuilder clientBuilder, IPolicyConfig policyConfig)
        {
            if (policyConfig.HasResilicence)
            {
                clientBuilder.AddTransientHttpErrorPolicy(builder => builder.WaitAndRetryAsync(
                                                              new[] {
                    TimeSpan.FromSeconds(1),
                    TimeSpan.FromSeconds(5),
                    TimeSpan.FromSeconds(10),
                }));

                clientBuilder.AddTransientHttpErrorPolicy(builder => builder.CircuitBreakerAsync(
                                                              handledEventsAllowedBeforeBreaking: 3,
                                                              durationOfBreak: TimeSpan.FromSeconds(30)));

                if (policyConfig.HandlerLifetime.Ticks > 0)
                {
                    clientBuilder.SetHandlerLifetime(policyConfig.HandlerLifetime);
                }
            }

            return(clientBuilder);
        }
示例#5
0
        public static S3Client Create()
        {
            //In this example we are using Dependency Injection (DI) using Microsoft's DI framework
            ServiceCollection services = new ServiceCollection();

            //We use Microsoft.Extensions.Configuration framework here to load our config file
            ConfigurationBuilder configBuilder = new ConfigurationBuilder();

            configBuilder.AddJsonFile("Config.json", false);
            IConfigurationRoot root = configBuilder.Build();

            //We use Microsoft.Extensions.Logging here to add support for logging
            services.AddLogging(x =>
            {
                x.AddConsole();
                x.AddConfiguration(root.GetSection("Logging"));
            });

            //Here we setup our S3Client
            IS3ClientBuilder clientBuilder = services.AddSimpleS3Core(s3Config =>
            {
                root.Bind(s3Config);

                s3Config.Credentials = new StringAccessKey("Q3AM3UQ867SPQQA43P2F", "zuf+tfteSlswRu7BJ86wekitnifILbZam1KYY3TG");
                s3Config.Region      = AwsRegion.USEast1;
                s3Config.Endpoint    = new Uri("https://play.min.io:9000/");
            });

            //We enable HTTP Factory support here.
            IHttpClientBuilder httpBuilder = clientBuilder.UseHttpClientFactory();

            //Every 5 minutes we create a new connection, thereby reacting to DNS changes
            httpBuilder.SetHandlerLifetime(TimeSpan.FromMinutes(5));

            //Uncomment this line if you want to use a proxy
            //httpBuilder.ConfigurePrimaryHttpMessageHandler(() => new HttpClientHandler { Proxy = new WebProxy("http://<YourProxy>:<ProxyPort>") });

            //Here we enable retrying. We retry 3 times with a delay of 600 ms between attempts. For more examples, see https://github.com/App-vNext/Polly
            httpBuilder.AddTransientHttpErrorPolicy(p => p.WaitAndRetryAsync(3, _ => TimeSpan.FromMilliseconds(600)));

            //Finally we build the service provider and return the S3Client
            IServiceProvider serviceProvider = services.BuildServiceProvider();

            return(serviceProvider.GetRequiredService <S3Client>());
        }
        /// <summary>
        /// The extension method to handle transient exceptions.
        /// It leverages the Polly lib in handling the exceptions.
        /// </summary>
        /// <param name="httpClientBuilder">The IHttpClientBuilder object.</param>
        /// <param name="configurationSettings">The configuration settings object.</param>
        /// <returns>Returns the same IHttpClientBuilder object.</returns>
        public static IHttpClientBuilder AddTransientExceptionHandlerPolicy(
            this IHttpClientBuilder httpClientBuilder,
            ConfigurationSettings configurationSettings)
        {
            var retryCount = configurationSettings.RetryCount;
            var retryDelay = configurationSettings.RetryDelay;

            return(httpClientBuilder.AddTransientHttpErrorPolicy(policyBuilder =>
            {
                return policyBuilder.WaitAndRetryAsync(
                    retryCount + 1,
                    (count) => TimeSpan.FromSeconds(retryDelay),
                    (response, timespan, count, context) =>
                {
                    if (count > retryCount)
                    {
                        throw new ApplicationException($"Transient exception still happens after {retryCount} re-tries!");
                    }
                });
            }));
        }
示例#7
0
        public static IHttpClientBuilder ApplyResiliencePolicies(this IHttpClientBuilder builder)
        {
            Random jitter = new Random();

            AsyncCircuitBreakerPolicy <HttpResponseMessage> circuitBreaker = Policy
                                                                             .Handle <HttpRequestException>()
                                                                             .OrResult <HttpResponseMessage>(x => x.StatusCode >= HttpStatusCode.InternalServerError)
                                                                             .AdvancedCircuitBreakerAsync(
                failureThreshold: 0.5,
                samplingDuration: TimeSpan.FromSeconds(10),
                minimumThroughput: 6,
                durationOfBreak: TimeSpan.FromMinutes(1));

            builder
            .AddTransientHttpErrorPolicy(transient => transient
                                         .WaitAndRetryAsync(3, (retryCount) =>
                                                            TimeSpan.FromSeconds(Math.Pow(retryCount, 2))
                                                            + TimeSpan.FromMilliseconds(jitter.NextDouble() * 500d)))
            .AddPolicyHandler(Policy.TimeoutAsync <HttpResponseMessage>(TimeSpan.FromSeconds(60)))
            .AddPolicyHandler(circuitBreaker);

            return(builder);
        }
示例#8
0
        /// <summary>
        /// Adds a Polly circiut breaker to your clients.
        /// </summary>
        /// <param name="client"></param>
        /// <param name="handledEventsAllowedBeforeBreaking"></param>
        /// <param name="durationOfBreak"></param>
        /// <returns></returns>
        public static IHttpClientBuilder AddCircuitBreakerPolicy(this IHttpClientBuilder client, int handledEventsAllowedBeforeBreaking, TimeSpan durationOfBreak)
        {
            client.AddTransientHttpErrorPolicy(builder => CircuitBreakerPolicy(builder, handledEventsAllowedBeforeBreaking, durationOfBreak));

            return(client);
        }
示例#9
0
        /// <summary>
        /// Applies configuration from <see cref="HttpClientOptions"/> <typeparamref name="TOptions"/>
        /// to the current <see cref="HttpClient"/> registration.
        /// Options are automatically registered as well.
        /// </summary>
        /// <typeparam name="TOptions">The type of <see cref="HttpClientOptions"/> to register and use.</typeparam>
        /// <param name="builder">The <see cref="IHttpClientBuilder"/>.</param>
        /// <param name="configuration">The <see cref="IConfiguration"/>.</param>
        /// <param name="key">
        /// The configuration section key name to use.
        /// If not provided, it will be the <typeparamref name="T"/> type name without the -Options prefix.
        /// (see <see cref="ConfigurationExtensions.DefaultOptionsName(Type)"/>.
        /// </param>
        public static IHttpClientBuilder ConfigureWithOptions <TOptions>(
            this IHttpClientBuilder builder,
            IConfiguration configuration,
            string?key = null)
            where TOptions : HttpClientOptions, new()
        {
            builder.ConfigureHttpClient((sp, client) =>
            {
                var options = sp.GetRequiredService <IOptionsMonitor <TOptions> >().CurrentValue;
                if (options.BaseAddress != null)
                {
                    client.BaseAddress = options.BaseAddress;
                }

                if (options.Headers != null)
                {
                    foreach (var header in options.Headers.Where(x => !string.IsNullOrEmpty(x.Value)))
                    {
                        client.DefaultRequestHeaders.TryAddWithoutValidation(header.Key, header.Value);
                    }
                }

                if (client.DefaultRequestHeaders.UserAgent.Count == 0)
                {
                    var appInfo = sp.GetRequiredService <IApplicationInfo>();
                    client.DefaultRequestHeaders.TryAddWithoutValidation("User-Agent", $"{appInfo.Name}/{appInfo.Version} ({appInfo.Environment})");
                }
            });

            var options = configuration.ReadOptionsAndValidate <TOptions>(key);

            if (options.Timeout != TimeSpan.Zero)
            {
                builder = builder.AddPolicyHandler(
                    Policy.TimeoutAsync(options.Timeout).AsAsyncPolicy <HttpResponseMessage>());
            }

            if (options.ErrorsAllowedBeforeBreaking > 0)
            {
                builder = builder.AddTransientHttpErrorPolicy(p => p.CircuitBreakerAsync(options.ErrorsAllowedBeforeBreaking, options.BreakDuration));
            }

            if (options.NumberOfRetries > 0)
            {
                if (options.RetriesMaximumSleepDuration == TimeSpan.FromTicks(0))
                {
                    builder = builder.AddTransientHttpErrorPolicy(
                        p => p.WaitAndRetryAsync(options.NumberOfRetries, _ => options.RetriesSleepDuration));
                }
                else
                {
                    builder = builder.AddTransientHttpErrorPolicy(
                        p => p.WaitAndRetryAsync(
                            DecorrelatedJitter(options.NumberOfRetries, options.RetriesSleepDuration, options.RetriesMaximumSleepDuration)));
                }
            }

            if (options.MaxParallelization > 0)
            {
                builder = builder.AddPolicyHandler(
                    Policy.BulkheadAsync(options.MaxParallelization).AsAsyncPolicy <HttpResponseMessage>());
            }

            if (options.IgnoreCertificateValidation)
            {
                builder.ConfigurePrimaryHttpMessageHandler(() => new HttpClientHandler()
                {
                    ServerCertificateCustomValidationCallback = HttpClientHandler.DangerousAcceptAnyServerCertificateValidator,
                });
            }

            return(builder);
        }
示例#10
0
        public static void DefaultStrategy(Serilog.ILogger logger, IHttpClientBuilder builder)
        {
            if (!int.TryParse(Environment.GetEnvironmentVariable($"apiRetriesCount"), out var retries))
            {
                retries = 3;
            }

            if (!int.TryParse(Environment.GetEnvironmentVariable($"apiRetriesDelayMin"), out var delayMin))
            {
                delayMin = 100;
            }

            if (!int.TryParse(Environment.GetEnvironmentVariable($"apiRetriesDelayStep"), out var delayStep))
            {
                delayStep = 50;
            }

            if (!int.TryParse(Environment.GetEnvironmentVariable($"apiRetriesJitterMin"), out var jitterMin))
            {
                jitterMin = 0;
            }

            if (!int.TryParse(Environment.GetEnvironmentVariable($"apiRetriesJitterMax"), out var jitterMax))
            {
                jitterMax = 100;
            }

            if (!int.TryParse(Environment.GetEnvironmentVariable($"apiCircuitBreakerCount"), out var circuitCount))
            {
                circuitCount = 5;
            }

            if (!int.TryParse(Environment.GetEnvironmentVariable($"apiCircuitBreakerDelaySeconds"),
                              out var circuitDelay))
            {
                circuitDelay = 30;
            }

            if (!int.TryParse(Environment.GetEnvironmentVariable($"apiHandlerLifetimeSeconds"), out var lifetime))
            {
                lifetime = 300;
            }


            Action <DelegateResult <HttpResponseMessage>, TimeSpan, Context> onRetry = (d, timespan, context) =>
            {
//                var isTimeout = d.Exception?.Message == "The operation was canceled."
//                                && d.Exception?.InnerException?.Message == "The operation was canceled.";
//
                var isTimeout = d.Exception is HttpRequestException &&
                                d.Exception?.InnerException is TaskCanceledException;

                if (isTimeout)
                {
                    logger.Warning("Retry - Timeout for api client {0}", builder.Name);
                }
                else
                {
                    var uri     = d.Result?.RequestMessage?.RequestUri;
                    var message = d.Result?.ToString();
                    logger.Warning("Retry - Exception for api client {0} - Url {1} Message {2}",
                                   builder.Name, uri, message);
                }
            };

            Action <DelegateResult <HttpResponseMessage>, TimeSpan, Context> onBreak = (d, timespan, context) =>
            {
                logger.Error("CircuitBreaker - onBreak for api client {0}", builder.Name);
            };

            Action <Context> onReset = (context) =>
            {
                logger.Information("CircuitBreaker - onReset for api client {0}", builder.Name);
            };

            Action onHalfOpen = () =>
            {
                logger.Warning("CircuitBreaker - onHalfOpen for api client {0}", builder.Name);
            };

            //https://docs.microsoft.com/en-us/aspnet/core/fundamentals/http-requests?view=aspnetcore-2.1
            //https://github.com/App-vNext/Polly
            //https://github.com/App-vNext/Polly/wiki/Polly-and-HttpClientFactory
            //HttpRequestException, 5XX and 408
            var jitter = new Random();

            builder
            .AddTransientHttpErrorPolicy(p =>
                                         p.WaitAndRetryAsync(retries, retryAttempt =>
                                                             //DecorrelatedExponent(jitter)
                                                             LinearAndJitter(retryAttempt, jitter, jitterMin, jitterMax, delayMin, delayStep)
                                                             , onRetry)
                                         )
            .AddTransientHttpErrorPolicy(p =>     /* or p.AdvancedCircuitBreakerAsync() */
                                         p.CircuitBreakerAsync(circuitCount, TimeSpan.FromSeconds(circuitDelay),
                                                               onBreak, onReset, onHalfOpen)
                                         )
            //SetHandlerLifetime can prevent the handler from reacting to DNS changes
            .SetHandlerLifetime(TimeSpan.FromSeconds(lifetime))
            .ConfigurePrimaryHttpMessageHandler(() => new HttpClientHandler()
            {
                AllowAutoRedirect        = true,
                MaxAutomaticRedirections = 3,
                UseDefaultCredentials    = false,
                AutomaticDecompression   = DecompressionMethods.GZip,
            });
        }