HttpClientBuilder implementation

This commit is contained in:
2024-08-16 13:43:21 +02:00
parent df2e270481
commit 40cf2d6fc6
5 changed files with 213 additions and 3 deletions
@@ -0,0 +1,40 @@
using AspNetCore.Extensions;
using AspNetCore.Extensions.Options;
using Microsoft.CorrelationVector;
using Microsoft.Extensions.Options;
using System.Core.Extensions;
namespace Net.Sdk.Web.Extensions.Http;
public sealed class CorrelationVectorHandler : DelegatingHandler
{
private readonly CorrelationVectorOptions options;
private readonly IHttpContextAccessor httpContextAccessor;
public CorrelationVectorHandler(
IOptions<CorrelationVectorOptions> options,
IHttpContextAccessor httpContextAccessor)
{
this.options = options.ThrowIfNull().Value.ThrowIfNull();
this.httpContextAccessor = httpContextAccessor.ThrowIfNull();
}
protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
{
if (this.httpContextAccessor.HttpContext?.GetCorrelationVector() is CorrelationVector cv)
{
cv.Increment();
request.Headers.Add(this.options.Header, cv.Value);
this.httpContextAccessor.HttpContext.SetCorrelationVector(cv);
}
var response = await base.SendAsync(request, cancellationToken);
if (response.Headers.TryGetValues(this.options.Header, out var cvHeaders) &&
cvHeaders.FirstOrDefault() is string responseCv)
{
this.httpContextAccessor.HttpContext?.SetCorrelationVector(CorrelationVector.Parse(responseCv));
}
return response;
}
}
@@ -0,0 +1,128 @@
using System.Core.Extensions;
namespace Net.Sdk.Web.Extensions.Http;
public sealed class HttpClientBuilder<T>
where T : class
{
private readonly WebApplicationBuilder app;
private readonly List<Func<IServiceProvider, DelegatingHandler>> handlers = [];
private readonly List<Func<IServiceProvider, (string Name, IEnumerable<string> Values)>> defaultRequestHeaders = [];
private readonly List<string> redactedLoggedHeaderNames = [];
private readonly List<Func<string, bool>> headerRedactHandlers = [];
private bool defaultLogger = false;
private Uri? baseAddress;
private long maxResponseBufferSize = 2147483647L; //2GB default HttpClient value [https://docs.microsoft.com/en-us/dotnet/api/system.net.http.httpclient.maxresponsecontentbuffersize?view=net-6.0]
private TimeSpan timeout = TimeSpan.FromSeconds(100); //100 seconds default HttpClient value [https://docs.microsoft.com/en-us/dotnet/api/system.net.http.httpclient.timeout?view=net-6.0]
internal HttpClientBuilder(WebApplicationBuilder app)
{
this.app = app.ThrowIfNull();
}
public HttpClientBuilder<T> WithBaseAddress(Uri baseAddress)
{
baseAddress.ThrowIfNull();
this.baseAddress = baseAddress;
return this;
}
public HttpClientBuilder<T> WithDelegatingHandler(Func<IServiceProvider, DelegatingHandler> handlerFactory)
{
handlerFactory.ThrowIfNull();
this.handlers.Add(handlerFactory);
return this;
}
public HttpClientBuilder<T> WithDefaultRequestHeaders(Func<IServiceProvider, (string Name, IEnumerable<string> Values)> headerFactory)
{
headerFactory.ThrowIfNull();
this.defaultRequestHeaders.Add(headerFactory);
return this;
}
public HttpClientBuilder<T> WithMaxResponseBufferSize(long responseBufferSize)
{
this.maxResponseBufferSize = responseBufferSize;
return this;
}
public HttpClientBuilder<T> WithRedactedLoggerHeaderName(string headerName)
{
this.redactedLoggedHeaderNames.Add(headerName);
return this;
}
public HttpClientBuilder<T> WithRedactedLoggerHeaderName(Func<string, bool> handler)
{
this.headerRedactHandlers.Add(handler);
return this;
}
public HttpClientBuilder<T> WithTimeout(TimeSpan timeout)
{
this.timeout = timeout;
return this;
}
public HttpClientBuilder<T> AddDefaultLogger()
{
this.defaultLogger = true;
return this;
}
public WebApplicationBuilder Build()
{
this.CreateBuilder();
return this.app;
}
public IHttpClientBuilder CreateBuilder()
{
var builder = this.app.Services.AddHttpClient(typeof(T).Name);
foreach (var handler in this.handlers)
{
builder.AddHttpMessageHandler(handler);
}
builder.ConfigureHttpClient((sp, client) =>
{
client.BaseAddress = this.baseAddress;
client.Timeout = this.timeout;
client.MaxResponseContentBufferSize = this.maxResponseBufferSize;
foreach (var header in this.defaultRequestHeaders)
{
(var name, var values) = header(sp);
client.DefaultRequestHeaders.Add(name, values);
}
});
if (this.defaultLogger)
{
builder.AddDefaultLogger();
}
if (this.redactedLoggedHeaderNames.Count > 0)
{
builder.RedactLoggedHeaders(this.redactedLoggedHeaderNames);
}
foreach (var handler in this.headerRedactHandlers)
{
builder.RedactLoggedHeaders(handler);
}
this.app.Services.AddScoped<IHttpClient<T>>(sp =>
{
var clientBuilder = sp.GetRequiredService<IHttpClientFactory>();
var innerClient = clientBuilder.CreateClient(typeof(T).Name);
return new HttpClient<T>(innerClient);
});
return builder;
}
}
@@ -6,7 +6,7 @@
<Nullable>enable</Nullable>
<OutputType>Library</OutputType>
<IsPackable>true</IsPackable>
<Version>0.1</Version>
<Version>0.5</Version>
<Authors>Alexandru Macocian</Authors>
<PackageLicenseFile>LICENSE</PackageLicenseFile>
<PackageRequireLicenseAcceptance>true</PackageRequireLicenseAcceptance>
@@ -26,7 +26,8 @@
<PackageReference Include="Microsoft.CorrelationVector" Version="1.0.42" />
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="8.0.0" />
<PackageReference Include="SystemExtensions.NetCore" Version="1.0.1" />
<PackageReference Include="SystemExtensions.NetStandard" Version="1.6.3" />
<PackageReference Include="SystemExtensions.NetStandard" Version="1.6.4" />
<PackageReference Include="SystemExtensions.NetStandard.DependencyInjection" Version="1.4.0" />
</ItemGroup>
</Project>
@@ -1,4 +1,6 @@
using AspNetCore.Extensions.Attributes;
using AspNetCore.Extensions.Middleware;
using Net.Sdk.Web.Extensions.Http;
using System.Core.Extensions;
using System.Extensions;
@@ -20,6 +22,36 @@ public static class WebApplicationBuilderExtensions
return configurationManager.GetRequiredSection(GetOptionsName<TOptions>());
}
public static WebApplicationBuilder WithCorrelationVector(this WebApplicationBuilder builder)
{
builder.ThrowIfNull();
builder.Services.AddScoped<CorrelationVectorMiddleware>();
builder.Services.AddScoped<CorrelationVectorHandler>();
return builder;
}
public static Net.Sdk.Web.Extensions.Http.HttpClientBuilder<T> RegisterHttpClient<T>(this WebApplicationBuilder builder)
where T : class
{
_ = builder.ThrowIfNull();
return new Net.Sdk.Web.Extensions.Http.HttpClientBuilder<T>(builder);
}
public static Net.Sdk.Web.Extensions.Http.HttpClientBuilder<T> WithCorrelationVector<T>(this Net.Sdk.Web.Extensions.Http.HttpClientBuilder<T> httpClientBuilder)
where T : class
{
return httpClientBuilder.ThrowIfNull()
.WithDelegatingHandler(sp => sp.GetRequiredService<CorrelationVectorHandler>());
}
public static IHttpClientBuilder WithCorrelationVector<T>(this IHttpClientBuilder httpClientBuilder)
where T : class
{
return httpClientBuilder.ThrowIfNull()
.AddHttpMessageHandler<CorrelationVectorHandler>();
}
private static string GetOptionsName<TOptions>()
{
var maybeAttribute = typeof(TOptions).GetCustomAttributes(false).OfType<OptionsNameAttribute>().FirstOrDefault();
@@ -1,4 +1,5 @@
using Microsoft.AspNetCore.Mvc;
using AspNetCore.Extensions.Middleware;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Abstractions;
using Microsoft.AspNetCore.Mvc.Filters;
using System.Core.Extensions;
@@ -10,6 +11,14 @@ namespace AspNetCore.Extensions.Websockets.Extensions;
public static class WebApplicationExtensions
{
public static WebApplication UseCorrelationVector(this WebApplication webApplication)
{
webApplication.ThrowIfNull()
.UseMiddleware<CorrelationVectorMiddleware>();
return webApplication;
}
public static WebApplication MapWebSocket<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors)] TWebSocketRoute>(this WebApplication app, string route)
where TWebSocketRoute : WebSocketRouteBase
{