using System.Net;
using Common.Configuration.DownloadClient;
using Common.Configuration.General;
using Infrastructure.Configuration;
using Infrastructure.Services;
using Microsoft.Extensions.Logging;
using Polly;
using Polly.Extensions.Http;
namespace Infrastructure.Http;
///
/// Provides dynamically configured HTTP clients for download services
///
public class DynamicHttpClientProvider : IDynamicHttpClientProvider
{
private readonly ILogger _logger;
private readonly IHttpClientFactory _httpClientFactory;
private readonly IConfigManager _configManager;
private readonly CertificateValidationService _certificateValidationService;
public DynamicHttpClientProvider(
ILogger logger,
IHttpClientFactory httpClientFactory,
IConfigManager configManager,
CertificateValidationService certificateValidationService)
{
_logger = logger;
_httpClientFactory = httpClientFactory;
_configManager = configManager;
_certificateValidationService = certificateValidationService;
}
///
public HttpClient CreateClient(ClientConfig clientConfig)
{
if (clientConfig == null)
{
throw new ArgumentNullException(nameof(clientConfig));
}
// Try to use named client if it exists
try
{
string clientName = GetClientName(clientConfig);
return _httpClientFactory.CreateClient(clientName);
}
catch (InvalidOperationException)
{
_logger.LogWarning("Named HTTP client for {clientId} not found, creating generic client", clientConfig.Id);
return CreateGenericClient(clientConfig);
}
}
///
/// Gets the client name for a specific client configuration
///
/// The client configuration
/// The client name for use with IHttpClientFactory
private string GetClientName(ClientConfig clientConfig)
{
return $"DownloadClient_{clientConfig.Id}";
}
///
/// Creates a generic HTTP client with appropriate configuration
///
/// The client configuration
/// A configured HttpClient instance
private HttpClient CreateGenericClient(ClientConfig clientConfig)
{
// TODO
var httpConfig = _configManager.GetConfiguration();
// Create handler with certificate validation
var handler = new HttpClientHandler
{
ServerCertificateCustomValidationCallback = (sender, certificate, chain, sslPolicyErrors) =>
_certificateValidationService.ShouldByPassValidationError(
httpConfig.CertificateValidation,
sender,
certificate,
chain,
sslPolicyErrors
),
UseDefaultCredentials = false
};
if (clientConfig.Type == Common.Enums.DownloadClientType.Deluge)
{
handler.AllowAutoRedirect = true;
handler.UseCookies = true;
handler.CookieContainer = new CookieContainer();
handler.AutomaticDecompression = DecompressionMethods.GZip | DecompressionMethods.Deflate;
}
// Create client with policy
var client = new HttpClient(handler)
{
Timeout = TimeSpan.FromSeconds(httpConfig.HttpTimeout)
};
// Set base address if needed
if (clientConfig.Url != null)
{
client.BaseAddress = clientConfig.Url;
}
_logger.LogDebug("Created generic HTTP client for client {clientId} with base address {baseAddress}",
clientConfig.Id, client.BaseAddress);
return client;
}
///
/// Creates a retry policy for the HTTP client
///
/// The HTTP configuration
/// A configured policy
private static IAsyncPolicy CreateRetryPolicy(GeneralConfig generalConfig)
{
return HttpPolicyExtensions
.HandleTransientHttpError()
// Do not retry on Unauthorized
.OrResult(response => !response.IsSuccessStatusCode && response.StatusCode != HttpStatusCode.Unauthorized)
.WaitAndRetryAsync(generalConfig.HttpMaxRetries, retryAttempt => TimeSpan.FromSeconds(Math.Pow(2, retryAttempt)));
}
}