in src/Microsoft.Azure.SignalR.Management/DependencyInjectionExtensions.cs [175:262]
private static IServiceCollection AddRestClient(this IServiceCollection services)
{
// For internal health check. Not impacted by user set timeout.
services
.AddHttpClient(Constants.HttpClientNames.InternalDefault, ConfigureProduceInfo)
.ConfigurePrimaryHttpMessageHandler(ConfigureProxy);
// Used by user. Impacted by user set timeout.
services.AddSingleton(sp => sp.GetRequiredService<PayloadBuilderResolver>().GetPayloadContentBuilder())
.AddSingleton<RestClient>()
.AddSingleton<IBackOffPolicy>(sp =>
{
var options = sp.GetRequiredService<IOptions<ServiceManagerOptions>>().Value;
var retryOptions = options.RetryOptions;
return retryOptions == null
? new DummyBackOffPolicy()
: retryOptions.Mode switch
{
ServiceManagerRetryMode.Fixed => ActivatorUtilities.CreateInstance<FixedBackOffPolicy>(sp),
ServiceManagerRetryMode.Exponential => ActivatorUtilities.CreateInstance<ExponentialBackOffPolicy>(sp),
_ => throw new NotSupportedException($"Retry mode {retryOptions.Mode} is not supported.")
};
});
services
.AddHttpClient(Constants.HttpClientNames.UserDefault, (sp, client) =>
{
ConfigureUserTimeout(sp, client);
ConfigureProduceInfo(sp, client);
})
.ConfigurePrimaryHttpMessageHandler(ConfigureProxy);
services
.AddHttpClient(Constants.HttpClientNames.Resilient, (sp, client) =>
{
var options = sp.GetRequiredService<IOptions<ServiceManagerOptions>>().Value;
if (options.RetryOptions == null)
{
client.Timeout = options.HttpClientTimeout;
}
else
{
// The timeout is enforced by TimeoutHttpMessageHandler.
client.Timeout = Timeout.InfiniteTimeSpan;
}
ConfigureProduceInfo(sp, client);
ConfigureMessageTracingId(sp, client);
})
.ConfigurePrimaryHttpMessageHandler(ConfigureProxy)
.AddHttpMessageHandler(sp => ActivatorUtilities.CreateInstance<RetryHttpMessageHandler>(sp, (HttpStatusCode code) => IsTransientErrorForNonMessageApi(code)))
.AddHttpMessageHandler(sp => ActivatorUtilities.CreateInstance<TimeoutHttpMessageHandler>(sp));
services
.AddHttpClient(Constants.HttpClientNames.MessageResilient, (sp, client) =>
{
ConfigureUserTimeout(sp, client);
ConfigureProduceInfo(sp, client);
ConfigureMessageTracingId(sp, client);
})
.ConfigurePrimaryHttpMessageHandler(ConfigureProxy)
.AddHttpMessageHandler(sp => ActivatorUtilities.CreateInstance<RetryHttpMessageHandler>(sp, (HttpStatusCode code) => IsTransientErrorAndIdempotentForMessageApi(code)));
return services;
static HttpMessageHandler ConfigureProxy(IServiceProvider sp) => new HttpClientHandler() { Proxy = sp.GetRequiredService<IOptions<ServiceManagerOptions>>().Value.Proxy };
static bool IsTransientErrorAndIdempotentForMessageApi(HttpStatusCode code) =>
// Runtime returns 500 for timeout errors too, to avoid duplicate message, we exclude 500 here.
code > HttpStatusCode.InternalServerError;
static bool IsTransientErrorForNonMessageApi(HttpStatusCode code) =>
code >= HttpStatusCode.InternalServerError ||
code == HttpStatusCode.RequestTimeout;
static void ConfigureUserTimeout(IServiceProvider sp, HttpClient client) => client.Timeout = sp.GetRequiredService<IOptions<ServiceManagerOptions>>().Value.HttpClientTimeout;
static void ConfigureProduceInfo(IServiceProvider sp, HttpClient client) =>
client.DefaultRequestHeaders.Add(Constants.AsrsUserAgent, sp.GetRequiredService<IOptions<ServiceManagerOptions>>().Value.ProductInfo ??
// The following value should not be used.
"Microsoft.Azure.SignalR.Management/");
static void ConfigureMessageTracingId(IServiceProvider sp, HttpClient client)
{
if (sp.GetRequiredService<IOptions<ServiceManagerOptions>>().Value.EnableMessageTracing)
{
client.DefaultRequestHeaders.Add(Constants.Headers.AsrsMessageTracingId, MessageWithTracingIdHelper.Generate().ToString(CultureInfo.InvariantCulture));
}
}
}