Skip to content
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
using AAS.TwinEngine.DataEngine.ApplicationLogic.Services.Plugin.Config;
using System.Net;

using AAS.TwinEngine.DataEngine.ApplicationLogic.Services.Plugin.Config;
using AAS.TwinEngine.DataEngine.Infrastructure.Http.Authorization.Headers;
using AAS.TwinEngine.DataEngine.Infrastructure.Http.Config;
using AAS.TwinEngine.DataEngine.Infrastructure.Http.Extensions;
Expand Down Expand Up @@ -154,6 +156,93 @@ public async Task AddHttpClientWithResilience_WithForwarding_AddsHeaderForwardin
.ApplyMappings(httpContextAccessor.HttpContext, Arg.Any<HttpRequestMessage>(), AasEnvironmentConfig.AasEnvironmentRepoHttpClientName);
}

[Fact]
public void AddHttpClientWithResilience_WhenCalled_AddsAcceptEncodingHeaders()
{
var configValues = new Dictionary<string, string>
{
{ $"{HttpRetryPolicyOptions.Section}:{HttpRetryPolicyOptions.TemplateProvider}:MaxRetryAttempts", "3" },
{ $"{HttpRetryPolicyOptions.Section}:{HttpRetryPolicyOptions.TemplateProvider}:DelayInSeconds", "1" }
};
IConfiguration configuration = new ConfigurationBuilder().AddInMemoryCollection(configValues!).Build();

var services = new ServiceCollection();
services.Configure<HttpRetryPolicyOptions>(HttpRetryPolicyOptions.TemplateProvider, configuration.GetSection($"{HttpRetryPolicyOptions.Section}:{HttpRetryPolicyOptions.TemplateProvider}"));
services.AddLogging();
services.AddHttpContextAccessor();
_ = services.AddScoped(_ => Substitute.For<IRequestHeaderMapper>());

services.AddHttpClientWithResilience(configuration, AasEnvironmentConfig.AasEnvironmentRepoHttpClientName, HttpRetryPolicyOptions.TemplateProvider, new Uri("https://example.com"));

var provider = services.BuildServiceProvider();
var client = provider.GetRequiredService<IHttpClientFactory>().CreateClient(AasEnvironmentConfig.AasEnvironmentRepoHttpClientName);

Assert.Contains(client.DefaultRequestHeaders.AcceptEncoding, h => h.Value == "br");
Assert.Contains(client.DefaultRequestHeaders.AcceptEncoding, h => h.Value == "gzip");
}

[Fact]
public void AddHttpClientWithResilience_WhenCalled_EnablesAutomaticDecompressionForGzipAndBrotli()
{
var configValues = new Dictionary<string, string>
{
{ $"{HttpRetryPolicyOptions.Section}:{HttpRetryPolicyOptions.TemplateProvider}:MaxRetryAttempts", "3" },
{ $"{HttpRetryPolicyOptions.Section}:{HttpRetryPolicyOptions.TemplateProvider}:DelayInSeconds", "1" }
};
IConfiguration configuration = new ConfigurationBuilder().AddInMemoryCollection(configValues!).Build();

var services = new ServiceCollection();
services.Configure<HttpRetryPolicyOptions>(HttpRetryPolicyOptions.TemplateProvider, configuration.GetSection($"{HttpRetryPolicyOptions.Section}:{HttpRetryPolicyOptions.TemplateProvider}"));
services.AddLogging();
services.AddHttpContextAccessor();
_ = services.AddScoped(_ => Substitute.For<IRequestHeaderMapper>());

services.AddHttpClientWithResilience(configuration, AasEnvironmentConfig.AasEnvironmentRepoHttpClientName, HttpRetryPolicyOptions.TemplateProvider, new Uri("https://example.com"));

HttpMessageHandler? capturedHandler = null;
services.Configure<HttpClientFactoryOptions>(AasEnvironmentConfig.AasEnvironmentRepoHttpClientName,
options => options.HttpMessageHandlerBuilderActions.Add(b => capturedHandler = b.PrimaryHandler));

var provider = services.BuildServiceProvider();
_ = provider.GetRequiredService<IHttpClientFactory>().CreateClient(AasEnvironmentConfig.AasEnvironmentRepoHttpClientName);

var handler = Assert.IsType<HttpClientHandler>(capturedHandler);
Assert.True(handler.AutomaticDecompression.HasFlag(DecompressionMethods.GZip));
Assert.True(handler.AutomaticDecompression.HasFlag(DecompressionMethods.Brotli));
}

[Fact]
public void AddHttpClientWithoutResilience_WhenCalled_AddsAcceptEncodingHeaders()
{
var services = new ServiceCollection();
services.AddHttpClientWithoutResilience("health-check", new Uri("https://example.com"));

var provider = services.BuildServiceProvider();
var client = provider.GetRequiredService<IHttpClientFactory>().CreateClient("health-check");

Assert.Contains(client.DefaultRequestHeaders.AcceptEncoding, h => h.Value == "br");
Assert.Contains(client.DefaultRequestHeaders.AcceptEncoding, h => h.Value == "gzip");
}

[Fact]
public void AddHttpClientWithoutResilience_WhenCalled_EnablesAutomaticDecompressionForGzipAndBrotli()
{
const string clientName = "health-check";
var services = new ServiceCollection();
services.AddHttpClientWithoutResilience(clientName, new Uri("https://example.com"));

HttpMessageHandler? capturedHandler = null;
services.Configure<HttpClientFactoryOptions>(clientName,
options => options.HttpMessageHandlerBuilderActions.Add(b => capturedHandler = b.PrimaryHandler));

var provider = services.BuildServiceProvider();
_ = provider.GetRequiredService<IHttpClientFactory>().CreateClient(clientName);

var handler = Assert.IsType<HttpClientHandler>(capturedHandler);
Assert.True(handler.AutomaticDecompression.HasFlag(DecompressionMethods.GZip));
Assert.True(handler.AutomaticDecompression.HasFlag(DecompressionMethods.Brotli));
}

private sealed class FaultyHttpMessageHandler : HttpMessageHandler
{
public int CallCount { get; private set; }
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using System.Net.Http.Headers;
using System.Net;

using AAS.TwinEngine.DataEngine.Infrastructure.Http.Authorization;
using AAS.TwinEngine.DataEngine.Infrastructure.Http.Authorization.Headers;
Expand All @@ -9,6 +10,8 @@ namespace AAS.TwinEngine.DataEngine.Infrastructure.Http.Extensions;

public static class HttpClientRegistrationExtensions
{
private static readonly IReadOnlyCollection<string> AcceptEncodings = ["br", "gzip"];

public static IServiceCollection AddHttpClientWithResilience(
this IServiceCollection services,
IConfiguration configuration,
Expand All @@ -22,9 +25,12 @@ public static IServiceCollection AddHttpClientWithResilience(
{
client.BaseAddress = baseUrl;
client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
ConfigureCompression(client);
})
.AddStandardResilienceHandler(retryPolicySectionKey);

httpClientBuilder.ConfigurePrimaryHttpMessageHandler(CreateHandler);

_ = httpClientBuilder.AddHttpMessageHandler(sp =>
new HeaderForwardingHandler(
sp.GetRequiredService<IHttpContextAccessor>(),
Expand All @@ -45,8 +51,37 @@ public static IServiceCollection AddHttpClientWithoutResilience(
client.BaseAddress = baseUrl;
client.Timeout = timeout ?? TimeSpan.FromSeconds(5);
client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
});
ConfigureCompression(client);
})
.ConfigurePrimaryHttpMessageHandler(CreateHandler);

return services;
}

private static void ConfigureCompression(HttpClient client)
{
foreach (var encoding in AcceptEncodings.Where(e => !string.IsNullOrWhiteSpace(e)))
{
client.DefaultRequestHeaders.AcceptEncoding.Add(
new StringWithQualityHeaderValue(encoding));
}
}

private static HttpMessageHandler CreateHandler()
{
return new HttpClientHandler
{
AutomaticDecompression = GetDecompressionMethods()
};
}

private static DecompressionMethods GetDecompressionMethods()
{
return AcceptEncodings.Aggregate(DecompressionMethods.None, (current, encoding) => current | encoding switch
{
"gzip" => DecompressionMethods.GZip,
"br" => DecompressionMethods.Brotli,
_ => DecompressionMethods.None
});
}
}
15 changes: 15 additions & 0 deletions source/AAS.TwinEngine.DataEngine/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,12 @@

using Asp.Versioning;

using Microsoft.AspNetCore.ResponseCompression;

using Serilog;

using System.IO.Compression;

namespace AAS.TwinEngine.DataEngine;

public class Program
Expand All @@ -18,6 +22,15 @@ public class Program
public static async Task Main(string[] args)
{
var builder = WebApplication.CreateBuilder(args);
_ = builder.Services.AddResponseCompression(options =>
{
options.EnableForHttps = true;
options.Providers.Add<BrotliCompressionProvider>();
options.Providers.Add<GzipCompressionProvider>();
});
_ = builder.Services.Configure<BrotliCompressionProviderOptions>(options => options.Level = CompressionLevel.Optimal);
_ = builder.Services.Configure<GzipCompressionProviderOptions>(options => options.Level = CompressionLevel.Optimal);

_ = builder.Host.UseSerilog();
builder.ConfigureLogging(builder.Configuration);
builder.ConfigureCorsServices();
Expand Down Expand Up @@ -71,6 +84,8 @@ public static async Task Main(string[] args)
_ = app.UseExceptionHandler();
_ = app.UseMiddleware<HeaderSanitizationMiddleware>();
_ = app.UseHttpsRedirection();
_ = app.UseResponseCompression();

_ = app.UseAuthorization();
app.UseCorsServices();
_ = app.UseOpenApi(c => c.PostProcess = (d, _) => d.Servers.Clear());
Expand Down
Loading