IHttpClientFactory用于创建HttpClient和IHttpMessageHandlerFactory用于创建HttpMessageHandler。Microsoft.Extensions.Http的需求的产物,其它核心接口定义如下:
/// <summary>
/// Http消息处理者工厂
/// </summary>
public interface IHttpMessageHandlerFactory
{
    /// <summary>
    /// 创建用于请求的HttpMessageHandler
    /// </summary>
    /// <param name="name">别名</param>
    /// <param name="proxyUri">支持携带UserInfo的代理地址</param> 
    /// <returns></returns>
    HttpMessageHandler CreateHandler(string name, Uri? proxyUri);
}
当然HttpMessageHandlerFactory也提供了和Microsoft.Extensions.Http相似的Builder能力,在使用服务注册时没有额外的学习成本。
2.1、可选的ProxyUri参数
接口多了一个可选的Uri参数,有值的时候,代表要使用这个参数做代理。别小看这个Uri参数,它是可继承的类型,传入Uri的子类型还可以实现很多意想不到的骚操作。
2.2、支持创建HttpClient
IHttpMessageHandlerFactory提供创建HttpClient的扩展,用于做客户端模式,且支持传入与用户实例绑定的CookieContainer,然后Cookie就完全自动化处理。
/// <summary>
/// 创建Http客户端
/// </summary>
/// <param name="factory"></param>
/// <param name="name">别名</param>
/// <param name="proxyUri">支持携带UserInfo的代理地址</param>
/// <param name="cookieContainer">cookie容器</param>
/// <returns></returns>
public static HttpClient CreateClient(this IHttpMessageHandlerFactory factory, string name, Uri? proxyUri = null, CookieContainer? cookieContainer = null)
{
    var httpHandler = factory.CreateHandler(name, proxyUri, cookieContainer);
    return new HttpClient(httpHandler, disposeHandler: false);
}
2.3、支持创建HttpMessageInvoker
IHttpMessageHandlerFactory提供创建HttpMessageInvoker的扩展,用于转发端模式,且支持传入与用户实例绑定的CookieContainer,然后Cookie就完全自动化处理。
/// <summary>
/// 创建Http执行器
/// </summary>
/// <param name="factory"></param>
/// <param name="name">别名</param>
/// <param name="proxyUri">支持携带UserInfo的代理地址</param>
/// <param name="cookieContainer">cookie容器</param>
/// <returns></returns>
public static HttpMessageInvoker CreateInvoker(this IHttpMessageHandlerFactory factory, string name, Uri? proxyUri = null, CookieContainer? cookieContainer = null)
{
    var httpHandler = factory.CreateHandler(name, proxyUri, cookieContainer);
    return new HttpMessageInvoker(httpHandler, disposeHandler: false);
}
三、生态与扩展
如果说HttpMessageHandlerFactory只解决了Microsoft.Extensions.Http的Proxy痛点,但丢了Microsoft.Extensions.Http的生态又不能扩展的话,那无疑HttpMessageHandlerFactory是非常局限和失败的。
实际上Microsoft.Extensions.Http上层的很多组件,移植到HttpMessageHandlerFactory是非常简单的,简单说是DI注册扩展的IHttpClientBuilder改为IHttpMessageHandlerBuilder就行。
3.1、HttpMessageHandlerFactory.Polly
为HttpMessageHandlerFactory提供Polly策略扩展,使得IHttpMessageHandlerBuilder拥有与IHttpClientFactory完全一致的Polly能力。
3.1.1 AddPolicyHandler能力
var retryPolicy = Policy.Handle<HttpRequestException>()
    .OrResult<HttpResponseMessage>(response =>
    {
        return response.IsSuccessStatusCode == false;
    }).WaitAndRetryAsync(3, t => TimeSpan.FromSeconds(3d));
 services
    .AddHttpMessageHandlerFactory("App")
    .AddPolicyHandler(retryPolicy);    
3.1.2 AddPolicyHandlerFromRegistry能力
var retryPolicy = Policy.Handle<HttpRequestException>()
    .OrResult<HttpResponseMessage>(response =>
    {
        return response.IsSuccessStatusCode == false;
    }).WaitAndRetryAsync(3, t => TimeSpan.FromSeconds(3d));
var registry = services.AddPolicyRegistry();
registry.Add("registry1", retryPolicy);
services
    .AddHttpMessageHandlerFactory("App")
    .AddPolicyHandlerFromRegistry("registry1");    
3.1.3 AddTransientHttpErrorPolicy能力
当以下任意条件成立时,触发TransientHttpErrorPolicy
- 
HttpRequestException的网络故障  - 
服务端响应5XX的状态码  - 
408的状态码(request timeout)  
services
    .AddHttpMessageHandlerFactory("App")
    .AddTransientHttpErrorPolicy(builder => builder.WaitAndRetryAsync(new[] {
        TimeSpan.FromSeconds(1d),
        TimeSpan.FromSeconds(5d),
        TimeSpan.FromSeconds(10d)
    }));  
3.2、HttpMessageHandlerFactory.Connection
为HttpMessageHandlerFactory提供自定义连接的功能。注意此扩展项目不是免费项目,有如下限制:
- 
不开放和提供源代码  - 
nuget包的程序集在应用程序运行2分钟后适用期结束  - 
适用期结束后所有的http请求响应为423 Locked  - 
需要license文件授权方可完全使用  
3.2.1 自定义域名解析
- 
当无代理连接时,连接到自定义解析得到的IP  - 
当使用http代理时,让代理服务器连接到自定义解析得到的IP  - 
当使用socks代理时,让代理服务器连接到自定义解析得到的IP  
services
    .AddHttpMessageHandlerFactory("App")
    .AddHostResolver<CustomHostResolver>();
sealed class CustomHostResolver : HostResolver
{
    public override ValueTask<HostPort> ResolveAsync(DnsEndPoint endpoint, CancellationToken cancellationToken)
    {
        if (endpoint.Host == "www.baidu.com")
        {
            return ValueTask.FromResult(new HostPort("14.119.104.189", endpoint.Port));
        }
        return ValueTask.FromResult(new HostPort(endpoint.Host, endpoint.Port));
    }
}
3.2.2 自定义ssl的sni
Server Name Indication (SNI) 是 TLS 协议(以前称为 SSL 协议)的扩展,该协议在 HTTPS 中使用。它包含在 TLS/SSL 握手流程中,以确保客户端设备能够看到他们尝试访问的网站的正确 SSL 证书。
该扩展使得可以在 TLS 握手期间指定网站的主机名或域名 ,而不是在握手之后打开 HTTP 连接时指定。
services
    .AddHttpMessageHandlerFactory("App")
    .AddSslSniProvider<CustomSslSniProvider>();
sealed class CustomSslSniProvider : SslSniProvider
{
    public override ValueTask<string> GetSslSniAsync(string host, CancellationToken cancellationToken)
    {
        return ValueTask.FromResult(string.Empty);
    }
    public override bool RemoteCertificateValidationCallback(string host, X509Certificate? cert, X509Chain? chain, SslPolicyErrors errors)
    {
        return true;
    }
}
四、两个库的场景选择
| 库 | 无/固定代理 | 动态代理 | 客户端 | 转发端 | 
|---|---|---|---|---|
| Microsoft.Extensions.Http | 适合 | 不适合 | 非常适合 | 功能弱 | 
| HttpMessageHandlerFactory | 适合 | 适合 | 功能弱 | 非常适合 | 
HttpMessageHandlerFactory的源代码在https://github.com/xljiulang/HttpMessageHandlerFactory,其功能单一代码量相对Microsoft.Extensions.Http要少一些,阅读其代码之后去理解Microsoft.Extensions.Http会更容易很多。