Add option to send the User-Agent header on all HTTP requests (#750)

This commit is contained in:
Flaminel authored and GitHub committed 2026-08-22 22:18:49 +03:00
1 parent 8f8f30c37a
commit 41dbbf155b
38 files changed
+4878 -67

No files matched your search

@@ -1,5 +1,6 @@
using System.Net;
using Cleanuparr.Infrastructure.Services;
using Cleanuparr.Shared.Helpers;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Http;
using Microsoft.Extensions.Options;
@@ -24,10 +25,20 @@ public class DynamicHttpClientConfiguration : IConfigureNamedOptions<HttpClientF
{
using var scope = _scopeFactory.CreateScope();
var configStore = scope.ServiceProvider.GetRequiredService<IHttpClientConfigStore>();
if (!configStore.TryGetConfiguration(name, out HttpClientConfig? config))
return;
if (config.SendUserAgent)
{
options.HttpClientActions.Add(ApplyUserAgent);
}
if (config.Type is HttpClientType.Plain)
{
return;
}
// Configure the HttpClient
options.HttpClientActions.Add(httpClient =>
{
@@ -47,6 +58,11 @@ public class DynamicHttpClientConfiguration : IConfigureNamedOptions<HttpClientF
});
}
internal static void ApplyUserAgent(HttpClient client)
{
client.DefaultRequestHeaders.UserAgent.ParseAdd(AppUserAgent.Value);
}
private void ConfigureHandler(HttpMessageHandlerBuilder builder, HttpClientConfig config)
{
using var scope = _scopeFactory.CreateScope();
@@ -130,6 +146,7 @@ public class DynamicHttpClientConfiguration : IConfigureNamedOptions<HttpClientF
public void Configure(HttpClientFactoryOptions options)
{
// This is called for unnamed clients - we don't need to do anything here
// Never called: the factory prefers the named overload.
// Unnamed clients reach that one with an empty name.
}
}
@@ -50,23 +50,24 @@ public class DynamicHttpClientFactory : IDynamicHttpClientFactory
_configStore.AddConfiguration(clientName, config);
}
public void RegisterRetryClient(string clientName, int timeout, RetryConfig retryConfig, CertificateValidationType certificateType)
public void RegisterRetryClient(string clientName, int timeout, RetryConfig retryConfig, CertificateValidationType certificateType, bool sendUserAgent)
{
var config = new HttpClientConfig
HttpClientConfig config = new()
{
Name = clientName,
Timeout = timeout,
Type = HttpClientType.WithRetry,
RetryConfig = retryConfig,
CertificateValidationType = certificateType
CertificateValidationType = certificateType,
SendUserAgent = sendUserAgent
};
RegisterConfiguration(clientName, config);
}
public void RegisterDelugeClient(string clientName, int timeout, RetryConfig retryConfig, CertificateValidationType certificateType)
public void RegisterDelugeClient(string clientName, int timeout, RetryConfig retryConfig, CertificateValidationType certificateType, bool sendUserAgent)
{
var config = new HttpClientConfig
HttpClientConfig config = new()
{
Name = clientName,
Timeout = timeout,
@@ -74,21 +75,23 @@ public class DynamicHttpClientFactory : IDynamicHttpClientFactory
RetryConfig = retryConfig,
AllowAutoRedirect = true,
CertificateValidationType = certificateType,
AutomaticDecompression = DecompressionMethods.GZip | DecompressionMethods.Deflate
AutomaticDecompression = DecompressionMethods.GZip | DecompressionMethods.Deflate,
SendUserAgent = sendUserAgent
};
RegisterConfiguration(clientName, config);
}
public void RegisterDownloadClient(string clientName, int timeout, HttpClientType clientType, RetryConfig retryConfig, CertificateValidationType certificateType)
public void RegisterDownloadClient(string clientName, int timeout, HttpClientType clientType, RetryConfig retryConfig, CertificateValidationType certificateType, bool sendUserAgent)
{
var config = new HttpClientConfig
HttpClientConfig config = new()
{
Name = clientName,
Timeout = timeout,
Type = clientType,
RetryConfig = retryConfig,
CertificateValidationType = certificateType
CertificateValidationType = certificateType,
SendUserAgent = sendUserAgent
};
// Configure Deluge-specific settings if needed
@@ -97,7 +100,19 @@ public class DynamicHttpClientFactory : IDynamicHttpClientFactory
config.AllowAutoRedirect = true;
config.AutomaticDecompression = DecompressionMethods.GZip | DecompressionMethods.Deflate;
}
RegisterConfiguration(clientName, config);
}
public void RegisterPlainClient(string clientName, bool sendUserAgent)
{
HttpClientConfig config = new()
{
Name = clientName,
Type = HttpClientType.Plain,
SendUserAgent = sendUserAgent
};
RegisterConfiguration(clientName, config);
}
@@ -114,7 +129,7 @@ public class DynamicHttpClientFactory : IDynamicHttpClientFactory
public void UpdateAllClientsFromGeneralConfig(GeneralConfig generalConfig)
{
var allConfigurations = _configStore.GetAllConfigurations().ToList();
if (!allConfigurations.Any())
{
_logger.LogDebug("No HTTP client configurations to update");
@@ -128,7 +143,8 @@ public class DynamicHttpClientFactory : IDynamicHttpClientFactory
// Update timeout and certificate validation for all clients
config.Timeout = generalConfig.HttpTimeout;
config.CertificateValidationType = generalConfig.HttpCertificateValidation;
config.SendUserAgent = generalConfig.HttpSendUserAgent;
// Update retry configuration if it exists
if (config.RetryConfig != null)
{
@@ -12,30 +12,15 @@ public class HttpClientConfig
public int Timeout { get; set; }
public HttpClientType Type { get; set; }
public RetryConfig? RetryConfig { get; set; }
// Deluge-specific settings
public bool AllowAutoRedirect { get; set; } = true;
public DecompressionMethods AutomaticDecompression { get; set; } = DecompressionMethods.GZip | DecompressionMethods.Deflate;
public CertificateValidationType CertificateValidationType { get; set; } = CertificateValidationType.Enabled;
}
/// <summary>
/// Retry configuration for HTTP clients
/// </summary>
public class RetryConfig
{
public int MaxRetries { get; set; }
public bool ExcludeUnauthorized { get; set; } = true;
/// <summary>
/// Whether requests from this client carry a User-Agent header.
/// </summary>
public bool SendUserAgent { get; set; }
}
/// <summary>
/// Types of HTTP clients that can be configured
/// </summary>
public enum HttpClientType
{
Default,
WithRetry,
Deluge,
UTorrent,
}
@@ -47,7 +47,8 @@ public class HttpClientConfigurationService : IHostedService
MaxRetries = config.HttpMaxRetries,
ExcludeUnauthorized = true
},
config.HttpCertificateValidation
config.HttpCertificateValidation,
config.HttpSendUserAgent
);
// Register the Deluge client
@@ -59,9 +60,17 @@ public class HttpClientConfigurationService : IHostedService
MaxRetries = config.HttpMaxRetries,
ExcludeUnauthorized = true
},
config.HttpCertificateValidation
config.HttpCertificateValidation,
config.HttpSendUserAgent
);
// These keep the handler defaults they had before the dynamic system existed.
// Registering them only puts them in reach of the User-Agent setting.
_clientFactory.RegisterPlainClient(Constants.HttpClientPlexAuthName, config.HttpSendUserAgent);
_clientFactory.RegisterPlainClient(Constants.HttpClientOidcAuthName, config.HttpSendUserAgent);
_clientFactory.RegisterPlainClient(Constants.HttpClientConnectivityName, config.HttpSendUserAgent);
_logger.LogInformation("Pre-registered standard HTTP client configurations");
}
catch (Exception ex)
@@ -0,0 +1,18 @@
namespace Cleanuparr.Infrastructure.Http.DynamicHttpClientSystem;
/// <summary>
/// Types of HTTP clients that can be configured
/// </summary>
public enum HttpClientType
{
Default,
WithRetry,
Deluge,
UTorrent,
/// <summary>
/// Carries the User-Agent and nothing else.
/// No primary handler, no timeout override, no retry policy.
/// </summary>
Plain,
}
@@ -26,18 +26,23 @@ public interface IDynamicHttpClientFactory
/// <summary>
/// Registers a retry-enabled HttpClient configuration
/// </summary>
void RegisterRetryClient(string clientName, int timeout, RetryConfig retryConfig, CertificateValidationType certificateType);
void RegisterRetryClient(string clientName, int timeout, RetryConfig retryConfig, CertificateValidationType certificateType, bool sendUserAgent);
/// <summary>
/// Registers a Deluge-specific HttpClient configuration
/// </summary>
void RegisterDelugeClient(string clientName, int timeout, RetryConfig retryConfig, CertificateValidationType certificateType);
void RegisterDelugeClient(string clientName, int timeout, RetryConfig retryConfig, CertificateValidationType certificateType, bool sendUserAgent);
/// <summary>
/// Registers a configuration for a download client
/// </summary>
void RegisterDownloadClient(string clientName, int timeout, HttpClientType clientType, RetryConfig retryConfig, CertificateValidationType certificateType);
void RegisterDownloadClient(string clientName, int timeout, HttpClientType clientType, RetryConfig retryConfig, CertificateValidationType certificateType, bool sendUserAgent);
/// <summary>
/// Registers a client that opts out of every handler setting and only carries the User-Agent.
/// </summary>
void RegisterPlainClient(string clientName, bool sendUserAgent);
/// <summary>
/// Unregisters a configuration
/// </summary>
@@ -0,0 +1,10 @@
namespace Cleanuparr.Infrastructure.Http.DynamicHttpClientSystem;
/// <summary>
/// Retry configuration for HTTP clients
/// </summary>
public class RetryConfig
{
public int MaxRetries { get; set; }
public bool ExcludeUnauthorized { get; set; } = true;
}