重构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) private async Task<IPAddress> LookupCoreAsync(IPEndPoint dns, string domain, CancellationToken cancellationToken)
{ {
var dnsClient = new DnsClient(dns); 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); using var linkedTokenSource = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken, timeoutTokenSource.Token);
var addresses = await dnsClient.Lookup(domain, RecordType.A, linkedTokenSource.Token); var addresses = await dnsClient.Lookup(domain, RecordType.A, linkedTokenSource.Token);

View File

@ -1,9 +1,6 @@
using FastGithub.Configuration; using FastGithub.Configuration;
using FastGithub.DomainResolve; using FastGithub.DomainResolve;
using System;
using System.Net.Http; using System.Net.Http;
using System.Threading;
using System.Threading.Tasks;
namespace FastGithub.Http namespace FastGithub.Http
{ {
@ -12,51 +9,24 @@ namespace FastGithub.Http
/// </summary> /// </summary>
public class HttpClient : HttpMessageInvoker public class HttpClient : HttpMessageInvoker
{ {
private readonly DomainConfig domainConfig;
private readonly TimeSpan defaltTimeout = TimeSpan.FromMinutes(2d);
/// <summary> /// <summary>
/// http客户端 /// http客户端
/// </summary> /// </summary>
/// <param name="domainConfig"></param> /// <param name="domainConfig"></param>
/// <param name="domainResolver"></param> /// <param name="domainResolver"></param>
public HttpClient(DomainConfig domainConfig, IDomainResolver domainResolver) public HttpClient(DomainConfig domainConfig, IDomainResolver domainResolver)
: this(domainConfig, new HttpClientHandler(domainResolver), disposeHandler: true) : this(new HttpClientHandler(domainConfig, domainResolver), disposeHandler: true)
{ {
} }
/// <summary> /// <summary>
/// http客户端 /// http客户端
/// </summary> /// </summary>
/// <param name="domainConfig"></param>
/// <param name="handler"></param> /// <param name="handler"></param>
/// <param name="disposeHandler"></param> /// <param name="disposeHandler"></param>
internal HttpClient(DomainConfig domainConfig, HttpClientHandler handler, bool disposeHandler) internal HttpClient(HttpClientHandler handler, bool disposeHandler)
: base(handler, 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.Configuration;
using FastGithub.DomainResolve; using FastGithub.DomainResolve;
using Microsoft.Extensions.Options; using Microsoft.Extensions.Options;
using System.Collections.Concurrent;
namespace FastGithub.Http namespace FastGithub.Http
{ {
@ -9,7 +10,8 @@ namespace FastGithub.Http
/// </summary> /// </summary>
sealed class HttpClientFactory : IHttpClientFactory sealed class HttpClientFactory : IHttpClientFactory
{ {
private HttpClientHandler httpClientHanlder; private readonly IDomainResolver domainResolver;
private ConcurrentDictionary<DomainConfig, HttpClientHandler> domainHandlers = new();
/// <summary> /// <summary>
/// HttpClient工厂 /// HttpClient工厂
@ -20,8 +22,8 @@ namespace FastGithub.Http
IDomainResolver domainResolver, IDomainResolver domainResolver,
IOptionsMonitor<FastGithubOptions> options) IOptionsMonitor<FastGithubOptions> options)
{ {
this.httpClientHanlder = new HttpClientHandler(domainResolver); this.domainResolver = domainResolver;
options.OnChange(opt => this.httpClientHanlder = new HttpClientHandler(domainResolver)); options.OnChange(opt => this.domainHandlers = new());
} }
/// <summary> /// <summary>
@ -31,7 +33,8 @@ namespace FastGithub.Http
/// <returns></returns> /// <returns></returns>
public HttpClient CreateHttpClient(DomainConfig domainConfig) 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;
using System.Collections; using System.Collections;
using System.Collections.Generic; using System.Collections.Generic;
@ -18,23 +19,74 @@ namespace FastGithub.Http
/// </summary> /// </summary>
class HttpClientHandler : DelegatingHandler class HttpClientHandler : DelegatingHandler
{ {
private readonly DomainConfig domainConfig;
private readonly IDomainResolver domainResolver; private readonly IDomainResolver domainResolver;
private readonly TimeSpan defaltTimeout = TimeSpan.FromMinutes(2d);
/// <summary> /// <summary>
/// HttpClientHandler /// HttpClientHandler
/// </summary> /// </summary>
/// <param name="domainResolver"></param> /// <param name="domainConfig"></param>
public HttpClientHandler(IDomainResolver domainResolver) /// <param name="domainResolver"></param>
public HttpClientHandler(DomainConfig domainConfig, IDomainResolver domainResolver)
{ {
this.domainResolver = 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> /// <summary>
/// 创建转发代理的httpHandler /// 创建转发代理的httpHandler
/// </summary> /// </summary>
/// <returns></returns> /// <returns></returns>
private static SocketsHttpHandler CreateSocketsHttpHandler() private SocketsHttpHandler CreateSocketsHttpHandler()
{ {
return new SocketsHttpHandler return new SocketsHttpHandler
{ {
@ -68,14 +120,14 @@ namespace FastGithub.Http
{ {
if (errors == SslPolicyErrors.RemoteCertificateNameMismatch) if (errors == SslPolicyErrors.RemoteCertificateNameMismatch)
{ {
if (requestContext.TlsIgnoreNameMismatch == true) if (this.domainConfig.TlsIgnoreNameMismatch == true)
{ {
return true; return true;
} }
var host = requestContext.Host; var domain = requestContext.Domain;
var dnsNames = ReadDnsNames(cert); var dnsNames = ReadDnsNames(cert);
return dnsNames.Any(dns => IsMatch(dns, host)); return dnsNames.Any(dns => IsMatch(dns, domain));
} }
return errors == SslPolicyErrors.None; return errors == SslPolicyErrors.None;
@ -119,59 +171,23 @@ namespace FastGithub.Http
/// 比较域名 /// 比较域名
/// </summary> /// </summary>
/// <param name="dnsName"></param> /// <param name="dnsName"></param>
/// <param name="host"></param> /// <param name="domain"></param>
/// <returns></returns> /// <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; return false;
} }
if (dnsName == host) if (dnsName == domain)
{ {
return true; return true;
} }
if (dnsName[0] == '*') if (dnsName[0] == '*')
{ {
return host.EndsWith(dnsName[1..]); return domain.EndsWith(dnsName[1..]);
} }
return false; 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 FastGithub.Configuration;
using System.Net;
namespace FastGithub.Http namespace FastGithub.Http
{ {
@ -14,23 +13,13 @@ namespace FastGithub.Http
public bool IsHttps { get; set; } public bool IsHttps { get; set; }
/// <summary> /// <summary>
/// 请求的主机 /// 请求的域名
/// </summary> /// </summary>
public string? Host { get; set; } public string? Domain { get; set; }
/// <summary>
/// 请求的ip
/// </summary>
public string? IPAddress { get; set; }
/// <summary> /// <summary>
/// 获取或设置Sni值的表达式 /// 获取或设置Sni值的表达式
/// </summary> /// </summary>
public TlsSniPattern TlsSniPattern { get; set; } public TlsSniPattern TlsSniPattern { get; set; }
/// <summary>
/// 是否忽略服务器证书域名不匹配
/// </summary>
public bool TlsIgnoreNameMismatch { get; set; }
} }
} }

View File

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