Skip to content

Commit aa6335b

Browse files
committed
feat!: add configurable HTTP client timeout
Add Route4MeConfig.HttpTimeout property to allow global configuration of HTTP request timeout for all SDK API calls. Default timeout reduced from 30 minutes to 30 seconds for better reliability. - Add Route4MeConfig static class for global SDK settings - Add HttpTimeout property with default of 30 seconds - Add ConfigurationExample demonstrating timeout configuration - Add ConfigurationTests validating timeout behavior - Update CHANGELOG.md with version 7.13.3 changes BREAKING CHANGE: Default HTTP timeout reduced from 30 minutes to 30 seconds.
1 parent e90dd69 commit aa6335b

File tree

5 files changed

+182
-1
lines changed

5 files changed

+182
-1
lines changed

route4me-csharp-sdk/CHANGELOG.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,12 @@
11
# Changelog
22
All notable changes to this project will be documented in this file.
33

4+
## [7.13.3] - 2025-12-17
5+
### Changed
6+
- Reduced default HTTP request timeout from 30 minutes to 30 seconds
7+
- Added configurable HTTP timeout via `Route4MeConfig.HttpTimeout` property
8+
- Users can now customize timeout globally before making API calls
9+
410
## [7.13.2] - 2025-01-17
511
### Changed
612
Migrated project to .NET 10.0:

route4me-csharp-sdk/Route4MeSDKLibrary/HttpClientHolderManager.cs

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,12 @@ internal class HttpClientHolderManager
1818

1919
private static readonly object SyncRoot = new object();
2020

21+
/// <summary>
22+
/// Gets or sets the HTTP request timeout. Default is 30 seconds.
23+
/// Change this value to configure the timeout for all HTTP requests made by the SDK.
24+
/// </summary>
25+
public static TimeSpan RequestsTimeout { get; set; } = TimeSpan.FromSeconds(30);
26+
2127
static HttpClientHolderManager()
2228
{
2329
// ReSharper disable once ObjectCreationAsStatement
@@ -59,7 +65,7 @@ public static void ReleaseHttpClientHolder(string baseAddress)
5965

6066
private static HttpClient CreateHttpClient(string baseAddress, string apiKey = null)
6167
{
62-
var result = new HttpClient { BaseAddress = new Uri(baseAddress), Timeout = TimeSpan.FromMinutes(30) };
68+
var result = new HttpClient { BaseAddress = new Uri(baseAddress), Timeout = RequestsTimeout };
6369

6470
result.DefaultRequestHeaders.Accept.Clear();
6571
result.DefaultRequestHeaders.ConnectionClose = false;
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
using System;
2+
3+
namespace Route4MeSDKLibrary
4+
{
5+
/// <summary>
6+
/// Global configuration settings for the Route4Me SDK.
7+
/// </summary>
8+
public static class Route4MeConfig
9+
{
10+
/// <summary>
11+
/// Gets or sets the HTTP request timeout for all API calls made by the SDK.
12+
/// Default is 30 seconds. Set this value before making any API calls to configure the timeout globally.
13+
/// </summary>
14+
/// <example>
15+
/// // Set a 60-second timeout for all API calls
16+
/// Route4MeConfig.HttpTimeout = TimeSpan.FromSeconds(60);
17+
///
18+
/// // Or use minutes
19+
/// Route4MeConfig.HttpTimeout = TimeSpan.FromMinutes(2);
20+
/// </example>
21+
public static TimeSpan HttpTimeout
22+
{
23+
get => HttpClientHolderManager.RequestsTimeout;
24+
set => HttpClientHolderManager.RequestsTimeout = value;
25+
}
26+
}
27+
}
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
using System;
2+
3+
using Route4MeSDKLibrary;
4+
5+
namespace Route4MeSDK.Examples
6+
{
7+
/// <summary>
8+
/// Example demonstrating how to configure the Route4Me SDK globally.
9+
/// </summary>
10+
public sealed partial class Route4MeExamples
11+
{
12+
public void TimeoutConfigurationExample()
13+
{
14+
Console.WriteLine("=== Route4Me SDK Configuration Examples ===\n");
15+
16+
// Example 1: Check default timeout
17+
Console.WriteLine($"Default HTTP timeout: {Route4MeConfig.HttpTimeout.TotalSeconds} seconds");
18+
19+
// Example 2: Set a custom timeout (60 seconds)
20+
Console.WriteLine("\nSetting custom timeout to 60 seconds...");
21+
Route4MeConfig.HttpTimeout = TimeSpan.FromSeconds(60);
22+
Console.WriteLine($"New HTTP timeout: {Route4MeConfig.HttpTimeout.TotalSeconds} seconds");
23+
24+
// Example 3: Set timeout using minutes
25+
Console.WriteLine("\nSetting timeout to 2 minutes...");
26+
Route4MeConfig.HttpTimeout = TimeSpan.FromMinutes(2);
27+
Console.WriteLine($"New HTTP timeout: {Route4MeConfig.HttpTimeout.TotalMinutes} minutes");
28+
29+
// Example 4: Reset to default (30 seconds)
30+
Console.WriteLine("\nResetting to default timeout (30 seconds)...");
31+
Route4MeConfig.HttpTimeout = TimeSpan.FromSeconds(30);
32+
Console.WriteLine($"HTTP timeout: {Route4MeConfig.HttpTimeout.TotalSeconds} seconds");
33+
34+
Console.WriteLine("\n=== Important Notes ===");
35+
Console.WriteLine("- Set the timeout BEFORE making any API calls");
36+
Console.WriteLine("- The timeout applies to all subsequent HTTP requests");
37+
Console.WriteLine("- Default timeout is 30 seconds");
38+
Console.WriteLine("- Increase for long-running operations (e.g., large optimizations)");
39+
Console.WriteLine("- Decrease for faster failure detection in time-sensitive scenarios");
40+
}
41+
}
42+
}
Lines changed: 100 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,100 @@
1+
using System;
2+
using System.Reflection;
3+
4+
using NUnit.Framework;
5+
6+
using Route4MeSDKLibrary;
7+
8+
namespace Route4MeSdkV5UnitTest.V5
9+
{
10+
[TestFixture]
11+
public class ConfigurationTests
12+
{
13+
[Test]
14+
public void HttpClient_UsesConfiguredTimeout_WhenCreated()
15+
{
16+
// Arrange
17+
var originalTimeout = Route4MeConfig.HttpTimeout;
18+
var customTimeout = TimeSpan.FromSeconds(45);
19+
20+
try
21+
{
22+
// Set custom timeout before creating HttpClient
23+
Route4MeConfig.HttpTimeout = customTimeout;
24+
25+
// Use a unique base address to ensure we get a fresh HttpClient instance
26+
var uniqueBaseAddress = $"https://test-{Guid.NewGuid()}.route4me.com";
27+
28+
// Act - Use reflection to access the internal HttpClientHolderManager
29+
var holderManagerType = Type.GetType("Route4MeSDKLibrary.HttpClientHolderManager, Route4MeSDKLibrary");
30+
Assert.IsNotNull(holderManagerType, "HttpClientHolderManager type not found");
31+
32+
var acquireMethod = holderManagerType.GetMethod("AcquireHttpClientHolder",
33+
BindingFlags.Public | BindingFlags.Static);
34+
Assert.IsNotNull(acquireMethod, "AcquireHttpClientHolder method not found");
35+
36+
var holder = acquireMethod.Invoke(null, new object[] { uniqueBaseAddress, null });
37+
Assert.IsNotNull(holder, "HttpClientHolder should not be null");
38+
39+
// Get the HttpClient from the holder
40+
var httpClientProperty = holder.GetType().GetProperty("HttpClient");
41+
Assert.IsNotNull(httpClientProperty, "HttpClient property not found");
42+
43+
var httpClient = httpClientProperty.GetValue(holder) as System.Net.Http.HttpClient;
44+
Assert.IsNotNull(httpClient, "HttpClient should not be null");
45+
46+
// Assert - Verify the HttpClient has the custom timeout
47+
Assert.AreEqual(customTimeout, httpClient.Timeout,
48+
$"HttpClient timeout should be {customTimeout.TotalSeconds} seconds");
49+
50+
// Cleanup - Release the holder
51+
var releaseMethod = holderManagerType.GetMethod("ReleaseHttpClientHolder",
52+
BindingFlags.Public | BindingFlags.Static);
53+
releaseMethod?.Invoke(null, new object[] { uniqueBaseAddress });
54+
}
55+
finally
56+
{
57+
// Restore original timeout
58+
Route4MeConfig.HttpTimeout = originalTimeout;
59+
}
60+
}
61+
62+
[Test]
63+
public void HttpClient_DefaultTimeout_Is30Seconds_WhenNoConfigurationSet()
64+
{
65+
// Arrange
66+
var originalTimeout = Route4MeConfig.HttpTimeout;
67+
68+
try
69+
{
70+
// Reset to default
71+
Route4MeConfig.HttpTimeout = TimeSpan.FromSeconds(30);
72+
73+
// Use a unique base address to ensure we get a fresh HttpClient instance
74+
var uniqueBaseAddress = $"https://test-default-{Guid.NewGuid()}.route4me.com";
75+
76+
// Act - Use reflection to access the internal HttpClientHolderManager
77+
var holderManagerType = Type.GetType("Route4MeSDKLibrary.HttpClientHolderManager, Route4MeSDKLibrary");
78+
var acquireMethod = holderManagerType.GetMethod("AcquireHttpClientHolder",
79+
BindingFlags.Public | BindingFlags.Static);
80+
var holder = acquireMethod.Invoke(null, new object[] { uniqueBaseAddress, null });
81+
var httpClientProperty = holder.GetType().GetProperty("HttpClient");
82+
var httpClient = httpClientProperty.GetValue(holder) as System.Net.Http.HttpClient;
83+
84+
// Assert - Verify the HttpClient has the default 30-second timeout
85+
Assert.AreEqual(TimeSpan.FromSeconds(30), httpClient.Timeout,
86+
"HttpClient should have default 30-second timeout");
87+
88+
// Cleanup
89+
var releaseMethod = holderManagerType.GetMethod("ReleaseHttpClientHolder",
90+
BindingFlags.Public | BindingFlags.Static);
91+
releaseMethod?.Invoke(null, new object[] { uniqueBaseAddress });
92+
}
93+
finally
94+
{
95+
// Restore original timeout
96+
Route4MeConfig.HttpTimeout = originalTimeout;
97+
}
98+
}
99+
}
100+
}

0 commit comments

Comments
 (0)