重新设置单例httpclient的证书(Reconfiguring IHttpclientFactory?)
Re-set the certificate of singleton httpclient (Reconfiguring IHttpclientFactory?)
使用 C#、.NET Core 3.1
我通过 startup.cs
:
添加了一个单例 httpclient
services.AddHttpClient<IClientLogic, ClientLogicA>().ConfigurePrimaryHttpMessageHandler(() =>
{
var handler = new HttpClientHandler();
var cert= GetCertFromX();
handler.ClientCertificates.Add(cert);
return handler;
});
但是可以说,稍后在 ClientLogicA
class 中,我想更改证书,我该怎么做,更改是否会在以后使用 httpclient 单例时持续存在?
所以您要做的是修改 HttpClient
的证书,该证书由 IHttpClientFactory
生成。看起来 Microsoft 可能会在 .NET 5 中添加此类功能,但与此同时,我们现在需要想出一种方法来实现它。
此解决方案适用于命名 HttpClient
和类型化 HttpClient
对象。
所以这里的问题是创建命名或类型化 HttpClient
,其中绑定到 HttpClient
的证书集合可以随时更新。问题是我们只能为 HttpClient
设置一次创建参数。之后,IHttpClientFactory
一遍又一遍地重复使用这些设置。
那么,让我们先看看如何注入我们的服务:
Named HttpClient Injection Routine
services.AddTransient<IMyService, MyService>();
services.AddSingleton<ICertificateService, CertificateService>();
services.AddHttpClient("MyCertBasedClient").
ConfigurePrimaryHttpMessageHandler(sp =>
new CertBasedHttpClientHandler(
sp.GetRequiredService<ICertificateService>()));
Typed HttpClient Injection Routine
services.AddSingleton<ICertificateService, CertificateService>();
services.AddHttpClient<IMyService, MyService>().
ConfigurePrimaryHttpMessageHandler(sp =>
new CertBasedHttpClientHandler(
sp.GetRequiredService<ICertificateService>()));
我们将 ICertificateService
作为 单例 注入,它持有我们当前的证书并允许其他服务更改它。 IMyService
在使用 Named HttpClient
时手动注入,而在使用 Typed HttpClient
时,IMyService
将被自动注入。当 IHttpClientFactory
创建我们的 HttpClient
时,它将调用 lambda 并生成一个扩展的 HttpClientHandler
,它将我们的 ICertificateService
从我们的服务管道中作为构造函数参数.
下一部分是 ICertificateService
的来源。此服务使用“id”维护证书(这只是上次更新时间的时间戳)。
CertificateService.cs
public interface ICertificateService
{
void UpdateCurrentCertificate(X509Certificate cert);
X509Certificate GetCurrentCertificate(out long certId);
bool HasCertificateChanged(long certId);
}
public sealed class CertificateService : ICertificateService
{
private readonly object _certLock = new object();
private X509Certificate _currentCert;
private long _certId;
private readonly Stopwatch _stopwatch = new Stopwatch();
public CertificateService()
{
_stopwatch.Start();
}
public bool HasCertificateChanged(long certId)
{
lock(_certLock)
{
return certId != _certId;
}
}
public X509Certificate GetCurrentCertificate(out long certId)
{
lock(_certLock)
{
certId = _certId;
return _currentCert;
}
}
public void UpdateCurrentCertificate(X509Certificate cert)
{
lock(_certLock)
{
_currentCert = cert;
_certId = _stopwatch.ElapsedTicks;
}
}
}
最后一部分是实现自定义 HttpClientHandler
的 class。有了这个,我们可以连接到客户端发出的 all HTTP 请求。如果证书已更改,我们会在发出请求之前将其换掉。
CertBasedHttpClientHandler.cs
public sealed class CertBasedHttpClientHandler : HttpClientHandler
{
private readonly ICertificateService _certService;
private long _currentCertId;
public CertBasedHttpClientHandler(ICertificateService certificateService)
{
_certService = certificateService;
var cert = _certService.GetCurrentCertificate(out _currentCertId);
if(cert != null)
{
ClientCertificates.Add(cert);
}
}
protected override Task<HttpResponseMessage> SendAsync(HttpRequestMessage request,
CancellationToken cancellationToken)
{
if(_certService.HasCertificateChanged(_currentCertId))
{
ClientCertificates.Clear();
var cert = _certService.GetCurrentCertificate(out _currentCertId);
if(cert != null)
{
ClientCertificates.Add(cert);
}
}
return base.SendAsync(request, cancellationToken);
}
}
现在我认为最大的 down-side 是如果 HttpClient
正处于另一个线程的请求中间,我们可以 运行 进入竞争条件。您可以通过使用 SemaphoreSlim
或任何其他异步线程同步模式保护 SendAsync
中的代码来缓解这种情况,但这可能会导致 bottle-neck,所以我没有费心这样做。如果你想看到添加,我会更新这个答案。
使用 C#、.NET Core 3.1
我通过 startup.cs
:
httpclient
services.AddHttpClient<IClientLogic, ClientLogicA>().ConfigurePrimaryHttpMessageHandler(() =>
{
var handler = new HttpClientHandler();
var cert= GetCertFromX();
handler.ClientCertificates.Add(cert);
return handler;
});
但是可以说,稍后在 ClientLogicA
class 中,我想更改证书,我该怎么做,更改是否会在以后使用 httpclient 单例时持续存在?
所以您要做的是修改 HttpClient
的证书,该证书由 IHttpClientFactory
生成。看起来 Microsoft 可能会在 .NET 5 中添加此类功能,但与此同时,我们现在需要想出一种方法来实现它。
此解决方案适用于命名 HttpClient
和类型化 HttpClient
对象。
所以这里的问题是创建命名或类型化 HttpClient
,其中绑定到 HttpClient
的证书集合可以随时更新。问题是我们只能为 HttpClient
设置一次创建参数。之后,IHttpClientFactory
一遍又一遍地重复使用这些设置。
那么,让我们先看看如何注入我们的服务:
Named HttpClient Injection Routine
services.AddTransient<IMyService, MyService>();
services.AddSingleton<ICertificateService, CertificateService>();
services.AddHttpClient("MyCertBasedClient").
ConfigurePrimaryHttpMessageHandler(sp =>
new CertBasedHttpClientHandler(
sp.GetRequiredService<ICertificateService>()));
Typed HttpClient Injection Routine
services.AddSingleton<ICertificateService, CertificateService>();
services.AddHttpClient<IMyService, MyService>().
ConfigurePrimaryHttpMessageHandler(sp =>
new CertBasedHttpClientHandler(
sp.GetRequiredService<ICertificateService>()));
我们将 ICertificateService
作为 单例 注入,它持有我们当前的证书并允许其他服务更改它。 IMyService
在使用 Named HttpClient
时手动注入,而在使用 Typed HttpClient
时,IMyService
将被自动注入。当 IHttpClientFactory
创建我们的 HttpClient
时,它将调用 lambda 并生成一个扩展的 HttpClientHandler
,它将我们的 ICertificateService
从我们的服务管道中作为构造函数参数.
下一部分是 ICertificateService
的来源。此服务使用“id”维护证书(这只是上次更新时间的时间戳)。
CertificateService.cs
public interface ICertificateService
{
void UpdateCurrentCertificate(X509Certificate cert);
X509Certificate GetCurrentCertificate(out long certId);
bool HasCertificateChanged(long certId);
}
public sealed class CertificateService : ICertificateService
{
private readonly object _certLock = new object();
private X509Certificate _currentCert;
private long _certId;
private readonly Stopwatch _stopwatch = new Stopwatch();
public CertificateService()
{
_stopwatch.Start();
}
public bool HasCertificateChanged(long certId)
{
lock(_certLock)
{
return certId != _certId;
}
}
public X509Certificate GetCurrentCertificate(out long certId)
{
lock(_certLock)
{
certId = _certId;
return _currentCert;
}
}
public void UpdateCurrentCertificate(X509Certificate cert)
{
lock(_certLock)
{
_currentCert = cert;
_certId = _stopwatch.ElapsedTicks;
}
}
}
最后一部分是实现自定义 HttpClientHandler
的 class。有了这个,我们可以连接到客户端发出的 all HTTP 请求。如果证书已更改,我们会在发出请求之前将其换掉。
CertBasedHttpClientHandler.cs
public sealed class CertBasedHttpClientHandler : HttpClientHandler
{
private readonly ICertificateService _certService;
private long _currentCertId;
public CertBasedHttpClientHandler(ICertificateService certificateService)
{
_certService = certificateService;
var cert = _certService.GetCurrentCertificate(out _currentCertId);
if(cert != null)
{
ClientCertificates.Add(cert);
}
}
protected override Task<HttpResponseMessage> SendAsync(HttpRequestMessage request,
CancellationToken cancellationToken)
{
if(_certService.HasCertificateChanged(_currentCertId))
{
ClientCertificates.Clear();
var cert = _certService.GetCurrentCertificate(out _currentCertId);
if(cert != null)
{
ClientCertificates.Add(cert);
}
}
return base.SendAsync(request, cancellationToken);
}
}
现在我认为最大的 down-side 是如果 HttpClient
正处于另一个线程的请求中间,我们可以 运行 进入竞争条件。您可以通过使用 SemaphoreSlim
或任何其他异步线程同步模式保护 SendAsync
中的代码来缓解这种情况,但这可能会导致 bottle-neck,所以我没有费心这样做。如果你想看到添加,我会更新这个答案。