Skip to content

Commit

Permalink
Merge pull request #28 from silkfire/httpclienthandlerex-concurrent-c…
Browse files Browse the repository at this point in the history
…ollection

Use concurrent dictionary to cache visited addresses in HttpClientHandlerEx message handler
  • Loading branch information
myarichuk committed Dec 15, 2020
2 parents 416191a + fa5d10e commit a2172ae
Show file tree
Hide file tree
Showing 2 changed files with 86 additions and 18 deletions.
78 changes: 73 additions & 5 deletions Simple.HttpClientFactory.Tests/BasicClientBuilderTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -18,18 +18,27 @@ namespace Simple.HttpClientFactory.Tests
public sealed class BasicClientBuilderTests : IDisposable
{
private const string _endpointUri = "/hello/world";
private const string _endpointUri2 = "/hello/world2";

private readonly WireMockServer _server;

public BasicClientBuilderTests()
{
_server = WireMockServer.Start();

_server.Given(Request.Create().WithPath(_endpointUri).UsingAnyMethod())
.RespondWith(
Response.Create()
.WithStatusCode(HttpStatusCode.OK)
.WithHeader("Content-Type", "text/plain")
.WithBody("Hello world!"));

_server.Given(Request.Create().WithPath(_endpointUri2).UsingAnyMethod())
.RespondWith(
Response.Create()
.WithStatusCode(HttpStatusCode.OK)
.WithHeader("Content-Type", "text/plain")
.WithBody("Hello world 2!"));
}


Expand Down Expand Up @@ -141,17 +150,76 @@ public async Task Will_send_default_headers()

#if NET472
[Fact]
public async Task HttpClient_will_cache_visited_urls()
public async Task HttpClientHandlerEx_should_cache_visited_url()
{
var baseUri = _server.Urls[0];

var clientHandler = new HttpClientHandlerEx();
var client = HttpClientFactory.Create().Build(clientHandler);

_ = await client.GetAsync($"{baseUri}{_endpointUri}");

var cachedUriKey = Assert.Single(clientHandler.AlreadySeenAddresses);
Assert.Equal(new HttpClientHandlerEx.UriCacheKey($"{baseUri}{_endpointUri}"), cachedUriKey);
}

[Fact]
public async Task UriCacheKey_equal_comparison()
{
var baseUri = _server.Urls[0];

var clientHandler = new HttpClientHandlerEx();
var client = HttpClientFactory.Create().Build(clientHandler);

_ = await client.GetAsync($"{baseUri}{_endpointUri}");

var cachedUriKey = Assert.Single(clientHandler.AlreadySeenAddresses);
Assert.True(new HttpClientHandlerEx.UriCacheKey($"{baseUri}{_endpointUri}") == cachedUriKey);
}

[Fact]
public async Task UriCacheKey_not_equal_comparison()
{
var baseUri = _server.Urls[0];

var clientHandler = new HttpClientHandlerEx();
var client = HttpClientFactory.Create(baseUri).Build(clientHandler);
var client = HttpClientFactory.Create().Build(clientHandler);

_ = await client.GetAsync(_endpointUri);
_ = await client.GetAsync($"{baseUri}{_endpointUri}");
_ = await client.GetAsync($"{baseUri}{_endpointUri2}");

Assert.Single(clientHandler.AlreadySeenAddresses);
Assert.True(clientHandler.AlreadySeenAddresses.First() == new HttpClientHandlerEx.UriCacheKey(new Uri($"{baseUri}{_endpointUri}")));
Assert.Equal(2, clientHandler.AlreadySeenAddresses.Count);

var cachedUriKey = Assert.Single(clientHandler.AlreadySeenAddresses, uck => uck != new HttpClientHandlerEx.UriCacheKey($"{baseUri}{_endpointUri}"));
Assert.Equal(new HttpClientHandlerEx.UriCacheKey($"{baseUri}{_endpointUri2}"), cachedUriKey);
}

[Fact]
public void UriCacheKey_equal_comparison_object()
{
object uriCacheKeyObject = new HttpClientHandlerEx.UriCacheKey($"{_server.Urls[0]}{_endpointUri}");

Assert.True(new HttpClientHandlerEx.UriCacheKey($"{_server.Urls[0]}{_endpointUri}").Equals(uriCacheKeyObject));
}

[Fact]
public void UriCacheKey_not_equal_comparison_object()
{
object notUriCacheKeyObject = 0;

Assert.False(new HttpClientHandlerEx.UriCacheKey($"{_server.Urls[0]}{_endpointUri2}").Equals(notUriCacheKeyObject));
}

[Fact]
public void UriCacheKey_ToString()
{
Assert.Equal($"{_server.Urls[0]}{_endpointUri}", new HttpClientHandlerEx.UriCacheKey($"{_server.Urls[0]}{_endpointUri}").ToString());
}

[Fact]
public void Two_UriCacheKeys_created_with_a_string_vs_a_uri_should_be_equal()
{
Assert.Equal(new HttpClientHandlerEx.UriCacheKey($"{_server.Urls[0]}{_endpointUri}"), new HttpClientHandlerEx.UriCacheKey(new Uri($"{_server.Urls[0]}{_endpointUri}")));
}
#endif

Expand Down
26 changes: 13 additions & 13 deletions Simple.HttpClientFactory/MessageHandlers/HttpClientHandlerEx.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
#if NETSTANDARD2_0
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Net.Http;
using System.Threading;
Expand All @@ -12,9 +14,9 @@ namespace Simple.HttpClientFactory.MessageHandlers
//note: Easy.Common is licensed with MIT License (https://github.com/NimaAra/Easy.Common/blob/master/LICENSE)
public class HttpClientHandlerEx : HttpClientHandler
{
private readonly HashSet<UriCacheKey> _alreadySeenAddresses = new HashSet<UriCacheKey>();
private readonly ConcurrentDictionary<UriCacheKey, UriCacheKey> _alreadySeenAddresses = new ConcurrentDictionary<UriCacheKey, UriCacheKey>();

public IReadOnlyCollection<UriCacheKey> AlreadySeenAddresses => _alreadySeenAddresses;
public IReadOnlyCollection<UriCacheKey> AlreadySeenAddresses => _alreadySeenAddresses.Values.ToList().AsReadOnly();

protected override Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
{
Expand All @@ -30,23 +32,19 @@ protected override Task<HttpResponseMessage> SendAsync(HttpRequestMessage reques

private void EnsureConnectionLeaseTimeout(Uri endpoint)
{
if (!endpoint.IsAbsoluteUri) { return; }

var key = new UriCacheKey(endpoint);
lock (_alreadySeenAddresses)
{
if (_alreadySeenAddresses.Contains(key)) { return; }

ServicePointManager.FindServicePoint(endpoint)
.ConnectionLeaseTimeout = (int)Constants.ConnectionLifeTime.TotalMilliseconds;
_alreadySeenAddresses.Add(key);
}
_alreadySeenAddresses.TryAdd(key, key);

ServicePointManager.FindServicePoint(endpoint).ConnectionLeaseTimeout = (int)Constants.ConnectionLifeTime.TotalMilliseconds;
}

public struct UriCacheKey : IEquatable<UriCacheKey>
public readonly struct UriCacheKey : IEquatable<UriCacheKey>
{
private readonly Uri _uri;

public UriCacheKey(string uri) => _uri = new Uri(uri);

public UriCacheKey(Uri uri) => _uri = uri;

public bool Equals(UriCacheKey other) => _uri == other._uri;
Expand All @@ -58,8 +56,10 @@ public struct UriCacheKey : IEquatable<UriCacheKey>
public static bool operator ==(UriCacheKey left, UriCacheKey right) => left.Equals(right);

public static bool operator !=(UriCacheKey left, UriCacheKey right) => !left.Equals(right);


public override string ToString() => _uri.ToString();
}
}

}
#endif

0 comments on commit a2172ae

Please sign in to comment.