2

使用 C#、.NET Core 3.1

httpclient我通过 in添加一个单例startup.cs

services.AddHttpClient<IClientLogic, ClientLogicA>().ConfigurePrimaryHttpMessageHandler(() =>
{
   var handler = new HttpClientHandler();
   
   var cert= GetCertFromX();

   handler.ClientCertificates.Add(cert);

   return handler;
});

但是可以说,稍后在ClientLogicA课堂上,我想更改证书,我该怎么做,并且更改是否会持续用于 httpclient 单例的未来使用?

4

2 回答 2

1

因此,您要做的是修改由 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>()));

我们注入一个ICertificateServiceas单例,它持有我们当前的证书并允许其他服务更改它。IMyService使用 Named 时手动注入HttpClient,而使用 TypedHttpClientIMyService将自动注入。当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,但这可能会导致瓶颈,所以我没有费心去做。如果您想看到添加的内容,我将更新此答案。

于 2020-08-18T18:44:25.577 回答
-1

我远非 ASP.NET 专家,但在阅读您的代码时我没有看到单例。

我看到每次都在创建一个新客户端并添加一个证书。

如果您想使用该代码,您需要更改 GetCertFromX 返回的内容。

于 2020-08-18T17:15:59.873 回答