Skip to content

Commit 40b3782

Browse files
committed
Make DefaultHttpClientFactory singleton ready, by supporting named clients + Disable content buffering by default in HttpRequestTracer
1 parent dc839e4 commit 40b3782

27 files changed

Lines changed: 575 additions & 381 deletions

src/Dibix.Http.Client/Client/DefaultHttpClientFactory.cs

Lines changed: 148 additions & 113 deletions
Large diffs are not rendered by default.

src/Dibix.Http.Client/Client/Handlers/BearerAuthorizationHttpMessageHandler.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,14 +15,14 @@ public BearerAuthorizationHttpMessageHandler() { }
1515

1616
protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
1717
{
18-
string token = await this.GetToken().ConfigureAwait(false);
18+
string token = await this.GetToken(request).ConfigureAwait(false);
1919
if (String.IsNullOrEmpty(token))
2020
throw new InvalidOperationException("Bearer token is empty");
2121

2222
request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", token);
2323
return await base.SendAsync(request, cancellationToken).ConfigureAwait(false);
2424
}
2525

26-
protected virtual Task<string> GetToken() => Task.FromResult(this._token);
26+
protected virtual Task<string> GetToken(HttpRequestMessage requestMessage) => Task.FromResult(this._token);
2727
}
2828
}

src/Dibix.Http.Client/Client/Handlers/TracingHttpMessageHandler.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -47,9 +47,9 @@ protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage
4747
#endregion
4848

4949
#region Private Methods
50-
private Task TraceRequest(HttpRequestMessage request) => this._tracer.TraceRequestAsync(request);
50+
private Task TraceRequest(HttpRequestMessage request) => this._tracer.TraceRequestMessageAsync(request);
5151

52-
private Task TraceResponse(HttpResponseMessage response) => this._tracer.TraceResponseAsync(response, this._requestDurationTracker.Elapsed);
52+
private Task TraceResponse(HttpResponseMessage response) => this._tracer.TraceResponseMessageAsync(response, this._requestDurationTracker.Elapsed);
5353

5454
private void StartTracking() => this._requestDurationTracker.Restart();
5555

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
namespace Dibix.Http.Client
2+
{
3+
public abstract class HttpClientConfiguration
4+
{
5+
public abstract string Name { get; }
6+
7+
public abstract void Configure(IHttpClientBuilder builder);
8+
}
9+
}

src/Dibix.Http.Client/Client/HttpMessageFormatter.cs

Lines changed: 0 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,6 @@
77
using System.Net.Http.Headers;
88
using System.Text;
99
using System.Text.RegularExpressions;
10-
using System.Threading.Tasks;
1110

1211
namespace Dibix.Http.Client
1312
{
@@ -40,14 +39,6 @@ public static string Format(string formattedRequestText, string formattedRespons
4039
#endregion
4140

4241
#region Internal Methods
43-
internal static async Task<string> Format(HttpRequestMessage requestMessage, bool maskSensitiveData)
44-
{
45-
string requestMessageContent = null;
46-
if (requestMessage.Content != null)
47-
requestMessageContent = await requestMessage.Content.ReadAsStringAsync().ConfigureAwait(false);
48-
49-
return Format(requestMessage, requestMessageContent, maskSensitiveData);
50-
}
5142
internal static string Format(HttpRequestMessage requestMessage, string requestContentText, bool maskSensitiveData)
5243
{
5344
Guard.IsNotNull(requestMessage, nameof(requestMessage));
@@ -81,11 +72,6 @@ internal static string Format(HttpRequestMessage requestMessage, string requestC
8172
return formattedRequest;
8273
}
8374

84-
internal static async Task<string> Format(HttpResponseMessage responseMessage)
85-
{
86-
string responseContentText = await responseMessage.Content.ReadAsStringAsync().ConfigureAwait(false);
87-
return Format(responseMessage, responseContentText);
88-
}
8975
internal static string Format(HttpResponseMessage responseMessage, string responseContentText)
9076
{
9177
Guard.IsNotNull(responseMessage, nameof(responseMessage));

src/Dibix.Http.Client/Client/HttpRequestTracer.cs

Lines changed: 71 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -6,47 +6,99 @@ namespace Dibix.Http.Client
66
{
77
public class HttpRequestTracer
88
{
9-
public bool MaskSensitiveData { get; set; } = true;
9+
public bool MaskSensitiveContent { get; } = true;
1010
public HttpRequestTrace LastRequest { get; private set; }
1111

12-
internal async Task TraceRequestAsync(HttpRequestMessage requestMessage)
12+
public HttpRequestTracer() { }
13+
public HttpRequestTracer(bool maskSensitiveContent)
1314
{
14-
string requestContentText = await this.GetRequestContentTextAsync(requestMessage).ConfigureAwait(false);
15-
string formattedRequestText = this.FormatRequest(requestMessage, requestContentText);
16-
await this.TraceRequestAsync(requestMessage, formattedRequestText).ConfigureAwait(false);
15+
this.MaskSensitiveContent = maskSensitiveContent;
16+
}
17+
18+
internal async Task TraceRequestMessageAsync(HttpRequestMessage requestMessage)
19+
{
20+
await this.CollectLastRequest(requestMessage).ConfigureAwait(false);
21+
await this.TraceRequestAsync(requestMessage).ConfigureAwait(false);
1722
}
1823

19-
internal async Task TraceResponseAsync(HttpResponseMessage responseMessage, TimeSpan duration)
24+
internal async Task TraceResponseMessageAsync(HttpResponseMessage responseMessage, TimeSpan duration)
2025
{
21-
string responseContentTest = await this.GetResponseContentTextAsync(responseMessage).ConfigureAwait(false);
22-
string formattedResponseText = this.FormatResponse(responseMessage, responseContentTest);
23-
await this.TraceResponseAsync(responseMessage, formattedResponseText, duration).ConfigureAwait(false);
26+
await CompleteLastRequest(responseMessage, duration).ConfigureAwait(false);
27+
await this.TraceResponseAsync(responseMessage, duration);
2428
}
2529

26-
protected virtual Task TraceRequestAsync(HttpRequestMessage requestMessage, string formattedRequestText)
30+
protected virtual Task TraceRequestAsync(HttpRequestMessage requestMessage) => Task.CompletedTask;
31+
32+
protected virtual Task TraceResponseAsync(HttpResponseMessage responseMessage, TimeSpan duration) => Task.CompletedTask;
33+
34+
protected virtual bool ShouldBufferRequestContent(HttpRequestMessage requestMessage) => false;
35+
36+
protected virtual bool ShouldBufferResponseContent(HttpRequestMessage requestMessage) => false;
37+
38+
private async Task CollectLastRequest(HttpRequestMessage requestMessage)
2739
{
40+
string formattedRequestText = await this.GetFormattedRequestContent(requestMessage).ConfigureAwait(false);
2841
this.LastRequest = new HttpRequestTrace(requestMessage, formattedRequestText);
29-
return Task.CompletedTask;
3042
}
3143

32-
protected virtual Task TraceResponseAsync(HttpResponseMessage responseMessage, string formattedResponseText, TimeSpan duration)
44+
private async Task CompleteLastRequest(HttpResponseMessage responseMessage, TimeSpan duration)
3345
{
3446
if (this.LastRequest == null)
3547
throw new InvalidOperationException("Request not initialized");
3648

49+
string formattedResponseText = await this.GetFormattedResponseContent(responseMessage).ConfigureAwait(false);
3750
this.LastRequest.ResponseMessage = responseMessage;
3851
this.LastRequest.FormattedResponseText = formattedResponseText;
3952
this.LastRequest.Duration = duration;
40-
41-
return Task.CompletedTask;
53+
54+
// Since non successful status code will throw an exception,
55+
// it is now safe to restore the request content, that was not previously captured
56+
HttpRequestMessage requestMessage = responseMessage.RequestMessage;
57+
if (!this.ShouldBufferRequestContent(requestMessage) && !responseMessage.IsSuccessStatusCode)
58+
{
59+
string requestContentText = await GetRequestContentTextAsync(requestMessage, bufferRequestContent: true).ConfigureAwait(false);
60+
this.LastRequest.FormattedRequestText = this.FormatRequest(requestMessage, requestContentText);
61+
}
62+
}
63+
64+
private async Task<string> GetFormattedRequestContent(HttpRequestMessage requestMessage)
65+
{
66+
bool bufferRequestContent = this.ShouldBufferRequestContent(requestMessage);
67+
string requestContentText = await GetRequestContentTextAsync(requestMessage, bufferRequestContent).ConfigureAwait(false);
68+
string formattedRequestText = this.FormatRequest(requestMessage, requestContentText);
69+
return formattedRequestText;
70+
}
71+
72+
private async Task<string> GetFormattedResponseContent(HttpResponseMessage responseMessage)
73+
{
74+
bool bufferResponseContent = this.ShouldBufferResponseContent(responseMessage.RequestMessage) || !responseMessage.IsSuccessStatusCode;
75+
string responseContentTest = await GetResponseContentTextAsync(responseMessage, bufferResponseContent).ConfigureAwait(false);
76+
string formattedResponseText = HttpMessageFormatter.Format(responseMessage, responseContentTest);
77+
return formattedResponseText;
78+
}
79+
80+
private string FormatRequest(HttpRequestMessage requestMessage, string requestContentText) => HttpMessageFormatter.Format(requestMessage, requestContentText, this.MaskSensitiveContent);
81+
82+
private static async Task<string> GetRequestContentTextAsync(HttpRequestMessage requestMessage, bool bufferRequestContent)
83+
{
84+
if (requestMessage.Content == null)
85+
return null;
86+
87+
if (!bufferRequestContent)
88+
return "<Request content unavailable>";
89+
90+
return await requestMessage.Content.ReadAsStringAsync().ConfigureAwait(false);
4291
}
4392

44-
protected virtual string FormatRequest(HttpRequestMessage requestMessage, string requestContentText) => HttpMessageFormatter.Format(requestMessage, requestContentText, this.MaskSensitiveData);
93+
private static async Task<string> GetResponseContentTextAsync(HttpResponseMessage responseMessage, bool bufferResponseContent)
94+
{
95+
if (responseMessage.Content == null)
96+
return null;
4597

46-
protected virtual string FormatResponse(HttpResponseMessage responseMessage, string responseContentTest) => HttpMessageFormatter.Format(responseMessage, responseContentTest);
98+
if (!bufferResponseContent)
99+
return "<Response content unavailable>";
47100

48-
protected virtual async Task<string> GetRequestContentTextAsync(HttpRequestMessage requestMessage) => requestMessage.Content != null ? await requestMessage.Content.ReadAsStringAsync().ConfigureAwait(false) : null;
49-
50-
protected virtual Task<string> GetResponseContentTextAsync(HttpResponseMessage responseMessage) => responseMessage.Content.ReadAsStringAsync();
101+
return await responseMessage.Content.ReadAsStringAsync().ConfigureAwait(false);
102+
}
51103
}
52104
}

src/Dibix.Http.Client/Client/IHttpClientBuilder.cs

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -5,13 +5,16 @@ namespace Dibix.Http.Client
55
{
66
public interface IHttpClientBuilder
77
{
8+
bool EnsureSuccessStatusCode { get; set; }
9+
bool FollowRedirectsGetRequests { get; set; }
10+
bool WrapTimeoutsInException { get; set; }
11+
bool TraceProxy { get; set; }
12+
HttpRequestTracer Tracer { get; set; }
13+
814
IHttpClientBuilder ConfigureClient(Action<HttpClient> configure);
9-
IHttpClientBuilder ConfigurePrimaryHttpMessageHandler<THandler>() where THandler : HttpMessageHandler, new();
10-
IHttpClientBuilder ConfigurePrimaryHttpMessageHandler<THandler>(THandler handler) where THandler : HttpMessageHandler;
1115
IHttpClientBuilder AddHttpMessageHandler<THandler>() where THandler : DelegatingHandler, new();
12-
IHttpClientBuilder AddHttpMessageHandler(DelegatingHandler handler);
13-
IHttpClientBuilder RemoveHttpMessageHandler<THandler>() where THandler : DelegatingHandler;
14-
IHttpClientBuilder RemoveHttpMessageHandler(DelegatingHandler handler);
15-
IHttpClientBuilder ClearHttpMessageHandlers();
16+
IHttpClientBuilder AddHttpMessageHandler(Func<DelegatingHandler> handlerFactory);
17+
IHttpClientBuilder ConfigurePrimaryHttpMessageHandler<THandler>() where THandler : HttpMessageHandler, new();
18+
IHttpClientBuilder ConfigurePrimaryHttpMessageHandler<THandler>(Func<THandler> handlerFactory) where THandler : HttpMessageHandler;
1619
}
1720
}

src/Dibix.Http.Client/Client/IHttpClientFactory.cs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@ namespace Dibix.Http.Client
66
public interface IHttpClientFactory
77
{
88
HttpClient CreateClient();
9+
HttpClient CreateClient(string name);
910
HttpClient CreateClient(Uri baseAddress);
11+
HttpClient CreateClient(string name, Uri baseAddress);
1012
}
1113
}

src/Dibix.Http.Client/Dibix.Http.Client.csproj

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
<ItemGroup>
88
<Compile Include="..\..\shared\Guard.cs" Link="Diagnostics\Guard.cs" />
99
<Compile Include="..\..\shared\KnownHeaders.cs" Link="Client\KnownHeaders.cs" />
10+
<Compile Include="..\..\shared\Extensions\CollectionExtensions.cs" Link="Extensions\CollectionExtensions.cs" />
1011
</ItemGroup>
1112

1213
<ItemGroup>
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
<wpf:ResourceDictionary xml:space="preserve" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:s="clr-namespace:System;assembly=mscorlib" xmlns:ss="urn:shemas-jetbrains-com:settings-storage-xaml" xmlns:wpf="http://schemas.microsoft.com/winfx/2006/xaml/presentation">
2+
<s:String x:Key="/Default/CodeInspection/CSharpLanguageProject/LanguageLevel/@EntryValue">CSharp80</s:String>
23
<s:Boolean x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=client/@EntryIndexedValue">True</s:Boolean>
34
<s:Boolean x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=client_005Chandlers/@EntryIndexedValue">True</s:Boolean>
45
<s:Boolean x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=description/@EntryIndexedValue">True</s:Boolean></wpf:ResourceDictionary>

0 commit comments

Comments
 (0)