diff --git a/CHANGELOG.md b/CHANGELOG.md
index a275e1dc..674c6f65 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,5 +1,8 @@
# Change Log
+## 2024-11-08 2.4.1
+- Improved how api requests are made (utilizing IHttpClientFactory)
+
## 2024-10-22 2.4.0
- CustomBet ICalculation and ICalculationFilter extended with new property Harmonization (extended with ICalculationV1 and ICalculationFilterV1)
- CustomBet ISelection extended with new property Odds (extended with ISelectionV1)
diff --git a/README.md b/README.md
index 2c1fe9b8..30dc8bd9 100644
--- a/README.md
+++ b/README.md
@@ -19,9 +19,8 @@ The SDK uses the following 3rd party libraries which must be added via the NuGet
- RabbitMQ.Client (6.5.0)
The package contains:
-- DemoProject: A Visual Studio 2022 solution containing a demo project showing the basic usage of the SDK
+- DemoProject: A solution containing a demo project showing the basic usage of the SDK
- libs: DLL file composing the Odds Feed SDK
-- Odds Feed SDK Documentation.chm: A documentation file describing exposed entities
- Resources containing the log4net configuration needed by the Odds Feed SDK
For more information please contact support@sportradar.com or visit https://iodocs.betradar.com/unifiedsdk/index.html
diff --git a/docs/docs/toc.pdf b/docs/docs/toc.pdf
index 45614412..52072bbf 100644
Binary files a/docs/docs/toc.pdf and b/docs/docs/toc.pdf differ
diff --git a/docs/sdkapi/toc.pdf b/docs/sdkapi/toc.pdf
index 706d4ea6..4182def3 100644
Binary files a/docs/sdkapi/toc.pdf and b/docs/sdkapi/toc.pdf differ
diff --git a/docs/toc.pdf b/docs/toc.pdf
index 8025de54..b3a4701a 100644
Binary files a/docs/toc.pdf and b/docs/toc.pdf differ
diff --git a/src/Sportradar.OddsFeed.SDK.DemoProject/Sportradar.OddsFeed.SDK.DemoProject.csproj b/src/Sportradar.OddsFeed.SDK.DemoProject/Sportradar.OddsFeed.SDK.DemoProject.csproj
index 0b65350c..2d1b435b 100644
--- a/src/Sportradar.OddsFeed.SDK.DemoProject/Sportradar.OddsFeed.SDK.DemoProject.csproj
+++ b/src/Sportradar.OddsFeed.SDK.DemoProject/Sportradar.OddsFeed.SDK.DemoProject.csproj
@@ -23,7 +23,7 @@
-
+
diff --git a/src/Sportradar.OddsFeed.SDK.Tests/API/Caching/VariantMarketDescriptionListCacheTests.cs b/src/Sportradar.OddsFeed.SDK.Tests/API/Caching/VariantMarketDescriptionListCacheTests.cs
index 3a2849b7..9ad7726d 100644
--- a/src/Sportradar.OddsFeed.SDK.Tests/API/Caching/VariantMarketDescriptionListCacheTests.cs
+++ b/src/Sportradar.OddsFeed.SDK.Tests/API/Caching/VariantMarketDescriptionListCacheTests.cs
@@ -575,7 +575,7 @@ public void OnTimerWhenDataRouterManagerDisposedThenHandleDisposedException()
_dataRouterManager.UriExceptions.Add(new Tuple("variant_market_descriptions.xml", new ObjectDisposedException("Drm disposed")));
_timer.FireOnce(TimeSpan.Zero);
- var success = TestExecutionHelper.WaitToComplete(() => _cultures.Count == _dataRouterManager.GetCallCount(TestDataRouterManager.EndpointVariantDescriptions));
+ var success = TestExecutionHelper.WaitToComplete(() => _cultures.Count == _dataRouterManager.GetCallCount(TestDataRouterManager.EndpointVariantDescriptions), timeoutMs: 20000);
Assert.True(success);
}
@@ -845,9 +845,9 @@ public async Task LoadMarketDescriptionsWhenReloadingThenNoThrottlingHappens()
// public async Task GetAllWhenNoDataPresentThenReturnEmptyCollection()
// {
// Assert.Equal(0, _variantsListMemoryCache.Count());
- //
+ //
// var result = await _variantsListCache.GetAllInvariantMarketDescriptionsAsync(_cultures);
- //
+ //
// Assert.Empty(result);
// }
//
@@ -855,9 +855,9 @@ public async Task LoadMarketDescriptionsWhenReloadingThenNoThrottlingHappens()
// public async Task GetAllWhenDataPresentThenReturnAllCollection()
// {
// _ = await _variantsListCache.LoadMarketDescriptionsAsync();
- //
+ //
// var result = await _variantsListCache.GetAllInvariantMarketDescriptionsAsync(_cultures);
- //
+ //
// Assert.Equal(ScheduleData.VariantListCacheCount, result.Count());
// }
//
diff --git a/src/Sportradar.OddsFeed.SDK.Tests/API/Config/UofSdkBootstrapApiTests.cs b/src/Sportradar.OddsFeed.SDK.Tests/API/Config/UofSdkBootstrapApiTests.cs
index 02c84e93..7342f97f 100644
--- a/src/Sportradar.OddsFeed.SDK.Tests/API/Config/UofSdkBootstrapApiTests.cs
+++ b/src/Sportradar.OddsFeed.SDK.Tests/API/Config/UofSdkBootstrapApiTests.cs
@@ -38,9 +38,9 @@ public void HttpClientFactoryIsSingleton()
[Fact]
public void HttpClientIsRegistered()
{
- var service1 = ServiceScope1.ServiceProvider.GetRequiredService().CreateClient("HttpClient");
- var service1A = ServiceScope1.ServiceProvider.GetRequiredService().CreateClient("HttpClient");
- var service2 = ServiceScope2.ServiceProvider.GetRequiredService().CreateClient("HttpClient");
+ var service1 = ServiceScope1.ServiceProvider.GetRequiredService().CreateClient(UofSdkBootstrap.HttpClientNameForNormal);
+ var service1A = ServiceScope1.ServiceProvider.GetRequiredService().CreateClient(UofSdkBootstrap.HttpClientNameForNormal);
+ var service2 = ServiceScope2.ServiceProvider.GetRequiredService().CreateClient(UofSdkBootstrap.HttpClientNameForNormal);
Assert.NotNull(service1);
Assert.NotNull(service1A);
Assert.NotNull(service2);
@@ -51,9 +51,9 @@ public void HttpClientIsRegistered()
[Fact]
public void HttpClientRecoveryIsRegistered()
{
- var service1 = ServiceScope1.ServiceProvider.GetRequiredService().CreateClient("HttpClientRecovery");
- var service1A = ServiceScope1.ServiceProvider.GetRequiredService().CreateClient("HttpClientRecovery");
- var service2 = ServiceScope2.ServiceProvider.GetRequiredService().CreateClient("HttpClientRecovery");
+ var service1 = ServiceScope1.ServiceProvider.GetRequiredService().CreateClient(UofSdkBootstrap.HttpClientNameForRecovery);
+ var service1A = ServiceScope1.ServiceProvider.GetRequiredService().CreateClient(UofSdkBootstrap.HttpClientNameForRecovery);
+ var service2 = ServiceScope2.ServiceProvider.GetRequiredService().CreateClient(UofSdkBootstrap.HttpClientNameForRecovery);
Assert.NotNull(service1);
Assert.NotNull(service1A);
Assert.NotNull(service2);
@@ -64,9 +64,9 @@ public void HttpClientRecoveryIsRegistered()
[Fact]
public void HttpClientFastFailingIsRegistered()
{
- var service1 = ServiceScope1.ServiceProvider.GetRequiredService().CreateClient("HttpClientFastFailing");
- var service1A = ServiceScope1.ServiceProvider.GetRequiredService().CreateClient("HttpClientFastFailing");
- var service2 = ServiceScope2.ServiceProvider.GetRequiredService().CreateClient("HttpClientFastFailing");
+ var service1 = ServiceScope1.ServiceProvider.GetRequiredService().CreateClient(UofSdkBootstrap.HttpClientNameForFastFailing);
+ var service1A = ServiceScope1.ServiceProvider.GetRequiredService().CreateClient(UofSdkBootstrap.HttpClientNameForFastFailing);
+ var service2 = ServiceScope2.ServiceProvider.GetRequiredService().CreateClient(UofSdkBootstrap.HttpClientNameForFastFailing);
Assert.NotNull(service1);
Assert.NotNull(service1A);
Assert.NotNull(service2);
@@ -77,9 +77,9 @@ public void HttpClientFastFailingIsRegistered()
[Fact]
public void HttpClientsHaveDifferentTimeouts()
{
- var httpClientNormal = ServiceScope1.ServiceProvider.GetRequiredService().CreateClient("HttpClient");
- var httpClientRecovery = ServiceScope1.ServiceProvider.GetRequiredService().CreateClient("HttpClientRecovery");
- var httpClientFastFailing = ServiceScope2.ServiceProvider.GetRequiredService().CreateClient("HttpClientFastFailing");
+ var httpClientNormal = ServiceScope1.ServiceProvider.GetRequiredService().CreateClient(UofSdkBootstrap.HttpClientNameForNormal);
+ var httpClientRecovery = ServiceScope1.ServiceProvider.GetRequiredService().CreateClient(UofSdkBootstrap.HttpClientNameForRecovery);
+ var httpClientFastFailing = ServiceScope2.ServiceProvider.GetRequiredService().CreateClient(UofSdkBootstrap.HttpClientNameForFastFailing);
Assert.NotNull(httpClientNormal);
Assert.NotNull(httpClientRecovery);
Assert.NotNull(httpClientFastFailing);
@@ -93,9 +93,9 @@ public void HttpClientsHaveDifferentTimeouts()
}
[Fact]
- public void SdkHttpClientInterfaceIsSingleton()
+ public void SdkHttpClientInterfaceIsTransient()
{
- CheckSingletonType();
+ CheckTransientType();
}
[Fact]
@@ -168,12 +168,6 @@ public void SdkHttpClientFastFailingHasTimeoutConfigured()
Assert.Equal(UofConfig.Api.HttpClientFastFailingTimeout, sdkHttpClient.Timeout);
}
- [Fact]
- public void HttpDataFetcherIsSingleton()
- {
- CheckSingletonType();
- }
-
[Fact]
public void DataFetcherIsSingleton()
{
diff --git a/src/Sportradar.OddsFeed.SDK.Tests/Entities/REST/HttpDataFetcherTests.cs b/src/Sportradar.OddsFeed.SDK.Tests/Entities/REST/HttpDataFetcherTests.cs
index 69444df2..7eea6ea2 100644
--- a/src/Sportradar.OddsFeed.SDK.Tests/Entities/REST/HttpDataFetcherTests.cs
+++ b/src/Sportradar.OddsFeed.SDK.Tests/Entities/REST/HttpDataFetcherTests.cs
@@ -9,6 +9,7 @@
using System.Text;
using System.Threading.Tasks;
using System.Xml.Serialization;
+using Microsoft.Extensions.DependencyInjection;
using Sportradar.OddsFeed.SDK.Api.Internal.ApiAccess;
using Sportradar.OddsFeed.SDK.Api.Internal.Config;
using Sportradar.OddsFeed.SDK.Common.Exceptions;
@@ -22,6 +23,7 @@ namespace Sportradar.OddsFeed.SDK.Tests.Entities.Rest;
public class HttpDataFetcherTests
{
+ private readonly ITestOutputHelper _outputHelper;
private readonly SdkHttpClient _sdkHttpClient;
private readonly HttpDataFetcher _httpDataFetcher;
private readonly Uri _badUri = new Uri("http://www.unexisting-url.com");
@@ -31,11 +33,20 @@ public class HttpDataFetcherTests
public HttpDataFetcherTests(ITestOutputHelper outputHelper)
{
- _stubMessageHandler = new StubMessageHandler(outputHelper, 100, 50);
- var httpClient = new HttpClient(_stubMessageHandler);
- httpClient.DefaultRequestHeaders.Add(UofSdkBootstrap.HttpClientDefaultRequestHeaderForAccessToken, "aaa");
- httpClient.DefaultRequestHeaders.Add(UofSdkBootstrap.HttpClientDefaultRequestHeaderForUserAgent, "UofSdk-Net");
- _sdkHttpClient = new SdkHttpClient(httpClient);
+ _outputHelper = outputHelper;
+ const string httpClientName = "test";
+ _stubMessageHandler = new StubMessageHandler(_outputHelper, 100, 50);
+ var services = new ServiceCollection();
+ services.AddHttpClient(httpClientName)
+ .ConfigureHttpClient(configureClient =>
+ {
+ configureClient.Timeout = TimeSpan.FromSeconds(5);
+ configureClient.DefaultRequestHeaders.Add(UofSdkBootstrap.HttpClientDefaultRequestHeaderForAccessToken, "aaa");
+ configureClient.DefaultRequestHeaders.Add(UofSdkBootstrap.HttpClientDefaultRequestHeaderForUserAgent, "UofSdk-Net");
+ })
+ .ConfigurePrimaryHttpMessageHandler(() => _stubMessageHandler);
+ var serviceProvider = services.BuildServiceProvider();
+ _sdkHttpClient = new SdkHttpClient(serviceProvider.GetRequiredService(), httpClientName);
_httpDataFetcher = new HttpDataFetcher(_sdkHttpClient, new Deserializer());
}
@@ -90,7 +101,7 @@ public async Task ValidateConnectionWhenConnectionFailureTime()
Assert.Null(result);
Assert.IsType(e);
Assert.Null(e.InnerException);
- Assert.Equal("Failed to execute request due to previous failures.", e.Message);
+ Assert.Equal("Failed to execute request due to previous failures", e.Message);
}
[Fact]
@@ -142,6 +153,23 @@ public async Task GetDataAsyncWhenWrongUrlThenThrowsHttpRequestException()
}
}
+ [Fact]
+ public async Task GetDataAsyncWhenTaskIsCanceled()
+ {
+ var httpDataFetcher = SetupHttpDataFetcherForTaskTimeout();
+
+ Stream result = null;
+ var ex = await Assert.ThrowsAsync(async () => result = await httpDataFetcher.GetDataAsync(_getUri));
+
+ Assert.Null(result);
+ Assert.IsType(ex);
+ Assert.Equal(HttpStatusCode.RequestTimeout, ex.ResponseCode);
+ if (ex.InnerException != null)
+ {
+ Assert.IsType(ex.InnerException);
+ }
+ }
+
[Fact]
public void GetDataWhenBadGatewayWithValidContent()
{
@@ -279,6 +307,23 @@ public async Task PostDataAsyncWhenWrongUrlThenThrowCommunicationException()
Assert.Null(e.InnerException);
}
+ [Fact]
+ public async Task PostDataAsyncWhenTaskIsCanceled()
+ {
+ var httpDataFetcher = SetupHttpDataFetcherForTaskTimeout();
+
+ HttpResponseMessage result = null;
+ var ex = await Assert.ThrowsAsync(async () => result = await httpDataFetcher.PostDataAsync(_getUri, new StringContent("test string")));
+
+ Assert.Null(result);
+ Assert.IsType(ex);
+ Assert.Equal(HttpStatusCode.RequestTimeout, ex.ResponseCode);
+ if (ex.InnerException != null)
+ {
+ Assert.IsType(ex.InnerException);
+ }
+ }
+
[Fact]
public async Task PostDataAsyncWhenValidContentWithResponseHeaders()
{
@@ -311,6 +356,25 @@ public async Task PostDataAsyncWhenValidContentWithMultipleSameResponseHeaders()
Assert.Equal("any-key", _httpDataFetcher.ResponseHeaders.First().Key);
}
+ private HttpDataFetcher SetupHttpDataFetcherForTaskTimeout()
+ {
+ const string httpClientName = "test";
+ var stubMessageHandler = new StubMessageHandler(_outputHelper, 1000);
+ var services = new ServiceCollection();
+ services.AddHttpClient(httpClientName)
+ .ConfigureHttpClient(configureClient =>
+ {
+ configureClient.Timeout = TimeSpan.FromMilliseconds(200);
+ configureClient.DefaultRequestHeaders.Add(UofSdkBootstrap.HttpClientDefaultRequestHeaderForAccessToken, "aaa");
+ configureClient.DefaultRequestHeaders.Add(UofSdkBootstrap.HttpClientDefaultRequestHeaderForUserAgent, "UofSdk-Net");
+ })
+ .ConfigurePrimaryHttpMessageHandler(() => stubMessageHandler);
+ var serviceProvider = services.BuildServiceProvider();
+ var sdkHttpClient = new SdkHttpClient(serviceProvider.GetRequiredService(), httpClientName);
+ var httpDataFetcher = new HttpDataFetcher(sdkHttpClient, new Deserializer());
+ return httpDataFetcher;
+ }
+
private HttpResponseMessage GetSuccessResponseMessage(HttpStatusCode httpStatusCode = HttpStatusCode.Accepted)
{
var response = GetResponse(httpStatusCode);
@@ -324,7 +388,7 @@ private response GetResponse(HttpStatusCode httpStatusCode = HttpStatusCode.Acce
return new response { action = "some-response-action", message = $"some-response-message {httpStatusCode}", response_code = response_code.ACCEPTED, response_codeSpecified = true };
}
- public static string ObjectToXmlString(T objectToSerialize)
+ private static string ObjectToXmlString(T objectToSerialize)
{
var xmlSerializer = new XmlSerializer(typeof(T));
diff --git a/src/Sportradar.OddsFeed.SDK.Tests/Entities/REST/LogHttpDataFetcherTests.cs b/src/Sportradar.OddsFeed.SDK.Tests/Entities/REST/LogHttpDataFetcherTests.cs
index 82304a12..4d3ec38f 100644
--- a/src/Sportradar.OddsFeed.SDK.Tests/Entities/REST/LogHttpDataFetcherTests.cs
+++ b/src/Sportradar.OddsFeed.SDK.Tests/Entities/REST/LogHttpDataFetcherTests.cs
@@ -9,6 +9,8 @@
using System.Net.Http;
using System.Net.Sockets;
using System.Threading.Tasks;
+using FluentAssertions;
+using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Logging.Abstractions;
using Moq;
@@ -28,10 +30,10 @@ namespace Sportradar.OddsFeed.SDK.Tests.Entities.Rest;
public class LogHttpDataFetcherTests
{
private readonly ITestOutputHelper _outputHelper;
+ private const string HttpClientDefaultName = "test";
private readonly IncrementalSequenceGenerator _incrementalSequenceGenerator;
private readonly SdkHttpClient _sdkHttpClient;
private LogHttpDataFetcher _logHttpDataFetcher;
- private readonly LogHttpDataFetcher _logHttpDataFetcherPool;
private readonly Uri _badUri = new Uri("http://www.unexisting-url.com");
private readonly Uri _getUri = new Uri("http://test.domain.com/get");
private readonly Uri _postUri = new Uri("http://test.domain.com/post");
@@ -41,104 +43,117 @@ public LogHttpDataFetcherTests(ITestOutputHelper outputHelper)
{
_outputHelper = outputHelper;
_stubMessageHandler = new StubMessageHandler(_outputHelper, 100, 50);
- var httpClient = new HttpClient(_stubMessageHandler);
- httpClient.DefaultRequestHeaders.Add(UofSdkBootstrap.HttpClientDefaultRequestHeaderForAccessToken, "aaa");
- httpClient.DefaultRequestHeaders.Add(UofSdkBootstrap.HttpClientDefaultRequestHeaderForUserAgent, "UofSdk-Net");
- _sdkHttpClient = new SdkHttpClient(httpClient);
- var sdkHttpClientPool = new SdkHttpClientPool("aaa", 20, TimeSpan.FromSeconds(5), _stubMessageHandler);
+ var services = new ServiceCollection();
+ services.AddHttpClient(HttpClientDefaultName)
+ .ConfigureHttpClient(configureClient =>
+ {
+ configureClient.Timeout = TimeSpan.FromSeconds(5);
+ configureClient.DefaultRequestHeaders.Add(UofSdkBootstrap.HttpClientDefaultRequestHeaderForAccessToken, "aaa");
+ configureClient.DefaultRequestHeaders.Add(UofSdkBootstrap.HttpClientDefaultRequestHeaderForUserAgent, "UofSdk-Net");
+ })
+ .ConfigurePrimaryHttpMessageHandler(() => _stubMessageHandler);
+ var serviceProvider = services.BuildServiceProvider();
+ _sdkHttpClient = new SdkHttpClient(serviceProvider.GetRequiredService(), HttpClientDefaultName);
_incrementalSequenceGenerator = new IncrementalSequenceGenerator(new NullLogger());
_logHttpDataFetcher = new LogHttpDataFetcher(_sdkHttpClient, _incrementalSequenceGenerator, new Deserializer(), new NullLogger());
- _logHttpDataFetcherPool = new LogHttpDataFetcher(sdkHttpClientPool, _incrementalSequenceGenerator, new Deserializer(), new NullLogger());
}
[Fact]
- public void SdkHttpClientWithoutAccessTokenFails()
+ public void SdkHttpClientWhenConstructedThenSetTimeout()
{
- var httpClient = new HttpClient();
- httpClient.DefaultRequestHeaders.Add(UofSdkBootstrap.HttpClientDefaultRequestHeaderForUserAgent, "UofSdk-Net");
-
- Assert.Throws(() => new SdkHttpClient(httpClient));
+ _sdkHttpClient.Timeout.TotalSeconds.Should().Be(5);
}
[Fact]
- public void SdkHttpClientWithoutUserAgentFails()
+ public void SdkHttpClientWhenWithoutHeadersThenThrow()
{
- var httpClient = new HttpClient();
- httpClient.DefaultRequestHeaders.Add(UofSdkBootstrap.HttpClientDefaultRequestHeaderForAccessToken, "aaa");
+ var services = new ServiceCollection();
+ services.AddHttpClient(HttpClientDefaultName)
+ .ConfigureHttpClient(configureClient =>
+ {
+ configureClient.Timeout = TimeSpan.FromSeconds(5);
+ configureClient.DefaultRequestHeaders.Clear();
+ })
+ .ConfigurePrimaryHttpMessageHandler(() => _stubMessageHandler);
+ var serviceProvider = services.BuildServiceProvider();
- Assert.Throws(() => new SdkHttpClient(httpClient));
+ Assert.Throws(() => new SdkHttpClient(serviceProvider.GetRequiredService(), HttpClientDefaultName));
}
[Fact]
- public async Task PerformanceOf100SequentialRequests()
+ public void SdkHttpClientWhenWithoutAccessTokenThenThrow()
{
- var stopwatch = Stopwatch.StartNew();
- for (var i = 0; i < 100; i++)
- {
- var result = await _logHttpDataFetcher.GetDataAsync(_getUri);
- Assert.NotNull(result);
- Assert.True(result.CanRead);
- }
- _outputHelper.WriteLine($"Elapsed {stopwatch.ElapsedMilliseconds} ms");
+ var services = new ServiceCollection();
+ services.AddHttpClient(HttpClientDefaultName)
+ .ConfigureHttpClient(configureClient =>
+ {
+ configureClient.Timeout = TimeSpan.FromSeconds(5);
+ configureClient.DefaultRequestHeaders.Add(UofSdkBootstrap.HttpClientDefaultRequestHeaderForUserAgent, "UofSdk-Net");
+ })
+ .ConfigurePrimaryHttpMessageHandler(() => _stubMessageHandler);
+ var serviceProvider = services.BuildServiceProvider();
+
+ Assert.Throws(() => new SdkHttpClient(serviceProvider.GetRequiredService(), HttpClientDefaultName));
}
[Fact]
- public async Task PerformanceOfManyParallelRequests()
+ public void SdkHttpClientWhenWithoutUserAgentThenThrow()
{
- var stopwatch = Stopwatch.StartNew();
- var tasks = new List();
- for (var i = 0; i < 1000; i++)
- {
- var task = _logHttpDataFetcher.GetDataAsync(GetRequestUri(false));
- tasks.Add(task);
- }
- await Task.WhenAll(tasks);
-
- Assert.True(tasks.TrueForAll(a => a.IsCompletedSuccessfully));
- _outputHelper.WriteLine($"Elapsed {stopwatch.ElapsedMilliseconds} ms");
+ var services = new ServiceCollection();
+ services.AddHttpClient(HttpClientDefaultName)
+ .ConfigureHttpClient(configureClient =>
+ {
+ configureClient.Timeout = TimeSpan.FromSeconds(5);
+ configureClient.DefaultRequestHeaders.Add(UofSdkBootstrap.HttpClientDefaultRequestHeaderForAccessToken, "aaa");
+ })
+ .ConfigurePrimaryHttpMessageHandler(() => _stubMessageHandler);
+ var serviceProvider = services.BuildServiceProvider();
+
+ Assert.Throws(() => new SdkHttpClient(serviceProvider.GetRequiredService(), HttpClientDefaultName));
}
[Fact]
- public async Task PerformancePoolOfManyParallelRequests()
+ public void SdkHttpClientWhenWithLowCaseUserAgentThenDoNotThrow()
{
- var stopwatch = Stopwatch.StartNew();
- var tasks = new List();
- for (var i = 0; i < 1000; i++)
- {
- var task = _logHttpDataFetcherPool.GetDataAsync(GetRequestUri(false));
- tasks.Add(task);
- }
- await Task.WhenAll(tasks);
+ var services = new ServiceCollection();
+ services.AddHttpClient(HttpClientDefaultName)
+ .ConfigureHttpClient(configureClient =>
+ {
+ configureClient.Timeout = TimeSpan.FromSeconds(5);
+ configureClient.DefaultRequestHeaders.Clear();
+ configureClient.DefaultRequestHeaders.Add(UofSdkBootstrap.HttpClientDefaultRequestHeaderForAccessToken, "aaa");
+ configureClient.DefaultRequestHeaders.Add("user-agent", "UofSdk-Net");
+ })
+ .ConfigurePrimaryHttpMessageHandler(() => _stubMessageHandler);
+ var serviceProvider = services.BuildServiceProvider();
- Assert.True(tasks.TrueForAll(a => a.IsCompletedSuccessfully));
- _outputHelper.WriteLine($"Elapsed {stopwatch.ElapsedMilliseconds} ms");
+ var sdkHttpClient = new SdkHttpClient(serviceProvider.GetRequiredService(), HttpClientDefaultName);
+
+ sdkHttpClient.Should().NotBeNull();
}
[Fact]
- public async Task PerformanceOfManyUniqueParallelRequests()
+ public async Task PerformanceOf100SequentialRequests()
{
var stopwatch = Stopwatch.StartNew();
- var tasks = new List();
- for (var i = 0; i < 1000; i++)
+ for (var i = 0; i < 100; i++)
{
- var task = _logHttpDataFetcher.GetDataAsync(GetRequestUri(true));
- tasks.Add(task);
+ var result = await _logHttpDataFetcher.GetDataAsync(_getUri);
+ Assert.NotNull(result);
+ Assert.True(result.CanRead);
}
- await Task.WhenAll(tasks);
-
- Assert.True(tasks.TrueForAll(a => a.IsCompletedSuccessfully));
_outputHelper.WriteLine($"Elapsed {stopwatch.ElapsedMilliseconds} ms");
}
[Fact]
- public async Task PerformancePoolOfManyUniqueParallelRequests()
+ public async Task PerformanceOfManyParallelRequests()
{
var stopwatch = Stopwatch.StartNew();
var tasks = new List();
for (var i = 0; i < 1000; i++)
{
- var task = _logHttpDataFetcherPool.GetDataAsync(GetRequestUri(true));
+ var task = _logHttpDataFetcher.GetDataAsync(GetRequestUri(false));
tasks.Add(task);
}
await Task.WhenAll(tasks);
@@ -148,13 +163,13 @@ public async Task PerformancePoolOfManyUniqueParallelRequests()
}
[Fact]
- public async Task PerformanceOfManyUniqueUriParallelRequests()
+ public async Task PerformanceOfManyUniqueParallelRequests()
{
var stopwatch = Stopwatch.StartNew();
var tasks = new List();
for (var i = 0; i < 1000; i++)
{
- var task = _logHttpDataFetcher.GetDataAsync(GetRandomUri(true));
+ var task = _logHttpDataFetcher.GetDataAsync(GetRequestUri(true));
tasks.Add(task);
}
await Task.WhenAll(tasks);
@@ -164,13 +179,13 @@ public async Task PerformanceOfManyUniqueUriParallelRequests()
}
[Fact]
- public async Task PerformancePoolOfManyUniqueUriParallelRequests()
+ public async Task PerformanceOfManyUniqueUriParallelRequests()
{
var stopwatch = Stopwatch.StartNew();
var tasks = new List();
for (var i = 0; i < 1000; i++)
{
- var task = _logHttpDataFetcherPool.GetDataAsync(GetRandomUri(true));
+ var task = _logHttpDataFetcher.GetDataAsync(GetRandomUri(true));
tasks.Add(task);
}
await Task.WhenAll(tasks);
@@ -180,7 +195,7 @@ public async Task PerformancePoolOfManyUniqueUriParallelRequests()
}
[Fact]
- public async Task GetDataAsyncWhneNormalUri()
+ public async Task GetDataAsyncWhenNormalUri()
{
// in logRest file there should be result for this call
var result = await _logHttpDataFetcher.GetDataAsync(_getUri);
@@ -240,6 +255,22 @@ public async Task GetDataAsyncWhenWrongUrlThenThrowsHttpRequestException()
}
}
+ [Fact]
+ public async Task GetDataAsyncWhenWrongUrlThenThrowsHttpRequestExceptionWithNotFound()
+ {
+ _stubMessageHandler.SetWantedResponse(new HttpRequestException("NotFound any-message", new SocketException(), HttpStatusCode.NotFound));
+ Stream result = null;
+
+ var e = await Assert.ThrowsAsync(async () => result = await _logHttpDataFetcher.GetDataAsync(_badUri));
+
+ Assert.Null(result);
+ Assert.IsType(e);
+ if (e.InnerException != null)
+ {
+ Assert.IsType(e.InnerException);
+ }
+ }
+
[Fact]
public async Task GetDataAsyncWhenWrongUrlThenThrowsSocketException()
{
@@ -268,13 +299,9 @@ public async Task PostDataWhenNormalUri()
[Fact]
public async Task PostData_DebugLog()
{
- var httpClient = new HttpClient(_stubMessageHandler);
- httpClient.DefaultRequestHeaders.Add(UofSdkBootstrap.HttpClientDefaultRequestHeaderForAccessToken, "aaa");
- httpClient.DefaultRequestHeaders.Add(UofSdkBootstrap.HttpClientDefaultRequestHeaderForUserAgent, "UofSdk-Net");
- var sdkHttpClient = new SdkHttpClient(httpClient);
var mockLocker = new Mock();
mockLocker.Setup(l => l.IsEnabled(LogLevel.Debug)).Returns(true);
- var logHttpDataFetcher = new LogHttpDataFetcher(sdkHttpClient, _incrementalSequenceGenerator, new Deserializer(), mockLocker.Object);
+ var logHttpDataFetcher = new LogHttpDataFetcher(_sdkHttpClient, _incrementalSequenceGenerator, new Deserializer(), mockLocker.Object);
var result = await logHttpDataFetcher.PostDataAsync(_getUri);
diff --git a/src/Sportradar.OddsFeed.SDK/API/Internal/ApiAccess/HttpDataFetcher.cs b/src/Sportradar.OddsFeed.SDK/API/Internal/ApiAccess/HttpDataFetcher.cs
index c2e1689b..c0c127a3 100644
--- a/src/Sportradar.OddsFeed.SDK/API/Internal/ApiAccess/HttpDataFetcher.cs
+++ b/src/Sportradar.OddsFeed.SDK/API/Internal/ApiAccess/HttpDataFetcher.cs
@@ -4,6 +4,7 @@
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.IO;
+using System.Net;
using System.Net.Http;
using System.Threading;
using System.Threading.Tasks;
@@ -85,20 +86,26 @@ public HttpDataFetcher(ISdkHttpClient sdkHttpClient,
public virtual async Task GetDataAsync(Uri uri)
{
ValidateConnection(uri);
- var responseMessage = new HttpResponseMessage();
+ HttpResponseMessage responseMessage = null;
try
{
responseMessage = await SdkHttpClient.GetAsync(uri).ConfigureAwait(false);
return await ProcessGetDataAsync(responseMessage, uri).ConfigureAwait(false);
}
+ catch (CommunicationException)
+ {
+ RecordFailure();
+ throw;
+ }
+ catch (TaskCanceledException ex)
+ {
+ RecordFailure();
+ throw new CommunicationException("Failed to execute http get", uri.ToString(), responseMessage?.StatusCode ?? HttpStatusCode.RequestTimeout, ex);
+ }
catch (Exception ex)
{
RecordFailure();
- if (ex is CommunicationException)
- {
- throw;
- }
- throw new CommunicationException("Failed to execute http get", uri.ToString(), responseMessage.StatusCode, ex);
+ throw new CommunicationException("Failed to execute http get", uri.ToString(), responseMessage?.StatusCode ?? HttpStatusCode.OK, ex);
}
}
@@ -173,7 +180,7 @@ private async Task DeserializeResponseContent(string responseContent)
public virtual async Task PostDataAsync(Uri uri, HttpContent content = null)
{
ValidateConnection(uri);
- var responseMessage = new HttpResponseMessage();
+ HttpResponseMessage responseMessage = null;
try
{
responseMessage = await SdkHttpClient.PostAsync(uri, content ?? new StringContent(string.Empty)).ConfigureAwait(false);
@@ -190,14 +197,20 @@ public virtual async Task PostDataAsync(Uri uri, HttpConten
}
return responseMessage;
}
+ catch (CommunicationException)
+ {
+ RecordFailure();
+ throw;
+ }
+ catch (TaskCanceledException ex)
+ {
+ RecordFailure();
+ throw new CommunicationException("Failed to execute http post", uri.ToString(), responseMessage?.StatusCode ?? HttpStatusCode.RequestTimeout, ex);
+ }
catch (Exception ex)
{
RecordFailure();
- if (ex is CommunicationException)
- {
- throw;
- }
- throw new CommunicationException("Failed to execute http post", uri.ToString(), responseMessage.StatusCode, ex);
+ throw new CommunicationException("Failed to execute http post", uri.ToString(), responseMessage?.StatusCode ?? HttpStatusCode.OK, ex);
}
}
@@ -242,7 +255,7 @@ protected void ValidateConnection(Uri uri)
if (_blockingModeActive)
{
- throw new CommunicationException("Failed to execute request due to previous failures.", uri.ToString(), null);
+ throw new CommunicationException("Failed to execute request due to previous failures", uri.ToString(), null);
}
}
}
diff --git a/src/Sportradar.OddsFeed.SDK/API/Internal/ApiAccess/LogHttpDataFetcher.cs b/src/Sportradar.OddsFeed.SDK/API/Internal/ApiAccess/LogHttpDataFetcher.cs
index a87c8df8..5cd30164 100644
--- a/src/Sportradar.OddsFeed.SDK/API/Internal/ApiAccess/LogHttpDataFetcher.cs
+++ b/src/Sportradar.OddsFeed.SDK/API/Internal/ApiAccess/LogHttpDataFetcher.cs
@@ -5,7 +5,6 @@
using System.Globalization;
using System.IO;
using System.Net.Http;
-using System.Text;
using System.Threading.Tasks;
using Dawn;
using Microsoft.Extensions.Logging;
@@ -17,7 +16,7 @@
namespace Sportradar.OddsFeed.SDK.Api.Internal.ApiAccess
{
///
- /// A implementation of and which uses the HTTP requests to fetch or post the requested data. All request are logged.
+ /// An implementation of and which uses the HTTP requests to fetch or post the requested data. All request are logged.
///
///
/// ALL, DEBUG, INFO, WARN, ERROR, FATAL, OFF - the levels are defined in order of increasing priority
@@ -36,7 +35,7 @@ internal class LogHttpDataFetcher : HttpDataFetcher, ILogHttpDataFetcher
/// Logger to log rest requests
/// Indicates the limit of consecutive request failures, after which it goes in "blocking mode"
/// indicates the timeout after which comes out of "blocking mode" (in seconds)
- public LogHttpDataFetcher(ISdkHttpClient sdkHttpClient, ISequenceGenerator sequenceGenerator, IDeserializer responseDeserializer, ILogger logger, int connectionFailureLimit = 5, int connectionFailureTimeout = 15)
+ public LogHttpDataFetcher(ISdkHttpClient sdkHttpClient, ISequenceGenerator sequenceGenerator, IDeserializer responseDeserializer, ILogger logger, int connectionFailureLimit = 50, int connectionFailureTimeout = 15)
: base(sdkHttpClient, responseDeserializer, connectionFailureLimit, connectionFailureTimeout)
{
Guard.Argument(sequenceGenerator, nameof(sequenceGenerator)).NotNull();
@@ -57,9 +56,6 @@ public override async Task GetDataAsync(Uri uri)
{
var dataId = _sequenceGenerator.GetNext().ToString("D7", CultureInfo.InvariantCulture); // because request can take long time, there may be several request at the same time; Id to know what belongs together.
- var logBuilder = new StringBuilder();
- logBuilder.Append("Id:").Append(dataId).Append(" Fetching url: ").Append(uri.AbsoluteUri);
-
var watch = Stopwatch.StartNew();
Stream responseStream;
try
@@ -70,29 +66,27 @@ public override async Task GetDataAsync(Uri uri)
catch (Exception ex)
{
watch.Stop();
- if (ex.GetType() == typeof(CommunicationException))
+ if (!(ex is CommunicationException commException))
{
- var commException = (CommunicationException)ex;
- logBuilder.Append(" ResponseCode:").Append(commException.ResponseCode);
- logBuilder.Append(" Duration: ").Append(watch.ElapsedMilliseconds);
- logBuilder.Append(" ms Response:").Append(commException.Response?.Replace("\n", string.Empty));
- _restLog.LogError("{LogMsg}", logBuilder.ToString());
- throw;
+ throw new CommunicationException("Failed to execute http get", uri.ToString(), ex);
}
- throw new CommunicationException("Failed to execute http get", uri.ToString(), ex);
+ const string msgErrorTemplate = "Id:{DataId} GET: {GetUri} Response: {ResponseStatusCode} took {Elapsed} ms Response: {ResponseContent}";
+ _restLog.LogError(msgErrorTemplate, dataId, uri.AbsoluteUri, commException.ResponseCode, watch.ElapsedMilliseconds.ToString(CultureInfo.InvariantCulture), GetCommunicationExceptionContent(commException));
+ throw;
}
- logBuilder.Append(" Duration: ").Append(watch.ElapsedMilliseconds).Append(" ms");
if (!_restLog.IsEnabled(LogLevel.Debug))
{
- _restLog.LogInformation("{LogMsg}", logBuilder.ToString());
+ const string msgSuccessTemplate = "Id:{DataId} GET: {GetUri} took {Elapsed} ms";
+ _restLog.LogInformation(msgSuccessTemplate, dataId, uri.AbsoluteUri, watch.ElapsedMilliseconds.ToString(CultureInfo.InvariantCulture));
return responseStream;
}
- var responseContent = new StreamReader(responseStream).ReadToEndAsync().GetAwaiter().GetResult();
+ const string msgSuccessWithContentTemplate = "Id:{DataId} GET: {GetUri} took {Elapsed} ms Response: {ResponseContent}";
+ var responseContent = await new StreamReader(responseStream).ReadToEndAsync();
responseContent = responseContent.Replace("\n", string.Empty);
- logBuilder.Append(" Response:").Append(responseContent);
- _restLog.LogDebug("{LogMsg}", logBuilder.ToString());
+
+ _restLog.LogDebug(msgSuccessWithContentTemplate, dataId, uri.AbsoluteUri, watch.ElapsedMilliseconds.ToString(CultureInfo.InvariantCulture), responseContent);
var memoryStream = new MemoryStream();
var writer = new StreamWriter(memoryStream);
@@ -124,6 +118,34 @@ public override async Task PostDataAsync(Uri uri, HttpConte
{
var dataId = _sequenceGenerator.GetNext().ToString("D7", CultureInfo.InvariantCulture);
+ await LogPostRequestHttpContent(dataId, uri, content);
+
+ var watch = Stopwatch.StartNew();
+
+ HttpResponseMessage response = null;
+ try
+ {
+ response = await base.PostDataAsync(uri, content).ConfigureAwait(false);
+ watch.Stop();
+ }
+ catch (Exception ex)
+ {
+ watch.Stop();
+ _restLog.LogError("Id:{PostRequestId} POST: {PostUri} Response: {ResponseStatusCode} took {Elapsed} ms", dataId, uri.AbsoluteUri, response?.StatusCode, watch.ElapsedMilliseconds.ToString(CultureInfo.InvariantCulture));
+ if (ex.GetType() != typeof(ObjectDisposedException) && ex.GetType() != typeof(TaskCanceledException))
+ {
+ _restLog.LogError(ex, "{ErrorMessage}", ex.Message);
+ }
+ throw;
+ }
+
+ await LogPostResponseHttpContent(dataId, uri, response, watch);
+
+ return response;
+ }
+
+ private async Task LogPostRequestHttpContent(string dataId, Uri uri, HttpContent content)
+ {
if (content != null)
{
try
@@ -131,11 +153,11 @@ public override async Task PostDataAsync(Uri uri, HttpConte
if (_restLog.IsEnabled(LogLevel.Debug))
{
var s = await content.ReadAsStringAsync().ConfigureAwait(false);
- _restLog.LogDebug("Id:{PostRequestId} Posting url: {PostUri} {PostContent}", dataId, uri.AbsoluteUri, s);
+ _restLog.LogDebug("Id:{PostRequestId} POST url: {PostUri} {PostContent}", dataId, uri.AbsoluteUri, s);
}
else
{
- _restLog.LogInformation("Id:{PostRequestId} Posting url: {PostUri}", dataId, uri.AbsoluteUri);
+ _restLog.LogInformation("Id:{PostRequestId} POST url: {PostUri}", dataId, uri.AbsoluteUri);
}
}
catch
@@ -145,28 +167,12 @@ public override async Task PostDataAsync(Uri uri, HttpConte
}
else
{
- _restLog.LogInformation("Id:{PostRequestId} Posting url: {PostUri}", dataId, uri.AbsoluteUri);
- }
-
- var watch = Stopwatch.StartNew();
-
- HttpResponseMessage response;
- try
- {
- response = await base.PostDataAsync(uri, content).ConfigureAwait(false);
- }
- catch (Exception ex)
- {
- watch.Stop();
- _restLog.LogError("Id:{PostRequestId} Posting error to {PostUri} at {Elapsed} ms", dataId, uri.AbsoluteUri, watch.ElapsedMilliseconds.ToString(CultureInfo.InvariantCulture));
- if (ex.GetType() != typeof(ObjectDisposedException) && ex.GetType() != typeof(TaskCanceledException))
- {
- _restLog.LogError(ex, "{ErrorMessage}", ex.Message);
- }
- throw;
+ _restLog.LogInformation("Id:{PostRequestId} POST url: {PostUri}", dataId, uri.AbsoluteUri);
}
+ }
- watch.Stop();
+ private async Task LogPostResponseHttpContent(string dataId, Uri uri, HttpResponseMessage response, Stopwatch watch)
+ {
var responseContent = string.Empty;
if (response.Content != null)
{
@@ -179,32 +185,40 @@ public override async Task PostDataAsync(Uri uri, HttpConte
// ignored
}
}
- if (_restLog.IsEnabled(LogLevel.Debug) || !response.IsSuccessStatusCode)
+
+ var wantedLogLevel = SdkLoggerFactory.GetWriteLogLevel(_restLog, LogLevel.Debug);
+ if (!response.IsSuccessStatusCode)
+ {
+ wantedLogLevel = LogLevel.Warning;
+ }
+ else if (!_restLog.IsEnabled(LogLevel.Debug))
{
- const string msgTemplate = "Id:{DataId} Posting took {Elapsed} ms. Response: {ResponseStatusCode}-{ResponseReasonPhrase} {ResponseContent}";
- if (!response.IsSuccessStatusCode)
- {
- _restLog.LogWarning(msgTemplate, dataId, watch.ElapsedMilliseconds.ToString(CultureInfo.InvariantCulture), ((int)response.StatusCode).ToString(CultureInfo.InvariantCulture), response.ReasonPhrase, responseContent);
- }
- else
- {
- _restLog.Log(SdkLoggerFactory.GetWriteLogLevel(_restLog, LogLevel.Debug),
- msgTemplate,
- dataId,
- watch.ElapsedMilliseconds.ToString(CultureInfo.InvariantCulture),
- ((int)response.StatusCode).ToString(CultureInfo.InvariantCulture),
- response.ReasonPhrase,
- responseContent);
- }
+ responseContent = string.Empty;
}
- else
+ const string msgTemplate = "Id:{DataId} POST: {PostUri} took {Elapsed} ms. Response: {ResponseStatusCode}-{ResponseReasonPhrase} {ResponseContent}";
+ _restLog.Log(wantedLogLevel,
+ msgTemplate,
+ dataId,
+ uri.AbsoluteUri,
+ watch.ElapsedMilliseconds.ToString(CultureInfo.InvariantCulture),
+ ((int)response.StatusCode).ToString(CultureInfo.InvariantCulture),
+ response.ReasonPhrase,
+ responseContent);
+ }
+
+ private static string GetCommunicationExceptionContent(CommunicationException commException)
+ {
+ if (commException.Message.Contains("NotFound"))
{
- if (watch.ElapsedMilliseconds > 100)
- {
- _restLog.LogInformation("Id:{PostRequestId} Posting took {Elapsed} ms", dataId, watch.ElapsedMilliseconds.ToString(CultureInfo.InvariantCulture));
- }
+ return commException.Message;
}
- return response;
+ if (commException.InnerException != null)
+ {
+ return commException.InnerException.Message;
+ }
+ return commException.Response == null
+ ? commException.Message
+ : commException.Response.Replace("\n", string.Empty);
}
}
}
diff --git a/src/Sportradar.OddsFeed.SDK/API/Internal/ApiAccess/SdkHttpClient.cs b/src/Sportradar.OddsFeed.SDK/API/Internal/ApiAccess/SdkHttpClient.cs
index 9dadb7fd..ed9b2142 100644
--- a/src/Sportradar.OddsFeed.SDK/API/Internal/ApiAccess/SdkHttpClient.cs
+++ b/src/Sportradar.OddsFeed.SDK/API/Internal/ApiAccess/SdkHttpClient.cs
@@ -10,55 +10,61 @@ namespace Sportradar.OddsFeed.SDK.Api.Internal.ApiAccess
{
internal class SdkHttpClient : ISdkHttpClient
{
- private readonly HttpClient _httpClient;
-
- public TimeSpan Timeout => _httpClient.Timeout;
+ private readonly IHttpClientFactory _httpClientFactory;
+ private readonly string _httpClientName;
+ public TimeSpan Timeout { get; }
///
- public HttpRequestHeaders DefaultRequestHeaders => _httpClient.DefaultRequestHeaders;
+ public HttpRequestHeaders DefaultRequestHeaders { get; }
- public SdkHttpClient(HttpClient httpClient)
+ public SdkHttpClient(IHttpClientFactory httpClientFactory, string httpClientName)
{
- _httpClient = httpClient ?? throw new ArgumentNullException(nameof(httpClient));
- if (_httpClient.DefaultRequestHeaders.IsNullOrEmpty())
+ _httpClientFactory = httpClientFactory ?? throw new ArgumentNullException(nameof(httpClientFactory));
+ _httpClientName = string.IsNullOrEmpty(httpClientName) ? throw new ArgumentNullException(nameof(httpClientName)) : httpClientName;
+
+ var httpClient = _httpClientFactory.CreateClient(_httpClientName);
+ if (httpClient.DefaultRequestHeaders.IsNullOrEmpty())
{
throw new InvalidOperationException("Missing DefaultRequestHeaders");
}
- if (!_httpClient.DefaultRequestHeaders.Contains("x-access-token"))
+ if (!httpClient.DefaultRequestHeaders.Contains("x-access-token"))
{
throw new InvalidOperationException("Missing x-access-token");
}
- if (!_httpClient.DefaultRequestHeaders.Contains("User-Agent"))
+ if (!httpClient.DefaultRequestHeaders.Contains("User-Agent"))
{
throw new InvalidOperationException("User-Agent");
}
- //_httpClient.DefaultRequestHeaders.Add("x-access-token", accessToken); //
- //_httpClient.DefaultRequestHeaders.Add("User-Agent", $"UfSdk-{SdkInfo.SdkType}/{SdkInfo.GetVersion()} (OS: {Environment.OSVersion}, NET: {Environment.Version}, Init: {SdkInfo.Created:yyyyMMddHHmm})");
- //Timeout = TimeSpan.FromSeconds(_httpClient.Timeout.TotalSeconds);
+ Timeout = httpClient.Timeout;
+ DefaultRequestHeaders = httpClient.DefaultRequestHeaders;
}
///
public async Task GetAsync(Uri requestUri)
{
- return await _httpClient.GetAsync(requestUri).ConfigureAwait(false);
+ var httpClient = _httpClientFactory.CreateClient(_httpClientName);
+ return await httpClient.GetAsync(requestUri).ConfigureAwait(false);
}
///
public async Task PostAsync(Uri requestUri, HttpContent content)
{
- return await _httpClient.PostAsync(requestUri, content).ConfigureAwait(false);
+ var httpClient = _httpClientFactory.CreateClient(_httpClientName);
+ return await httpClient.PostAsync(requestUri, content).ConfigureAwait(false);
}
///
public async Task DeleteAsync(Uri requestUri)
{
- return await _httpClient.DeleteAsync(requestUri).ConfigureAwait(false);
+ var httpClient = _httpClientFactory.CreateClient(_httpClientName);
+ return await httpClient.DeleteAsync(requestUri).ConfigureAwait(false);
}
///
public async Task PutAsync(Uri requestUri, HttpContent content)
{
- return await _httpClient.PutAsync(requestUri, content).ConfigureAwait(false);
+ var httpClient = _httpClientFactory.CreateClient(_httpClientName);
+ return await httpClient.PutAsync(requestUri, content).ConfigureAwait(false);
}
}
}
diff --git a/src/Sportradar.OddsFeed.SDK/API/Internal/ApiAccess/SdkHttpClientFastFailing.cs b/src/Sportradar.OddsFeed.SDK/API/Internal/ApiAccess/SdkHttpClientFastFailing.cs
index b6d6b6ad..25483abe 100644
--- a/src/Sportradar.OddsFeed.SDK/API/Internal/ApiAccess/SdkHttpClientFastFailing.cs
+++ b/src/Sportradar.OddsFeed.SDK/API/Internal/ApiAccess/SdkHttpClientFastFailing.cs
@@ -6,8 +6,8 @@ namespace Sportradar.OddsFeed.SDK.Api.Internal.ApiAccess
{
internal class SdkHttpClientFastFailing : SdkHttpClient, ISdkHttpClientFastFailing
{
- public SdkHttpClientFastFailing(HttpClient httpClient)
- : base(httpClient)
+ public SdkHttpClientFastFailing(IHttpClientFactory httpClientFactory, string httpClientName)
+ : base(httpClientFactory, httpClientName)
{
}
}
diff --git a/src/Sportradar.OddsFeed.SDK/API/Internal/ApiAccess/SdkHttpClientPool.cs b/src/Sportradar.OddsFeed.SDK/API/Internal/ApiAccess/SdkHttpClientPool.cs
deleted file mode 100644
index 0449975f..00000000
--- a/src/Sportradar.OddsFeed.SDK/API/Internal/ApiAccess/SdkHttpClientPool.cs
+++ /dev/null
@@ -1,118 +0,0 @@
-// Copyright (C) Sportradar AG.See LICENSE for full license governing this code
-
-using System;
-using System.Collections.Generic;
-using System.Linq;
-using System.Net.Http;
-using System.Net.Http.Headers;
-using System.Threading.Tasks;
-using Microsoft.Extensions.Logging;
-using Sportradar.OddsFeed.SDK.Common.Internal;
-using Sportradar.OddsFeed.SDK.Common.Internal.Telemetry;
-
-namespace Sportradar.OddsFeed.SDK.Api.Internal.ApiAccess
-{
- internal class SdkHttpClientPool : ISdkHttpClient
- {
- private readonly IList _httpClientPool;
- private int _latest;
- private readonly object _lock = new object();
-
- public TimeSpan Timeout { get; }
-
- ///
- public HttpRequestHeaders DefaultRequestHeaders { get; }
-
- public SdkHttpClientPool(string accessToken, int poolSize, int timeoutSecond, int maxServerConnections)
- : this(accessToken, poolSize, TimeSpan.FromSeconds(timeoutSecond), maxServerConnections)
- {
- }
-
- public SdkHttpClientPool(string accessToken, int poolSize, TimeSpan timeout, int maxServerConnections)
- {
- if (poolSize < 1)
- {
- poolSize = 1;
- }
- var httpClientHandler = new HttpClientHandler
- {
- MaxConnectionsPerServer = maxServerConnections,
- AllowAutoRedirect = true
- };
- _httpClientPool = new List(poolSize);
- for (var i = 0; i < poolSize; i++)
- {
- var httpClient = new HttpClient(httpClientHandler) { Timeout = timeout };
- httpClient.DefaultRequestHeaders.Add("x-access-token", accessToken);
- httpClient.DefaultRequestHeaders.Add("User-Agent", $"UfSdk-{SdkInfo.SdkType}/{SdkInfo.GetVersion()} (NET: {Environment.Version}, OS: {Environment.OSVersion}, Init: {SdkInfo.Created:yyyyMMddHHmm})");
- _httpClientPool.Add(httpClient);
- }
-
- SdkLoggerFactory.GetLoggerForExecution(typeof(SdkHttpClientPool)).LogDebug("SdkHttpClientPool with size {PoolSize} and timeout {TimeoutSeconds}s created", poolSize, timeout.TotalSeconds);
- DefaultRequestHeaders = _httpClientPool.First().DefaultRequestHeaders;
- Timeout = TimeSpan.FromSeconds(_httpClientPool.First().Timeout.TotalSeconds);
- }
-
- internal SdkHttpClientPool(string accessToken, int poolSize, TimeSpan timeout, HttpMessageHandler httpMessageHandler)
- {
- if (poolSize < 1)
- {
- poolSize = 1;
- }
- _httpClientPool = new List(poolSize);
- for (var i = 0; i < poolSize; i++)
- {
- var httpClient = new HttpClient(httpMessageHandler) { Timeout = timeout };
- httpClient.DefaultRequestHeaders.Add("x-access-token", accessToken);
- httpClient.DefaultRequestHeaders.Add("User-Agent", $"UfSdk-{SdkInfo.SdkType}/{SdkInfo.GetVersion()} (NET: {Environment.Version}, OS: {Environment.OSVersion}, Init: {SdkInfo.Created:yyyyMMddHHmm})");
- _httpClientPool.Add(httpClient);
- }
-
- SdkLoggerFactory.GetLoggerForExecution(typeof(SdkHttpClientPool)).LogDebug("SdkHttpClientPool with size {PoolSize} and timeout {TimeoutSeconds}s created", poolSize, timeout.TotalSeconds);
- DefaultRequestHeaders = _httpClientPool.First().DefaultRequestHeaders;
- }
-
- ///
- public async Task GetAsync(Uri requestUri)
- {
- var httpClient = GetHttpClient();
- var result = await httpClient.GetAsync(requestUri).ConfigureAwait(false);
- return result;
- }
-
- ///
- public async Task PostAsync(Uri requestUri, HttpContent content)
- {
- var httpClient = GetHttpClient();
- return await httpClient.PostAsync(requestUri, content).ConfigureAwait(false);
- }
-
- ///
- public async Task DeleteAsync(Uri requestUri)
- {
- var httpClient = GetHttpClient();
- return await httpClient.DeleteAsync(requestUri).ConfigureAwait(false);
- }
-
- ///
- public async Task PutAsync(Uri requestUri, HttpContent content)
- {
- var httpClient = GetHttpClient();
- return await httpClient.PutAsync(requestUri, content).ConfigureAwait(false);
- }
-
- private HttpClient GetHttpClient()
- {
- if (_httpClientPool.Count == 1)
- {
- return _httpClientPool[0];
- }
-
- lock (_lock)
- {
- _latest = _latest == _httpClientPool.Count - 1 ? 0 : _latest + 1;
- return _httpClientPool[_latest];
- }
- }
- }
-}
diff --git a/src/Sportradar.OddsFeed.SDK/API/Internal/ApiAccess/SdkHttpClientRecovery.cs b/src/Sportradar.OddsFeed.SDK/API/Internal/ApiAccess/SdkHttpClientRecovery.cs
index 2bc801ff..5f323216 100644
--- a/src/Sportradar.OddsFeed.SDK/API/Internal/ApiAccess/SdkHttpClientRecovery.cs
+++ b/src/Sportradar.OddsFeed.SDK/API/Internal/ApiAccess/SdkHttpClientRecovery.cs
@@ -6,8 +6,8 @@ namespace Sportradar.OddsFeed.SDK.Api.Internal.ApiAccess
{
internal class SdkHttpClientRecovery : SdkHttpClient, ISdkHttpClientRecovery
{
- public SdkHttpClientRecovery(HttpClient httpClient)
- : base(httpClient)
+ public SdkHttpClientRecovery(IHttpClientFactory httpClientFactory, string httpClientName)
+ : base(httpClientFactory, httpClientName)
{
}
}
diff --git a/src/Sportradar.OddsFeed.SDK/API/Internal/Config/UofSdkBootstrap.cs b/src/Sportradar.OddsFeed.SDK/API/Internal/Config/UofSdkBootstrap.cs
index 56178a6a..6ac6544c 100644
--- a/src/Sportradar.OddsFeed.SDK/API/Internal/Config/UofSdkBootstrap.cs
+++ b/src/Sportradar.OddsFeed.SDK/API/Internal/Config/UofSdkBootstrap.cs
@@ -39,6 +39,9 @@ internal static class UofSdkBootstrap
{
internal const string HttpClientDefaultRequestHeaderForAccessToken = "x-access-token";
internal const string HttpClientDefaultRequestHeaderForUserAgent = "User-Agent";
+ internal const string HttpClientNameForFastFailing = "HttpClientFastFailing";
+ internal const string HttpClientNameForRecovery = "HttpClientRecovery";
+ internal const string HttpClientNameForNormal = "HttpClientNormal";
internal const string CacheStoreNameForInvariantMarketDescriptionsCache = "MemoryCacheForInvariantMarketDescriptionsCache";
internal const string CacheStoreNameForVariantMarketDescriptionCache = "MemoryCacheForVariantMarketDescriptionCache";
@@ -146,61 +149,45 @@ private static void RegisterBaseTypes(IServiceCollection services, IUofConfigura
private static void RegisterHttpClients(IServiceCollection services, IUofConfiguration configuration)
{
- var httpClientHandler = new HttpClientHandler { MaxConnectionsPerServer = configuration.Api.MaxConnectionsPerServer, AllowAutoRedirect = true };
-
var userAgentData = string.Intern($"UfSdk-{SdkInfo.SdkType}/{SdkInfo.GetVersion()} (OS: {Environment.OSVersion}, NET: {Environment.Version}, Init: {DateTime.UtcNow:yyyyMMddHHmm})");
- services.AddHttpClient("HttpClient")
- .ConfigureHttpClient(configureClient =>
- {
- configureClient.Timeout = configuration.Api.HttpClientTimeout;
- configureClient.DefaultRequestHeaders.Add(HttpClientDefaultRequestHeaderForAccessToken, configuration.AccessToken);
- configureClient.DefaultRequestHeaders.Add(HttpClientDefaultRequestHeaderForUserAgent, userAgentData);
- })
- .ConfigurePrimaryHttpMessageHandler(() => httpClientHandler);
- services.AddHttpClient("HttpClientRecovery")
- .ConfigureHttpClient(configureClient =>
- {
- configureClient.Timeout = configuration.Api.HttpClientRecoveryTimeout;
- configureClient.DefaultRequestHeaders.Add(HttpClientDefaultRequestHeaderForAccessToken, configuration.AccessToken);
- configureClient.DefaultRequestHeaders.Add(HttpClientDefaultRequestHeaderForUserAgent, userAgentData);
- })
- .ConfigurePrimaryHttpMessageHandler(() => httpClientHandler);
- services.AddHttpClient("HttpClientFastFailing")
- .ConfigureHttpClient(configureClient =>
- {
- configureClient.Timeout = configuration.Api.HttpClientFastFailingTimeout;
- configureClient.DefaultRequestHeaders.Add(HttpClientDefaultRequestHeaderForAccessToken, configuration.AccessToken);
- configureClient.DefaultRequestHeaders.Add(HttpClientDefaultRequestHeaderForUserAgent, userAgentData);
- })
- .ConfigurePrimaryHttpMessageHandler(() => httpClientHandler);
-
- services.AddSingleton(serviceProvider =>
-
- new SdkHttpClient(serviceProvider.GetRequiredService().CreateClient("HttpClient"))
- );
- services.AddSingleton(serviceProvider =>
-
- new SdkHttpClientRecovery(serviceProvider.GetRequiredService().CreateClient("HttpClientRecovery"))
- );
- services.AddSingleton(serviceProvider =>
-
- new SdkHttpClientFastFailing(serviceProvider.GetRequiredService().CreateClient("HttpClientFastFailing"))
- );
-
- services.AddSingleton(serviceProvider =>
- new HttpDataFetcher(
- serviceProvider.GetRequiredService(),
- serviceProvider.GetRequiredService>())
- );
- var logHttpDataFetcherLoggerName = SdkLoggerFactory.SdkLogRepositoryName + "." + Enum.GetName(typeof(LoggerType), LoggerType.RestTraffic);
+ services.AddHttpClient(HttpClientNameForNormal)
+ .ConfigureHttpClient(configureClient =>
+ {
+ configureClient.Timeout = configuration.Api.HttpClientTimeout;
+ configureClient.DefaultRequestHeaders.Add(HttpClientDefaultRequestHeaderForAccessToken, configuration.AccessToken);
+ configureClient.DefaultRequestHeaders.Add(HttpClientDefaultRequestHeaderForUserAgent, userAgentData);
+ })
+ .ConfigurePrimaryHttpMessageHandler(() => new HttpClientHandler { MaxConnectionsPerServer = configuration.Api.MaxConnectionsPerServer, AllowAutoRedirect = true });
+ services.AddHttpClient(HttpClientNameForRecovery)
+ .ConfigureHttpClient(configureClient =>
+ {
+ configureClient.Timeout = configuration.Api.HttpClientRecoveryTimeout;
+ configureClient.DefaultRequestHeaders.Add(HttpClientDefaultRequestHeaderForAccessToken, configuration.AccessToken);
+ configureClient.DefaultRequestHeaders.Add(HttpClientDefaultRequestHeaderForUserAgent, userAgentData);
+ })
+ .ConfigurePrimaryHttpMessageHandler(() => new HttpClientHandler { MaxConnectionsPerServer = configuration.Api.MaxConnectionsPerServer, AllowAutoRedirect = true });
+ services.AddHttpClient(HttpClientNameForFastFailing)
+ .ConfigureHttpClient(configureClient =>
+ {
+ configureClient.Timeout = configuration.Api.HttpClientFastFailingTimeout;
+ configureClient.DefaultRequestHeaders.Add(HttpClientDefaultRequestHeaderForAccessToken, configuration.AccessToken);
+ configureClient.DefaultRequestHeaders.Add(HttpClientDefaultRequestHeaderForUserAgent, userAgentData);
+ })
+ .ConfigurePrimaryHttpMessageHandler(() => new HttpClientHandler { MaxConnectionsPerServer = configuration.Api.MaxConnectionsPerServer, AllowAutoRedirect = true });
+
+ services.AddTransient(serviceProvider => new SdkHttpClient(serviceProvider.GetRequiredService(), HttpClientNameForNormal));
+ services.AddTransient(serviceProvider => new SdkHttpClientRecovery(serviceProvider.GetRequiredService(), HttpClientNameForRecovery));
+ services.AddTransient(serviceProvider => new SdkHttpClientFastFailing(serviceProvider.GetRequiredService(), HttpClientNameForFastFailing));
+
+ var restTrafficLoggerName = SdkLoggerFactory.SdkLogRepositoryName + "." + Enum.GetName(typeof(LoggerType), LoggerType.RestTraffic);
services.AddSingleton(serviceProvider =>
new LogHttpDataFetcher(
serviceProvider.GetRequiredService(),
serviceProvider.GetRequiredService(),
serviceProvider.GetRequiredService>(),
- serviceProvider.GetService().CreateLogger(logHttpDataFetcherLoggerName))
+ serviceProvider.GetService().CreateLogger(restTrafficLoggerName))
);
services.AddSingleton(serviceProvider =>
@@ -208,7 +195,7 @@ private static void RegisterHttpClients(IServiceCollection services, IUofConfigu
serviceProvider.GetRequiredService(),
serviceProvider.GetRequiredService(),
serviceProvider.GetRequiredService>(),
- serviceProvider.GetService().CreateLogger(logHttpDataFetcherLoggerName))
+ serviceProvider.GetService().CreateLogger(restTrafficLoggerName))
);
services.AddSingleton(serviceProvider =>
@@ -222,7 +209,7 @@ private static void RegisterHttpClients(IServiceCollection services, IUofConfigu
serviceProvider.GetRequiredService(),
serviceProvider.GetRequiredService(),
serviceProvider.GetRequiredService>(),
- serviceProvider.GetService().CreateLogger(logHttpDataFetcherLoggerName))
+ serviceProvider.GetService().CreateLogger(restTrafficLoggerName))
);
services.AddSingleton(serviceProvider =>
@@ -230,7 +217,7 @@ private static void RegisterHttpClients(IServiceCollection services, IUofConfigu
serviceProvider.GetRequiredService(),
serviceProvider.GetRequiredService(),
serviceProvider.GetRequiredService>(),
- serviceProvider.GetService().CreateLogger(logHttpDataFetcherLoggerName))
+ serviceProvider.GetService().CreateLogger(restTrafficLoggerName))
);
services.AddSingleton(serviceProvider =>
@@ -238,11 +225,11 @@ private static void RegisterHttpClients(IServiceCollection services, IUofConfigu
serviceProvider.GetRequiredService(),
serviceProvider.GetRequiredService(),
serviceProvider.GetRequiredService>(),
- serviceProvider.GetService().CreateLogger(logHttpDataFetcherLoggerName))
+ serviceProvider.GetService().CreateLogger(restTrafficLoggerName))
);
}
- internal static void RegisterBookmakerDetailsProvider(IServiceCollection services)
+ private static void RegisterBookmakerDetailsProvider(IServiceCollection services)
{
services.AddSingleton, Deserializer>();
services.AddSingleton, BookmakerDetailsMapperFactory>();
diff --git a/src/Sportradar.OddsFeed.SDK/Sportradar.OddsFeed.SDK.csproj b/src/Sportradar.OddsFeed.SDK/Sportradar.OddsFeed.SDK.csproj
index c135a6b5..99f3122f 100644
--- a/src/Sportradar.OddsFeed.SDK/Sportradar.OddsFeed.SDK.csproj
+++ b/src/Sportradar.OddsFeed.SDK/Sportradar.OddsFeed.SDK.csproj
@@ -14,9 +14,9 @@
https://github.com/sportradar/UnifiedOddsSdkNetCore
SportRadar OddsFeed UnifiedFeed SDK NETCore NETStandard
https://sdk.sportradar.com
- 2.4.0.0
- 2.4.0.0
- 2.4.0
+ 2.4.1.0
+ 2.4.1.0
+ 2.4.1
27ef2334-e9fc-4f05-b88a-a33259c908c3
True
True
diff --git a/src/Sportradar.OddsFeed.SDK/Sportradar.OddsFeed.SDK.xml b/src/Sportradar.OddsFeed.SDK/Sportradar.OddsFeed.SDK.xml
index a221d26c..f65dc213 100644
--- a/src/Sportradar.OddsFeed.SDK/Sportradar.OddsFeed.SDK.xml
+++ b/src/Sportradar.OddsFeed.SDK/Sportradar.OddsFeed.SDK.xml
@@ -2484,7 +2484,7 @@
- A implementation of and which uses the HTTP requests to fetch or post the requested data. All request are logged.
+ An implementation of and which uses the HTTP requests to fetch or post the requested data. All request are logged.
ALL, DEBUG, INFO, WARN, ERROR, FATAL, OFF - the levels are defined in order of increasing priority
@@ -2576,21 +2576,6 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
Class used to get the bookmaker details