-
Notifications
You must be signed in to change notification settings - Fork 751
/
ResilienceHttpClientBuilderExtensions.Hedging.cs
153 lines (130 loc) · 7.15 KB
/
ResilienceHttpClientBuilderExtensions.Hedging.cs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
// 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;
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;
}