Skip to content

Commit e24f379

Browse files
Move to FlurlClientCache
1 parent 9607941 commit e24f379

File tree

6 files changed

+75
-47
lines changed

6 files changed

+75
-47
lines changed

src/CouchDB.Driver/CouchClient.cs

Lines changed: 50 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -9,9 +9,11 @@
99
using CouchDB.Driver.DTOs;
1010
using CouchDB.Driver.Exceptions;
1111
using System.Net;
12+
using System.Text.Json;
1213
using System.Threading;
1314
using CouchDB.Driver.Options;
1415
using CouchDB.Driver.Query;
16+
using Flurl.Http.Configuration;
1517

1618
namespace CouchDB.Driver;
1719

@@ -20,11 +22,13 @@ namespace CouchDB.Driver;
2022
/// </summary>
2123
public partial class CouchClient : ICouchClient
2224
{
25+
private const string DefaultHttpClientName = "Default";
2326
public const string DefaultDatabaseSplitDiscriminator = "split_discriminator";
2427
private DateTimeOffset? _cookieCreationDate;
2528
private string? _cookieToken;
2629

27-
private readonly IFlurlClient _flurlClient;
30+
private readonly Func<IFlurlClient> _flurlClient;
31+
private readonly IFlurlClientCache _flurlClientCache;
2832
private readonly CouchOptions _options;
2933
private readonly string[] _systemDatabases = ["_users", "_replicator", "_global_changes"];
3034
public Uri Endpoint { get; }
@@ -45,55 +49,69 @@ public CouchClient(string endpoint, Action<CouchOptionsBuilder>? couchSettingsFu
4549
/// <param name="endpoint">URI to the CouchDB endpoint.</param>
4650
/// <param name="couchSettingsFunc">A function to configure options.</param>
4751
public CouchClient(Uri endpoint, Action<CouchOptionsBuilder>? couchSettingsFunc = null)
52+
: this(BuildOptions(couchSettingsFunc, endpoint))
4853
{
49-
var optionsBuilder = new CouchOptionsBuilder();
50-
couchSettingsFunc?.Invoke(optionsBuilder);
51-
_options = optionsBuilder.Options;
52-
Endpoint = endpoint;
53-
_flurlClient = GetConfiguredClient();
5454
}
5555

5656
/// <summary>
5757
/// Creates a new CouchDB client.
5858
/// </summary>
5959
/// <param name="couchSettingsFunc">A function to configure options.</param>
6060
public CouchClient(Action<CouchOptionsBuilder>? couchSettingsFunc = null)
61+
: this(BuildOptions(couchSettingsFunc))
6162
{
62-
var optionsBuilder = new CouchOptionsBuilder();
63-
couchSettingsFunc?.Invoke(optionsBuilder);
63+
}
6464

65-
if (optionsBuilder.Options.Endpoint == null)
65+
internal CouchClient(CouchOptions options)
66+
{
67+
if (options.Endpoint == null)
6668
{
6769
throw new InvalidOperationException("Database endpoint must be set.");
6870
}
6971

70-
_options = optionsBuilder.Options;
72+
_options = options;
7173
Endpoint = _options.Endpoint;
72-
_flurlClient = GetConfiguredClient();
74+
_flurlClientCache = GetConfiguredClientCache();
75+
_flurlClient = () => _flurlClientCache.Get(DefaultHttpClientName)
76+
.WithSettings(s =>
77+
{
78+
_options.JsonSerializerOptions ??= new JsonSerializerOptions();
79+
_options.JsonSerializerOptions.PropertyNamingPolicy ??= JsonNamingPolicy.CamelCase;
80+
81+
// TODO: Check type resolver
82+
_options.JsonSerializerOptions.TypeInfoResolver =
83+
new CouchJsonTypeInfoResolver(_options.DatabaseSplitDiscriminator);
84+
s.JsonSerializer = new DefaultJsonSerializer(_options.JsonSerializerOptions);
85+
86+
// TODO: Check authorization
87+
// s.HttpClientFactory = new CertClientFactory(_options.ServerCertificateCustomValidationCallback);
88+
});
7389
}
7490

75-
internal CouchClient(CouchOptions options)
91+
private static CouchOptions BuildOptions(Action<CouchOptionsBuilder>? couchSettingsFunc, Uri? endpoint = null)
7692
{
77-
if (options.Endpoint == null)
93+
var optionsBuilder = new CouchOptionsBuilder();
94+
couchSettingsFunc?.Invoke(optionsBuilder);
95+
96+
if (endpoint != null)
97+
{
98+
optionsBuilder.Options.Endpoint = endpoint;
99+
}
100+
101+
if (optionsBuilder.Options.Endpoint == null)
78102
{
79103
throw new InvalidOperationException("Database endpoint must be set.");
80104
}
81105

82-
_options = options;
83-
Endpoint = _options.Endpoint;
84-
_flurlClient = GetConfiguredClient();
106+
return optionsBuilder.Options;
85107
}
86108

87-
private IFlurlClient GetConfiguredClient() =>
88-
new FlurlClient(Endpoint.AbsoluteUri).Configure(s =>
89-
{
90-
s.TypeInfoResolver = new CouchJsonTypeInfoResolver(_options.DatabaseSplitDiscriminator)
91-
s.BeforeCallAsync = OnBeforeCallAsync;
92-
if (_options.ServerCertificateCustomValidationCallback != null)
93-
{
94-
s.HttpClientFactory = new CertClientFactory(_options.ServerCertificateCustomValidationCallback);
95-
}
96-
});
109+
private FlurlClientCache GetConfiguredClientCache()
110+
{
111+
var cache = new FlurlClientCache();
112+
cache.Add(DefaultHttpClientName, _options.Endpoint!.AbsolutePath);
113+
return cache;
114+
}
97115

98116
#region Operations
99117

@@ -212,7 +230,8 @@ public Task<ICouchDatabase<TSource>> CreateDatabaseAsync<TSource>(int? shards =
212230
bool? partitioned = null, string? discriminator = null,
213231
CancellationToken cancellationToken = default) where TSource : CouchDocument
214232
{
215-
return CreateDatabaseAsync<TSource>(TypeExtensions.GetDatabaseName<TSource>(), shards, replicas, partitioned, discriminator,
233+
return CreateDatabaseAsync<TSource>(TypeExtensions.GetDatabaseName<TSource>(), shards, replicas, partitioned,
234+
discriminator,
216235
cancellationToken);
217236
}
218237

@@ -221,7 +240,8 @@ public Task<ICouchDatabase<TSource>> GetOrCreateDatabaseAsync<TSource>(int? shar
221240
bool? partitioned = null, string? discriminator = null,
222241
CancellationToken cancellationToken = default) where TSource : CouchDocument
223242
{
224-
return GetOrCreateDatabaseAsync<TSource>(TypeExtensions.GetDatabaseName<TSource>(), shards, replicas, partitioned, discriminator,
243+
return GetOrCreateDatabaseAsync<TSource>(TypeExtensions.GetDatabaseName<TSource>(), shards, replicas,
244+
partitioned, discriminator,
225245
cancellationToken);
226246
}
227247

@@ -418,7 +438,7 @@ public async Task<bool> RemoveReplicationAsync(string source, string target, Cou
418438

419439
private IFlurlRequest NewRequest()
420440
{
421-
return _flurlClient.Request(Endpoint);
441+
return _flurlClient().Request(Endpoint);
422442
}
423443

424444
private QueryContext NewQueryContext(string database)
@@ -457,7 +477,7 @@ protected virtual async Task DisposeAsync(bool disposing)
457477
await LogoutAsync().ConfigureAwait(false);
458478
}
459479

460-
_flurlClient.Dispose();
480+
_flurlClientCache.Clear();
461481
}
462482
}
463483

src/CouchDB.Driver/CouchClientAuthentication.cs

Lines changed: 14 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -10,15 +10,18 @@ namespace CouchDB.Driver;
1010

1111
public partial class CouchClient
1212
{
13+
// TODO: Use OnBeforeCallAsync from FlurlClientSettings when Flurl.Http 4.0 is released
1314
protected virtual async Task OnBeforeCallAsync(FlurlCall httpCall)
1415
{
1516
ArgumentNullException.ThrowIfNull(httpCall);
1617

1718
// If session requests no authorization needed
18-
if (httpCall.Request?.Url?.ToString()?.Contains("_session", StringComparison.InvariantCultureIgnoreCase) == true)
19+
if (httpCall.Request?.Url?.ToString()?.Contains("_session", StringComparison.InvariantCultureIgnoreCase) ==
20+
true)
1921
{
2022
return;
2123
}
24+
2225
switch (_options.AuthenticationType)
2326
{
2427
case AuthenticationType.None:
@@ -34,6 +37,7 @@ protected virtual async Task OnBeforeCallAsync(FlurlCall httpCall)
3437
{
3538
await LoginAsync().ConfigureAwait(false);
3639
}
40+
3741
httpCall.Request = httpCall.Request.WithCookie("AuthSession", _cookieToken);
3842
break;
3943
case AuthenticationType.Proxy:
@@ -43,29 +47,29 @@ protected virtual async Task OnBeforeCallAsync(FlurlCall httpCall)
4347
{
4448
httpCall.Request = httpCall.Request.WithHeader("X-Auth-CouchDB-Token", _options.Password);
4549
}
50+
4651
break;
4752
case AuthenticationType.Jwt:
4853
if (_options.JwtTokenGenerator == null)
4954
{
5055
throw new InvalidOperationException("JWT generation cannot be null.");
5156
}
57+
5258
var jwt = await _options.JwtTokenGenerator().ConfigureAwait(false);
5359
httpCall.Request = httpCall.Request.WithHeader("Authorization", jwt);
5460
break;
5561
default:
56-
throw new NotSupportedException($"Authentication of type {_options.AuthenticationType} is not supported.");
62+
throw new NotSupportedException(
63+
$"Authentication of type {_options.AuthenticationType} is not supported.");
5764
}
5865
}
5966

6067
private async Task LoginAsync()
6168
{
62-
IFlurlResponse response = await _flurlClient.Request(Endpoint)
69+
IFlurlResponse response = await _flurlClient()
70+
.Request(Endpoint)
6371
.AppendPathSegment("_session")
64-
.PostJsonAsync(new
65-
{
66-
name = _options.Username,
67-
password = _options.Password
68-
})
72+
.PostJsonAsync(new { name = _options.Username, password = _options.Password })
6973
.ConfigureAwait(false);
7074

7175
_cookieCreationDate = DateTimeOffset.UtcNow;
@@ -81,7 +85,8 @@ private async Task LoginAsync()
8185

8286
private async Task LogoutAsync()
8387
{
84-
OperationResult result = await _flurlClient.Request(Endpoint)
88+
OperationResult result = await _flurlClient()
89+
.Request(Endpoint)
8590
.AppendPathSegment("_session")
8691
.DeleteAsync()
8792
.ReceiveJson<OperationResult>()

src/CouchDB.Driver/CouchDB.Driver.csproj

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,6 @@
2727

2828
<ItemGroup>
2929
<PackageReference Include="Flurl.Http" Version="4.*"/>
30-
<PackageReference Include="Humanizer.Core" Version="2.*"/>
3130
</ItemGroup>
3231

3332
<ItemGroup>

src/CouchDB.Driver/CouchDatabase.cs

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@ public partial class CouchDatabase<TSource> : ICouchDatabase<TSource>
3939
{
4040
private readonly Regex _feedChangeLineStartPattern;
4141
private readonly IAsyncQueryProvider _queryProvider;
42-
private readonly IFlurlClient _flurlClient;
42+
private readonly Func<IFlurlClient> _flurlClient;
4343
private readonly CouchOptions _options;
4444
private readonly QueryContext _queryContext;
4545
private readonly string? _discriminator;
@@ -53,7 +53,7 @@ public partial class CouchDatabase<TSource> : ICouchDatabase<TSource>
5353
/// <inheritdoc />
5454
public ILocalDocuments LocalDocuments { get; }
5555

56-
internal CouchDatabase(IFlurlClient flurlClient, CouchOptions options, QueryContext queryContext,
56+
internal CouchDatabase(Func<IFlurlClient> flurlClient, CouchOptions options, QueryContext queryContext,
5757
string? discriminator)
5858
{
5959
_feedChangeLineStartPattern = FeedChangeStartLinePattern();
@@ -878,7 +878,9 @@ public override string ToString()
878878
/// <inheritdoc />
879879
public IFlurlRequest NewRequest()
880880
{
881-
return _flurlClient.Request(_queryContext.Endpoint).AppendPathSegment(_queryContext.EscapedDatabaseName);
881+
return _flurlClient()
882+
.Request(_queryContext.Endpoint)
883+
.AppendPathSegment(_queryContext.EscapedDatabaseName);
882884
}
883885

884886
internal CouchQueryable<TSource> AsQueryable()

src/CouchDB.Driver/Local/LocalDocuments.cs

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@
1111
namespace CouchDB.Driver.Local;
1212

1313
/// <inheritdoc />
14-
public class LocalDocuments(IFlurlClient flurlClient, QueryContext queryContext) : ILocalDocuments
14+
public class LocalDocuments(Func<IFlurlClient> flurlClient, QueryContext queryContext) : ILocalDocuments
1515
{
1616
/// <inheritdoc />
1717
public async Task<IList<CouchDocumentInfo>> GetAsync(LocalDocumentsOptions? options = null,
@@ -92,6 +92,8 @@ private static string GetLocalId(string id)
9292

9393
private IFlurlRequest NewRequest()
9494
{
95-
return flurlClient.Request(queryContext.Endpoint).AppendPathSegment(queryContext.EscapedDatabaseName);
95+
return flurlClient()
96+
.Request(queryContext.Endpoint)
97+
.AppendPathSegment(queryContext.EscapedDatabaseName);
9698
}
9799
}

src/CouchDB.Driver/Query/QuerySender.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@
1111

1212
namespace CouchDB.Driver.Query;
1313

14-
internal class QuerySender(IFlurlClient client, QueryContext queryContext) : IQuerySender
14+
internal class QuerySender(Func<IFlurlClient> client, QueryContext queryContext) : IQuerySender
1515
{
1616
private static readonly MethodInfo GenericToListMethod
1717
= typeof(QuerySender).GetRuntimeMethods()
@@ -52,7 +52,7 @@ private async Task<CouchList<TItem>> ToListAsync<TItem>(string body, Cancellatio
5252

5353
private async Task<FindResult<TItem>> SendAsync<TItem>(string body, CancellationToken cancellationToken)
5454
{
55-
FindResult<TItem>? findResult = await client
55+
FindResult<TItem>? findResult = await client()
5656
.Request(queryContext.Endpoint)
5757
.AppendPathSegments(queryContext.DatabaseName, "_find")
5858
.WithHeader("Content-Type", "application/json")

0 commit comments

Comments
 (0)