重构HttpClientFactory

This commit is contained in:
陈国伟 2021-08-11 16:29:05 +08:00
parent fab33b4fad
commit 4f3578dc9e
6 changed files with 82 additions and 102 deletions

View File

@ -87,7 +87,7 @@ namespace FastGithub.DomainResolve
private async Task<IPAddress> LookupCoreAsync(IPEndPoint dns, string domain, CancellationToken cancellationToken)
{
var dnsClient = new DnsClient(dns);
using var timeoutTokenSource = new CancellationTokenSource(TimeSpan.FromSeconds(1d));
using var timeoutTokenSource = new CancellationTokenSource(TimeSpan.FromSeconds(2d));
using var linkedTokenSource = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken, timeoutTokenSource.Token);
var addresses = await dnsClient.Lookup(domain, RecordType.A, linkedTokenSource.Token);

View File

@ -1,9 +1,6 @@
using FastGithub.Configuration;
using FastGithub.DomainResolve;
using System;
using System.Net.Http;
using System.Threading;
using System.Threading.Tasks;
namespace FastGithub.Http
{
@ -12,51 +9,24 @@ namespace FastGithub.Http
/// </summary>
public class HttpClient : HttpMessageInvoker
{
private readonly DomainConfig domainConfig;
private readonly TimeSpan defaltTimeout = TimeSpan.FromMinutes(2d);
/// <summary>
/// http客户端
/// </summary>
/// <param name="domainConfig"></param>
/// <param name="domainResolver"></param>
public HttpClient(DomainConfig domainConfig, IDomainResolver domainResolver)
: this(domainConfig, new HttpClientHandler(domainResolver), disposeHandler: true)
: this(new HttpClientHandler(domainConfig, domainResolver), disposeHandler: true)
{
}
/// <summary>
/// http客户端
/// </summary>
/// <param name="domainConfig"></param>
/// </summary>
/// <param name="handler"></param>
/// <param name="disposeHandler"></param>
internal HttpClient(DomainConfig domainConfig, HttpClientHandler handler, bool disposeHandler)
internal HttpClient(HttpClientHandler handler, bool disposeHandler)
: base(handler, disposeHandler)
{
this.domainConfig = domainConfig;
}
/// <summary>
/// 发送数据
/// </summary>
/// <param name="request"></param>
/// <param name="cancellationToken"></param>
/// <returns></returns>
public override Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
{
request.SetRequestContext(new RequestContext
{
Host = request.RequestUri?.Host,
IPAddress = this.domainConfig.IPAddress,
IsHttps = request.RequestUri?.Scheme == Uri.UriSchemeHttps,
TlsSniPattern = this.domainConfig.GetTlsSniPattern(),
TlsIgnoreNameMismatch = this.domainConfig.TlsIgnoreNameMismatch
});
using var timeoutTokenSource = new CancellationTokenSource(this.domainConfig.Timeout ?? defaltTimeout);
using var linkedTokenSource = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken, timeoutTokenSource.Token);
return base.SendAsync(request, linkedTokenSource.Token);
}
}
}

View File

@ -1,6 +1,7 @@
using FastGithub.Configuration;
using FastGithub.DomainResolve;
using Microsoft.Extensions.Options;
using System.Collections.Concurrent;
namespace FastGithub.Http
{
@ -9,7 +10,8 @@ namespace FastGithub.Http
/// </summary>
sealed class HttpClientFactory : IHttpClientFactory
{
private HttpClientHandler httpClientHanlder;
private readonly IDomainResolver domainResolver;
private ConcurrentDictionary<DomainConfig, HttpClientHandler> domainHandlers = new();
/// <summary>
/// HttpClient工厂
@ -20,8 +22,8 @@ namespace FastGithub.Http
IDomainResolver domainResolver,
IOptionsMonitor<FastGithubOptions> options)
{
this.httpClientHanlder = new HttpClientHandler(domainResolver);
options.OnChange(opt => this.httpClientHanlder = new HttpClientHandler(domainResolver));
this.domainResolver = domainResolver;
options.OnChange(opt => this.domainHandlers = new());
}
/// <summary>
@ -31,7 +33,8 @@ namespace FastGithub.Http
/// <returns></returns>
public HttpClient CreateHttpClient(DomainConfig domainConfig)
{
return new HttpClient(domainConfig, this.httpClientHanlder, disposeHandler: false);
var httpClientHandler = this.domainHandlers.GetOrAdd(domainConfig, config => new HttpClientHandler(config, this.domainResolver));
return new HttpClient(httpClientHandler, disposeHandler: false);
}
}
}

View File

@ -1,4 +1,5 @@
using FastGithub.DomainResolve;
using FastGithub.Configuration;
using FastGithub.DomainResolve;
using System;
using System.Collections;
using System.Collections.Generic;
@ -18,23 +19,74 @@ namespace FastGithub.Http
/// </summary>
class HttpClientHandler : DelegatingHandler
{
private readonly DomainConfig domainConfig;
private readonly IDomainResolver domainResolver;
private readonly TimeSpan defaltTimeout = TimeSpan.FromMinutes(2d);
/// <summary>
/// HttpClientHandler
/// </summary>
/// <param name="domainResolver"></param>
public HttpClientHandler(IDomainResolver domainResolver)
/// <param name="domainConfig"></param>
/// <param name="domainResolver"></param>
public HttpClientHandler(DomainConfig domainConfig, IDomainResolver domainResolver)
{
this.domainResolver = domainResolver;
this.InnerHandler = CreateSocketsHttpHandler();
this.domainConfig = domainConfig;
this.InnerHandler = this.CreateSocketsHttpHandler();
}
/// <summary>
/// 替换域名为ip
/// </summary>
/// <param name="request"></param>
/// <param name="cancellationToken"></param>
/// <returns></returns>
protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
{
var uri = request.RequestUri;
if (uri == null)
{
throw new FastGithubException("必须指定请求的URI");
}
// 请求上下文信息
var context = new RequestContext
{
Domain = uri.Host,
IsHttps = uri.Scheme == Uri.UriSchemeHttps,
TlsSniPattern = this.domainConfig.GetTlsSniPattern().WithDomain(uri.Host).WithRandom()
};
request.SetRequestContext(context);
// 解析ip替换https为http
var uriBuilder = new UriBuilder(uri)
{
Scheme = Uri.UriSchemeHttp
};
if (uri.HostNameType == UriHostNameType.Dns)
{
if (IPAddress.TryParse(this.domainConfig.IPAddress, out var address) == false)
{
address = await this.domainResolver.ResolveAsync(context.Domain, cancellationToken);
}
uriBuilder.Host = address.ToString();
request.Headers.Host = context.Domain;
context.TlsSniPattern = context.TlsSniPattern.WithIPAddress(address);
}
request.RequestUri = uriBuilder.Uri;
using var timeoutTokenSource = new CancellationTokenSource(this.domainConfig.Timeout ?? defaltTimeout);
using var linkedTokenSource = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken, timeoutTokenSource.Token);
return await base.SendAsync(request, cancellationToken);
}
/// <summary>
/// 创建转发代理的httpHandler
/// </summary>
/// <returns></returns>
private static SocketsHttpHandler CreateSocketsHttpHandler()
private SocketsHttpHandler CreateSocketsHttpHandler()
{
return new SocketsHttpHandler
{
@ -68,14 +120,14 @@ namespace FastGithub.Http
{
if (errors == SslPolicyErrors.RemoteCertificateNameMismatch)
{
if (requestContext.TlsIgnoreNameMismatch == true)
if (this.domainConfig.TlsIgnoreNameMismatch == true)
{
return true;
}
var host = requestContext.Host;
var domain = requestContext.Domain;
var dnsNames = ReadDnsNames(cert);
return dnsNames.Any(dns => IsMatch(dns, host));
return dnsNames.Any(dns => IsMatch(dns, domain));
}
return errors == SslPolicyErrors.None;
@ -119,59 +171,23 @@ namespace FastGithub.Http
/// 比较域名
/// </summary>
/// <param name="dnsName"></param>
/// <param name="host"></param>
/// <param name="domain"></param>
/// <returns></returns>
private static bool IsMatch(string dnsName, string? host)
private static bool IsMatch(string dnsName, string? domain)
{
if (host == null)
if (domain == null)
{
return false;
}
if (dnsName == host)
if (dnsName == domain)
{
return true;
}
if (dnsName[0] == '*')
{
return host.EndsWith(dnsName[1..]);
return domain.EndsWith(dnsName[1..]);
}
return false;
}
/// <summary>
/// 替换域名为ip
/// </summary>
/// <param name="request"></param>
/// <param name="cancellationToken"></param>
/// <returns></returns>
protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
{
var uri = request.RequestUri;
if (uri != null)
{
var uriBuilder = new UriBuilder(uri)
{
Scheme = Uri.UriSchemeHttp
};
var domain = uri.Host;
var context = request.GetRequestContext();
context.TlsSniPattern = context.TlsSniPattern.WithDomain(domain).WithRandom();
if (uri.HostNameType == UriHostNameType.Dns)
{
if (IPAddress.TryParse(context.IPAddress, out var address) == false)
{
address = await this.domainResolver.ResolveAsync(domain, cancellationToken);
}
uriBuilder.Host = address.ToString();
request.Headers.Host = domain;
context.TlsSniPattern = context.TlsSniPattern.WithIPAddress(address);
}
request.RequestUri = uriBuilder.Uri;
}
return await base.SendAsync(request, cancellationToken);
}
}
}

View File

@ -1,5 +1,4 @@
using FastGithub.Configuration;
using System.Net;
namespace FastGithub.Http
{
@ -14,23 +13,13 @@ namespace FastGithub.Http
public bool IsHttps { get; set; }
/// <summary>
/// 请求的主机
/// 请求的域名
/// </summary>
public string? Host { get; set; }
/// <summary>
/// 请求的ip
/// </summary>
public string? IPAddress { get; set; }
public string? Domain { get; set; }
/// <summary>
/// 获取或设置Sni值的表达式
/// </summary>
public TlsSniPattern TlsSniPattern { get; set; }
/// <summary>
/// 是否忽略服务器证书域名不匹配
/// </summary>
public bool TlsIgnoreNameMismatch { get; set; }
}
}

View File

@ -2,10 +2,12 @@
"FastGithub": {
"DomainConfigs": {
"i.imgur.com": {
"IPAddress": "146.75.8.193"
"IPAddress": "146.75.8.193",
"TlsIgnoreNameMismatch": true
},
"i.*.imgur.com": {
"IPAddress": "146.75.8.193"
"IPAddress": "146.75.8.193",
"TlsIgnoreNameMismatch": true
},
"lh*.googleusercontent.com": {
"Response": {