Skip to content

Commit 1edda19

Browse files
authored
Merge pull request #24 from cnblogs/removing-default-timeout-for-socket-handler
fix: allow configuring timeout with DashScopeClient
2 parents a4b5109 + 93a859d commit 1edda19

File tree

3 files changed

+98
-20
lines changed

3 files changed

+98
-20
lines changed

sample/Cnblogs.DashScope.Sample/Program.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
using Json.Schema;
88
using Json.Schema.Generation;
99

10-
const string apiKey = "sk-eeff76d62cc946e5af8d1444f079a34e";
10+
const string apiKey = "sk-**";
1111
var dashScopeClient = new DashScopeClient(apiKey);
1212

1313
Console.WriteLine("Choose the sample you want to run:");
Lines changed: 27 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
1-
using System.Net.Http.Headers;
2-
using Cnblogs.DashScope.Core.Internals;
1+
using Cnblogs.DashScope.Core.Internals;
32

43
namespace Cnblogs.DashScope.Core;
54

@@ -8,27 +7,37 @@ namespace Cnblogs.DashScope.Core;
87
/// </summary>
98
public class DashScopeClient : DashScopeClientCore
109
{
11-
private static HttpClient? singletonClient;
12-
private static HttpClient SingletonClient
13-
{
14-
get
15-
{
16-
singletonClient ??= new HttpClient(
17-
new SocketsHttpHandler { PooledConnectionLifetime = TimeSpan.FromMinutes(2) })
18-
{
19-
BaseAddress = new Uri(DashScopeDefaults.DashScopeApiBaseAddress)
20-
};
21-
return singletonClient;
22-
}
23-
}
10+
private static readonly Dictionary<string, HttpClient> ClientPools = new();
2411

2512
/// <summary>
2613
/// Creates a DashScopeClient for further api call.
2714
/// </summary>
2815
/// <param name="apiKey">The DashScope api key.</param>
29-
public DashScopeClient(string apiKey)
30-
: base(SingletonClient)
16+
/// <param name="timeout">The timeout for internal http client, defaults to 2 minute.</param>
17+
/// <remarks>
18+
/// The underlying httpclient is cached by apiKey and timeout.
19+
/// Client created with same apiKey and timeout value will share same underlying <see cref="HttpClient"/> instance.
20+
/// </remarks>
21+
public DashScopeClient(string apiKey, TimeSpan? timeout = null)
22+
: base(GetConfiguredClient(apiKey, timeout))
3123
{
32-
SingletonClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", apiKey);
24+
}
25+
26+
private static HttpClient GetConfiguredClient(string apiKey, TimeSpan? timeout)
27+
{
28+
var client = ClientPools.GetValueOrDefault(GetCacheKey());
29+
if (client is null)
30+
{
31+
client = new HttpClient
32+
{
33+
BaseAddress = new Uri(DashScopeDefaults.DashScopeApiBaseAddress),
34+
Timeout = timeout ?? TimeSpan.FromMinutes(2)
35+
};
36+
ClientPools.Add(GetCacheKey(), client);
37+
}
38+
39+
return client;
40+
41+
string GetCacheKey() => $"{apiKey}-{timeout?.TotalMilliseconds}";
3342
}
3443
}

test/Cnblogs.DashScope.Sdk.UnitTests/DashScopeClientTests.cs

Lines changed: 70 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
1-
using Cnblogs.DashScope.Core;
1+
using System.Reflection;
2+
using Cnblogs.DashScope.Core;
23
using FluentAssertions;
34

45
namespace Cnblogs.DashScope.Sdk.UnitTests;
@@ -17,4 +18,72 @@ public void DashScopeClient_Constructor_New()
1718
// Assert
1819
act.Should().NotThrow();
1920
}
21+
22+
[Theory]
23+
[MemberData(nameof(ParamsShouldNotCache))]
24+
public void DashScopeClient_Constructor_NotCacheableParams(
25+
string apiKey,
26+
string apiKey2,
27+
TimeSpan? timeout,
28+
TimeSpan? timeout2)
29+
{
30+
// Arrange
31+
var client = new DashScopeClient(apiKey, timeout);
32+
var client2 = new DashScopeClient(apiKey2, timeout2);
33+
34+
// Act
35+
var value = HttpClientAccessor.GetValue(client);
36+
var value2 = HttpClientAccessor.GetValue(client2);
37+
38+
// Assert
39+
value.Should().NotBe(value2);
40+
}
41+
42+
[Theory]
43+
[MemberData(nameof(ParamsShouldCache))]
44+
public void DashScopeClient_Constructor_CacheableParams(
45+
string apiKey,
46+
string apiKey2,
47+
TimeSpan? timeout,
48+
TimeSpan? timeout2)
49+
{
50+
// Arrange
51+
var client = new DashScopeClient(apiKey, timeout);
52+
var client2 = new DashScopeClient(apiKey2, timeout2);
53+
54+
// Act
55+
var value = HttpClientAccessor.GetValue(client);
56+
var value2 = HttpClientAccessor.GetValue(client2);
57+
58+
// Assert
59+
value.Should().Be(value2);
60+
}
61+
62+
public static TheoryData<string, string, TimeSpan?, TimeSpan?> ParamsShouldNotCache
63+
=> new()
64+
{
65+
{ "apiKey", "apiKey2", null, null }, // same null timespan with different apikey
66+
{
67+
// same timespan with different apiKey
68+
"apikey", "apiKey2", TimeSpan.FromMinutes(2), TimeSpan.FromMinutes(2)
69+
},
70+
{
71+
// same apikey with different timeout
72+
"apiKey", "apiKey", TimeSpan.FromMinutes(2), TimeSpan.FromMinutes(1)
73+
}
74+
};
75+
76+
public static TheoryData<string, string, TimeSpan?, TimeSpan?> ParamsShouldCache
77+
=> new()
78+
{
79+
{ "apiKey", "apiKey", null, null }, // same null timespan with same apikey
80+
{
81+
// same timespan with same apiKey
82+
"apiKey", "apiKey", TimeSpan.FromMinutes(2), TimeSpan.FromMinutes(2)
83+
}
84+
};
85+
86+
private static readonly FieldInfo HttpClientAccessor = typeof(DashScopeClientCore).GetField(
87+
"_httpClient",
88+
BindingFlags.Instance | BindingFlags.NonPublic)!;
2089
}

0 commit comments

Comments
 (0)