因此,您要做的是修改由 aHttpClient
生成的 a 的证书IHttpClientFactory
。看起来微软可能会在 .NET 5 中添加这种类型的功能,但与此同时,我们现在需要想出一种方法来实现它。
此解决方案适用于命名对象HttpClient
和类型HttpClient
对象。
所以这里的问题是创建 Named 或 TypedHttpClient
绑定到的证书集合HttpClient
可以随时更新。问题是我们只能设置HttpClient
一次创建参数。之后,会IHttpClientFactory
一遍又一遍地重复使用这些设置。
所以,让我们先看看我们是如何注入服务的:
命名 HttpClient 注入例程
services.AddTransient<IMyService, MyService>();
services.AddSingleton<ICertificateService, CertificateService>();
services.AddHttpClient("MyCertBasedClient").
ConfigurePrimaryHttpMessageHandler(sp =>
new CertBasedHttpClientHandler(
sp.GetRequiredService<ICertificateService>()));
类型化 HttpClient 注入例程
services.AddSingleton<ICertificateService, CertificateService>();
services.AddHttpClient<IMyService, MyService>().
ConfigurePrimaryHttpMessageHandler(sp =>
new CertBasedHttpClientHandler(
sp.GetRequiredService<ICertificateService>()));
我们注入一个ICertificateService
as单例,它持有我们当前的证书并允许其他服务更改它。IMyService
使用 Named 时手动注入HttpClient
,而使用 TypedHttpClient
时IMyService
将自动注入。当IHttpClientFactory
需要创建 ourHttpClient
时,它将调用 lambda 并生成一个扩展HttpClientHandler
,它将我们ICertificateService
的服务管道中的我们作为构造函数参数。
下一部分是ICertificateService
. 该服务使用“id”维护证书(这只是上次更新时间的时间戳)。
证书服务.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
。有了这个,我们可以连接到客户端发出的所有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);
}
}
现在我认为最大的缺点是如果HttpClient
在另一个线程的请求中间,我们可能会遇到竞争条件。SendAsync
您可以通过使用 a或任何其他异步线程同步模式保护代码来缓解这种情况SemaphoreSlim
,但这可能会导致瓶颈,所以我没有费心去做。如果您想看到添加的内容,我将更新此答案。