| File: Hedging\ResilienceHttpClientBuilderExtensions.Hedging.cs | Web Access |
| Project: src\src\Libraries\Microsoft.Extensions.Http.Resilience\Microsoft.Extensions.Http.Resilience.csproj (Microsoft.Extensions.Http.Resilience) |
// Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. using System; using System.Net.Http; using System.Threading; using Microsoft.Extensions.DependencyInjection.Extensions; using Microsoft.Extensions.Http.Resilience; using Microsoft.Extensions.Http.Resilience.Hedging.Internals; using Microsoft.Extensions.Http.Resilience.Internal; using Microsoft.Extensions.Http.Resilience.Internal.Validators; using Microsoft.Extensions.Http.Resilience.Routing.Internal; using Microsoft.Shared.Diagnostics; using Polly; namespace Microsoft.Extensions.DependencyInjection; /// <summary> /// Extensions for <see cref="IHttpClientBuilder"/>. /// </summary> public static partial class ResilienceHttpClientBuilderExtensions { /// <summary> /// Adds a standard hedging handler that wraps the execution of the request with a standard hedging mechanism. /// </summary> /// <param name="builder">The HTTP client builder.</param> /// <param name="configure">Configures the routing strategy associated with this handler.</param> /// <returns> /// A <see cref="IStandardHedgingHandlerBuilder"/> instance that can be used to configure the standard hedging behavior. /// </returns> /// <remarks> /// The standard hedging uses a pool of circuit breakers to ensure that unhealthy endpoints are not hedged against. /// By default, the selection from pool is based on the URL Authority (scheme + host + port). /// It is recommended that you configure the way the strategies are selected by calling /// <see cref="StandardHedgingHandlerBuilderExtensions.SelectPipelineByAuthority(IStandardHedgingHandlerBuilder)"/> /// extensions. /// <para> /// See <see cref="HttpStandardHedgingResilienceOptions"/> for more details about the used resilience strategies. /// </para> /// </remarks> public static IStandardHedgingHandlerBuilder AddStandardHedgingHandler(this IHttpClientBuilder builder, Action<IRoutingStrategyBuilder> configure) { _ = Throw.IfNull(builder); _ = Throw.IfNull(configure); var hedgingBuilder = builder.AddStandardHedgingHandler(); configure(hedgingBuilder.RoutingStrategyBuilder); return hedgingBuilder; } /// <summary> /// Adds a standard hedging handler that wraps the execution of the request with a standard hedging mechanism. /// </summary> /// <param name="builder">The HTTP client builder.</param> /// <returns> /// A <see cref="IStandardHedgingHandlerBuilder"/> instance that can be used to configure the standard hedging behavior. /// </returns> /// <remarks> /// The standard hedging uses a pool of circuit breakers to ensure that unhealthy endpoints are not hedged against. /// By default, the selection from pool is based on the URL Authority (scheme + host + port). /// It is recommended that you configure the way the strategies are selected by calling /// <see cref="StandardHedgingHandlerBuilderExtensions.SelectPipelineByAuthority(IStandardHedgingHandlerBuilder)"/> /// extensions. /// <para> /// See <see cref="HttpStandardHedgingResilienceOptions"/> for more details about the used resilience strategies. /// </para> /// </remarks> public static IStandardHedgingHandlerBuilder AddStandardHedgingHandler(this IHttpClientBuilder builder) { _ = Throw.IfNull(builder); var optionsName = builder.Name; var routingBuilder = new RoutingStrategyBuilder(builder.Name, builder.Services); builder.Services.TryAddSingleton<Randomizer>(); _ = builder.Services.AddOptionsWithValidateOnStart<HttpStandardHedgingResilienceOptions, HttpStandardHedgingResilienceOptionsValidator>(optionsName); _ = builder.Services.AddOptionsWithValidateOnStart<HttpStandardHedgingResilienceOptions, HttpStandardHedgingResilienceOptionsCustomValidator>(optionsName); _ = builder.Services.PostConfigure<HttpStandardHedgingResilienceOptions>(optionsName, options => { options.Hedging.ActionGenerator = args => { if (!args.PrimaryContext.Properties.TryGetValue(ResilienceKeys.RequestSnapshot, out var snapshot)) { Throw.InvalidOperationException("Request message snapshot is not attached to the resilience context."); } var requestMessage = snapshot.CreateRequestMessage(); // The secondary request message should use the action resilience context requestMessage.SetResilienceContext(args.ActionContext); // replace the request message args.ActionContext.SetRequestMessage(requestMessage); if (args.PrimaryContext.Properties.TryGetValue(ResilienceKeys.RoutingStrategy, out var routingPipeline)) { if (!routingPipeline.TryGetNextRoute(out var route)) { // no routes left, stop hedging return null; } requestMessage.RequestUri = requestMessage.RequestUri!.ReplaceHost(route); } return () => args.Callback(args.ActionContext); }; }); // configure outer handler var outerHandler = builder.AddResilienceHandler(HedgingConstants.HandlerPostfix, (builder, context) => { var options = context.GetOptions<HttpStandardHedgingResilienceOptions>(optionsName); context.EnableReloads<HttpStandardHedgingResilienceOptions>(optionsName); var routingOptions = context.GetOptions<RequestRoutingOptions>(routingBuilder.Name); _ = builder .AddStrategy(_ => new RoutingResilienceStrategy(routingOptions.RoutingStrategyProvider)) .AddStrategy(_ => new RequestMessageSnapshotStrategy()) .AddTimeout(options.TotalRequestTimeout) .AddHedging(options.Hedging); }); // configure inner handler var innerBuilder = builder.AddResilienceHandler( HedgingConstants.InnerHandlerPostfix, (builder, context) => { var options = context.GetOptions<HttpStandardHedgingResilienceOptions>(optionsName); context.EnableReloads<HttpStandardHedgingResilienceOptions>(optionsName); _ = builder .AddRateLimiter(options.Endpoint.RateLimiter) .AddCircuitBreaker(options.Endpoint.CircuitBreaker) .AddTimeout(options.Endpoint.Timeout); }) .SelectPipelineByAuthority(); // Disable the HttpClient timeout to allow the timeout strategies to control the timeout. _ = builder.ConfigureHttpClient(client => client.Timeout = Timeout.InfiniteTimeSpan); return new StandardHedgingHandlerBuilder(builder.Name, builder.Services, routingBuilder); } private sealed record StandardHedgingHandlerBuilder( string Name, IServiceCollection Services, IRoutingStrategyBuilder RoutingStrategyBuilder) : IStandardHedgingHandlerBuilder; }