重构HttpClientFactory
This commit is contained in:
parent
fab33b4fad
commit
4f3578dc9e
@ -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);
|
||||
|
||||
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -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; }
|
||||
}
|
||||
}
|
||||
|
||||
@ -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": {
|
||||
|
||||
Loading…
Reference in New Issue
Block a user