-
Notifications
You must be signed in to change notification settings - Fork 768
/
Copy pathResilienceHttpClientBuilderExtensions.Resilience.cs
171 lines (145 loc) · 7.2 KB
/
ResilienceHttpClientBuilderExtensions.Resilience.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
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
// 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;
using System.Net.Http;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Diagnostics.ExceptionSummarization;
using Microsoft.Extensions.EnumStrings;
using Microsoft.Extensions.Http.Resilience;
using Microsoft.Extensions.Http.Resilience.Internal;
using Microsoft.Extensions.Resilience;
using Microsoft.Shared.Diagnostics;
using Microsoft.Shared.Text;
using Polly;
using Polly.Registry;
[assembly: EnumStrings(typeof(HttpStatusCode))]
namespace Microsoft.Extensions.DependencyInjection;
/// <summary>
/// Extensions for <see cref="IHttpClientBuilder"/>.
/// </summary>
public static partial class ResilienceHttpClientBuilderExtensions
{
/// <summary>
/// Adds a resilience pipeline handler that uses a named inline resilience pipeline.
/// </summary>
/// <param name="builder">The builder instance.</param>
/// <param name="pipelineName">The custom identifier for the resilience pipeline, used in the name of the pipeline.</param>
/// <param name="configure">The callback that configures the pipeline.</param>
/// <returns>The value of <paramref name="builder"/>.</returns>
/// <remarks>
/// The final pipeline name is combination of <see cref="IHttpClientBuilder.Name"/> and <paramref name="pipelineName"/>.
/// Use pipeline name identifier if your HTTP client contains multiple resilience handlers.
/// </remarks>
public static IHttpResiliencePipelineBuilder AddResilienceHandler(
this IHttpClientBuilder builder,
string pipelineName,
Action<ResiliencePipelineBuilder<HttpResponseMessage>> configure)
{
_ = Throw.IfNull(builder);
_ = Throw.IfNullOrEmpty(pipelineName);
_ = Throw.IfNull(configure);
return builder.AddResilienceHandler(pipelineName, ConfigureBuilder);
void ConfigureBuilder(ResiliencePipelineBuilder<HttpResponseMessage> builder, ResilienceHandlerContext context) => configure(builder);
}
/// <summary>
/// Adds a resilience pipeline handler that uses a named inline resilience pipeline.
/// </summary>
/// <param name="builder">The builder instance.</param>
/// <param name="pipelineName">The custom identifier for the resilience pipeline, used in the name of the pipeline.</param>
/// <param name="configure">The callback that configures the pipeline.</param>
/// <returns>The value of <paramref name="builder"/>.</returns>
/// <remarks>
/// The final pipeline name is combination of <see cref="IHttpClientBuilder.Name"/> and <paramref name="pipelineName"/>.
/// Use pipeline name identifier if your HTTP client contains multiple resilience handlers.
/// </remarks>
public static IHttpResiliencePipelineBuilder AddResilienceHandler(
this IHttpClientBuilder builder,
string pipelineName,
Action<ResiliencePipelineBuilder<HttpResponseMessage>, ResilienceHandlerContext> configure)
{
_ = Throw.IfNull(builder);
_ = Throw.IfNullOrEmpty(pipelineName);
_ = Throw.IfNull(configure);
var pipelineBuilder = builder.AddHttpResiliencePipeline(pipelineName, configure);
_ = builder.AddHttpMessageHandler(serviceProvider =>
{
var selector = CreatePipelineSelector(serviceProvider, pipelineBuilder.PipelineName);
var provider = serviceProvider.GetRequiredService<ResiliencePipelineProvider<HttpKey>>();
return new ResilienceHandler(selector);
});
return pipelineBuilder;
}
private static Func<HttpRequestMessage, ResiliencePipeline<HttpResponseMessage>> CreatePipelineSelector(IServiceProvider serviceProvider, string pipelineName)
{
var resilienceProvider = serviceProvider.GetRequiredService<ResiliencePipelineProvider<HttpKey>>();
var pipelineKeyProvider = serviceProvider.GetPipelineKeyProvider(pipelineName);
if (pipelineKeyProvider == null)
{
var pipeline = resilienceProvider.GetPipeline<HttpResponseMessage>(new HttpKey(pipelineName, string.Empty));
return _ => pipeline;
}
else
{
TouchPipelineKey(pipelineKeyProvider);
return request =>
{
var key = pipelineKeyProvider(request);
return resilienceProvider.GetPipeline<HttpResponseMessage>(new HttpKey(pipelineName, key));
};
}
}
private static void TouchPipelineKey(Func<HttpRequestMessage, string> provider)
{
// this piece of code eagerly checks that the pipeline key provider is correctly configured
// combined with HttpClient auto-activation we can detect any issues on startup
#pragma warning disable S1075 // URIs should not be hardcoded - this URL is not used for any real request, nor in any telemetry
using var request = new HttpRequestMessage(HttpMethod.Get, "https://localhost:123");
#pragma warning restore S1075 // URIs should not be hardcoded
_ = provider(request);
}
private static HttpResiliencePipelineBuilder AddHttpResiliencePipeline(
this IHttpClientBuilder builder,
string name,
Action<ResiliencePipelineBuilder<HttpResponseMessage>, ResilienceHandlerContext> configure)
{
var pipelineName = PipelineNameHelper.GetName(builder.Name, name);
var key = new HttpKey(pipelineName, string.Empty);
_ = builder.Services.AddResiliencePipeline<HttpKey, HttpResponseMessage>(key, (builder, context) => configure(builder, new ResilienceHandlerContext(context)));
ConfigureHttpServices(builder.Services);
return new(pipelineName, builder.Services);
}
private static void ConfigureHttpServices(IServiceCollection services)
{
// don't add any new service if this method is called multiple times
if (services.Contains(Marker.ServiceDescriptor))
{
return;
}
services.Add(Marker.ServiceDescriptor);
// This code configure the multi-instance support of the registry
_ = services.Configure<ResiliencePipelineRegistryOptions<HttpKey>>(options =>
{
options.BuilderNameFormatter = key => key.Name;
options.InstanceNameFormatter = key => key.InstanceName;
options.BuilderComparer = HttpKey.BuilderComparer;
});
_ = services
.AddExceptionSummarizer(b => b.AddHttpProvider())
.ConfigureFailureResultContext<HttpResponseMessage>((response) =>
{
if (response != null)
{
return FailureResultContext.Create(
failureReason: ((int)response.StatusCode).ToInvariantString(),
additionalInformation: response.StatusCode.ToInvariantString());
}
return FailureResultContext.Create();
});
}
private sealed class Marker
{
public static readonly ServiceDescriptor ServiceDescriptor = ServiceDescriptor.Singleton<Marker, Marker>();
}
private record HttpResiliencePipelineBuilder(string PipelineName, IServiceCollection Services) : IHttpResiliencePipelineBuilder;
}