分离和复用HttpClient

This commit is contained in:
xljiulang 2021-07-24 04:08:29 +08:00
parent ec06b11d7e
commit 82a816e40d
20 changed files with 232 additions and 146 deletions

View File

@ -8,12 +8,12 @@ using System.Net;
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
namespace FastGithub.ReverseProxy namespace FastGithub.Http
{ {
/// <summary> /// <summary>
/// 域名解析器 /// 域名解析器
/// </summary> /// </summary>
sealed class DomainResolver sealed class DomainResolver : IDomainResolver
{ {
private readonly IMemoryCache memoryCache; private readonly IMemoryCache memoryCache;
private readonly FastGithubConfig fastGithubConfig; private readonly FastGithubConfig fastGithubConfig;

View File

@ -0,0 +1,14 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net5.0</TargetFramework>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="DNS" Version="6.1.0" />
<PackageReference Include="Portable.BouncyCastle" Version="1.8.10" />
<PackageReference Include="Microsoft.Extensions.Caching.Memory" Version="5.0.0" />
<ProjectReference Include="..\FastGithub.Core\FastGithub.Core.csproj" />
</ItemGroup>
</Project>

View File

@ -0,0 +1,56 @@
using System;
using System.Net.Http;
using System.Threading;
using System.Threading.Tasks;
namespace FastGithub.Http
{
/// <summary>
/// 表示http客户端
/// </summary>
public class HttpClient : HttpMessageInvoker
{
private readonly DomainConfig domainConfig;
/// <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.domainConfig = domainConfig;
}
/// <summary>
/// http客户端
/// </summary>
/// <param name="domainConfig"></param>
/// <param name="handler"></param>
/// <param name="disposeHandler"></param>
internal HttpClient(DomainConfig domainConfig, 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,
IsHttps = request.RequestUri?.Scheme == Uri.UriSchemeHttps,
TlsSniPattern = this.domainConfig.GetTlsSniPattern(),
TlsIgnoreNameMismatch = this.domainConfig.TlsIgnoreNameMismatch
});
return base.SendAsync(request, cancellationToken);
}
}
}

View File

@ -0,0 +1,35 @@
using Microsoft.Extensions.Options;
namespace FastGithub.Http
{
/// <summary>
/// HttpClient工厂
/// </summary>
sealed class HttpClientFactory : IHttpClientFactory
{
private HttpClientHandler httpClientHanlder;
/// <summary>
/// HttpClient工厂
/// </summary>
/// <param name="domainResolver"></param>
/// <param name="options"></param>
public HttpClientFactory(
IDomainResolver domainResolver,
IOptionsMonitor<FastGithubOptions> options)
{
this.httpClientHanlder = new HttpClientHandler(domainResolver);
options.OnChange(opt => this.httpClientHanlder = new HttpClientHandler(domainResolver));
}
/// <summary>
/// 创建httpClient
/// </summary>
/// <param name="domainConfig"></param>
/// <returns></returns>
public HttpClient CreateHttpClient(DomainConfig domainConfig)
{
return new HttpClient(domainConfig, this.httpClientHanlder, disposeHandler: false);
}
}
}

View File

@ -9,20 +9,20 @@ using System.Security.Cryptography.X509Certificates;
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
namespace FastGithub.ReverseProxy namespace FastGithub.Http
{ {
/// <summary> /// <summary>
/// YARP的HttpClientHandler /// HttpClientHandler
/// </summary> /// </summary>
class HttpClientHanlder : DelegatingHandler class HttpClientHandler : DelegatingHandler
{ {
private readonly DomainResolver domainResolver; private readonly IDomainResolver domainResolver;
/// <summary> /// <summary>
/// YARP的HttpClientHandler /// HttpClientHandler
/// </summary> /// </summary>
/// <param name="domainResolver"></param> /// <param name="domainResolver"></param>
public HttpClientHanlder(DomainResolver domainResolver) public HttpClientHandler(IDomainResolver domainResolver)
{ {
this.domainResolver = domainResolver; this.domainResolver = domainResolver;
this.InnerHandler = CreateSocketsHttpHandler(); this.InnerHandler = CreateSocketsHttpHandler();

View File

@ -0,0 +1,25 @@
using FastGithub.Http;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.DependencyInjection.Extensions;
namespace FastGithub
{
/// <summary>
/// httpClient的服务注册扩展
/// </summary>
public static class HttpClientServiceCollectionExtensions
{
/// <summary>
/// 添加HttpClient相关服务
/// </summary>
/// <param name="services"></param>
/// <returns></returns>
public static IServiceCollection AddHttpClient(this IServiceCollection services)
{
services.AddMemoryCache();
services.TryAddSingleton<IDomainResolver, DomainResolver>();
services.TryAddSingleton<IHttpClientFactory, HttpClientFactory>();
return services;
}
}
}

View File

@ -0,0 +1,20 @@
using System.Net;
using System.Threading;
using System.Threading.Tasks;
namespace FastGithub.Http
{
/// <summary>
/// 域名解析器
/// </summary>
public interface IDomainResolver
{
/// <summary>
/// 解析域名
/// </summary>
/// <param name="domain"></param>
/// <param name="cancellationToken"></param>
/// <returns></returns>
Task<IPAddress> ResolveAsync(string domain, CancellationToken cancellationToken);
}
}

View File

@ -0,0 +1,15 @@
namespace FastGithub.Http
{
/// <summary>
/// httpClient工厂
/// </summary>
public interface IHttpClientFactory
{
/// <summary>
/// 创建httpClient
/// </summary>
/// <param name="domainConfig"></param>
/// <returns></returns>
HttpClient CreateHttpClient(DomainConfig domainConfig);
}
}

View File

@ -1,4 +1,4 @@
namespace FastGithub.ReverseProxy namespace FastGithub.Http
{ {
/// <summary> /// <summary>
/// 表示请求上下文 /// 表示请求上下文

View File

@ -1,7 +1,7 @@
using System; using System;
using System.Net.Http; using System.Net.Http;
namespace FastGithub.ReverseProxy namespace FastGithub.Http
{ {
/// <summary> /// <summary>
/// 请求上下文扩展 /// 请求上下文扩展

View File

@ -6,13 +6,13 @@
<ItemGroup> <ItemGroup>
<FrameworkReference Include="Microsoft.AspNetCore.App" /> <FrameworkReference Include="Microsoft.AspNetCore.App" />
<PackageReference Include="DNS" Version="6.1.0" />
<PackageReference Include="Portable.BouncyCastle" Version="1.8.10" /> <PackageReference Include="Portable.BouncyCastle" Version="1.8.10" />
<PackageReference Include="Yarp.ReverseProxy" Version="1.0.0-preview.12.21328.2" /> <PackageReference Include="Yarp.ReverseProxy" Version="1.0.0-preview.12.21328.2" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<ProjectReference Include="..\FastGithub.Core\FastGithub.Core.csproj" /> <ProjectReference Include="..\FastGithub.Core\FastGithub.Core.csproj" />
<ProjectReference Include="..\FastGithub.Http\FastGithub.Http.csproj" />
</ItemGroup> </ItemGroup>
</Project> </Project>

View File

@ -1,73 +0,0 @@
using Microsoft.Extensions.Options;
using System;
using System.Net.Http;
using System.Threading;
using System.Threading.Tasks;
namespace FastGithub.ReverseProxy
{
/// <summary>
/// HttpClient工厂
/// </summary>
sealed class HttpClientFactory
{
private HttpClientHanlder httpClientHanlder;
/// <summary>
/// HttpClient工厂
/// </summary>
/// <param name="domainResolver"></param>
/// <param name="options"></param>
public HttpClientFactory(
DomainResolver domainResolver,
IOptionsMonitor<FastGithubOptions> options)
{
this.httpClientHanlder = new HttpClientHanlder(domainResolver);
options.OnChange(opt => this.httpClientHanlder = new HttpClientHanlder(domainResolver));
}
/// <summary>
/// 创建httpClient
/// </summary>
/// <param name="domainConfig"></param>
/// <returns></returns>
public HttpMessageInvoker CreateHttpClient(DomainConfig domainConfig)
{
return new HttpClient(this.httpClientHanlder, domainConfig, disposeHandler: false);
}
/// <summary>
/// http客户端
/// </summary>
private class HttpClient : HttpMessageInvoker
{
private readonly DomainConfig domainConfig;
public HttpClient(
HttpMessageHandler handler,
DomainConfig domainConfig,
bool disposeHandler = false) : 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,
IsHttps = request.RequestUri?.Scheme == Uri.UriSchemeHttps,
TlsSniPattern = this.domainConfig.GetTlsSniPattern(),
TlsIgnoreNameMismatch = this.domainConfig.TlsIgnoreNameMismatch
});
return base.SendAsync(request, cancellationToken);
}
}
}
}

View File

@ -1,4 +1,5 @@
using Microsoft.AspNetCore.Http; using FastGithub.Http;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
using System; using System;
using System.Threading.Tasks; using System.Threading.Tasks;
@ -12,13 +13,13 @@ namespace FastGithub.ReverseProxy
sealed class ReverseProxyMiddleware sealed class ReverseProxyMiddleware
{ {
private readonly IHttpForwarder httpForwarder; private readonly IHttpForwarder httpForwarder;
private readonly HttpClientFactory httpClientFactory; private readonly IHttpClientFactory httpClientFactory;
private readonly FastGithubConfig fastGithubConfig; private readonly FastGithubConfig fastGithubConfig;
private readonly ILogger<ReverseProxyMiddleware> logger; private readonly ILogger<ReverseProxyMiddleware> logger;
public ReverseProxyMiddleware( public ReverseProxyMiddleware(
IHttpForwarder httpForwarder, IHttpForwarder httpForwarder,
HttpClientFactory httpClientFactory, IHttpClientFactory httpClientFactory,
FastGithubConfig fastGithubConfig, FastGithubConfig fastGithubConfig,
ILogger<ReverseProxyMiddleware> logger) ILogger<ReverseProxyMiddleware> logger)
{ {

View File

@ -18,8 +18,7 @@ namespace FastGithub
return services return services
.AddMemoryCache() .AddMemoryCache()
.AddHttpForwarder() .AddHttpForwarder()
.AddSingleton<DomainResolver>() .AddHttpClient()
.AddTransient<HttpClientFactory>()
.AddSingleton<RequestLoggingMilldeware>() .AddSingleton<RequestLoggingMilldeware>()
.AddSingleton<ReverseProxyMiddleware>(); .AddSingleton<ReverseProxyMiddleware>();
} }

View File

@ -10,6 +10,7 @@
<ItemGroup> <ItemGroup>
<ProjectReference Include="..\FastGithub.Core\FastGithub.Core.csproj" /> <ProjectReference Include="..\FastGithub.Core\FastGithub.Core.csproj" />
<ProjectReference Include="..\FastGithub.Http\FastGithub.Http.csproj" />
</ItemGroup> </ItemGroup>
</Project> </Project>

View File

@ -0,0 +1,17 @@
using System.Net.Http;
using System.Net.Http.Headers;
namespace FastGithub.Upgrade
{
/// <summary>
/// github请求消息
/// </summary>
class GithubRequestMessage : HttpRequestMessage
{
public GithubRequestMessage()
{
this.Headers.Accept.Add(new MediaTypeWithQualityHeaderValue("*/*"));
this.Headers.UserAgent.Add(new ProductInfoHeaderValue(nameof(FastGithub), "1.0"));
}
}
}

View File

@ -1,46 +0,0 @@
using System;
using System.Net;
using System.Net.Http;
using System.Threading;
using System.Threading.Tasks;
namespace FastGithub.Upgrade
{
/// <summary>
/// 本机反向代理的httpHandler
/// </summary>
sealed class ReverseProxyHttpHandler : DelegatingHandler
{
/// <summary>
/// 本机反向代理的httpHandler
/// </summary>
public ReverseProxyHttpHandler()
{
this.InnerHandler = new HttpClientHandler
{
ServerCertificateCustomValidationCallback = delegate { return true; }
};
}
/// <summary>
/// 替换为Loopback
/// </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 && uri.HostNameType == UriHostNameType.Dns)
{
var domain = uri.Host;
var builder = new UriBuilder(uri) { Host = IPAddress.Loopback.ToString() };
request.RequestUri = builder.Uri;
request.Headers.Host = domain;
}
return await base.SendAsync(request, cancellationToken);
}
}
}

View File

@ -1,8 +1,7 @@
using Microsoft.Extensions.Logging; using FastGithub.Http;
using Microsoft.Extensions.Logging;
using System; using System;
using System.Linq; using System.Linq;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Net.Http.Json; using System.Net.Http.Json;
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
@ -14,15 +13,20 @@ namespace FastGithub.Upgrade
/// </summary> /// </summary>
sealed class UpgradeService sealed class UpgradeService
{ {
private readonly IDomainResolver domainResolver;
private readonly ILogger<UpgradeService> logger; private readonly ILogger<UpgradeService> logger;
private const string ReleasesUri = "https://api.github.com/repos/xljiulang/fastgithub/releases"; private readonly Uri releasesUri = new("https://api.github.com/repos/xljiulang/fastgithub/releases");
/// <summary> /// <summary>
/// 升级服务 /// 升级服务
/// </summary> /// </summary>
/// <param name="domainResolver"></param>
/// <param name="logger"></param> /// <param name="logger"></param>
public UpgradeService(ILogger<UpgradeService> logger) public UpgradeService(
IDomainResolver domainResolver,
ILogger<UpgradeService> logger)
{ {
this.domainResolver = domainResolver;
this.logger = logger; this.logger = logger;
} }
@ -48,9 +52,13 @@ namespace FastGithub.Upgrade
var lastedVersion = lastRelease.GetProductionVersion(); var lastedVersion = lastRelease.GetProductionVersion();
if (lastedVersion.CompareTo(currentVersion) > 0) if (lastedVersion.CompareTo(currentVersion) > 0)
{ {
this.logger.LogInformation($"您正在使用{currentVersion}版本{Environment.NewLine}请前往{lastRelease.HtmlUrl}下载新版本"); this.logger.LogInformation($"当前版本{currentVersion}为老版本{Environment.NewLine}请前往{lastRelease.HtmlUrl}下载新版本");
this.logger.LogInformation(lastRelease.ToString()); this.logger.LogInformation(lastRelease.ToString());
} }
else
{
this.logger.LogInformation($"当前版本{currentVersion}为最新版本");
}
} }
/// <summary> /// <summary>
@ -59,14 +67,21 @@ namespace FastGithub.Upgrade
/// <returns></returns> /// <returns></returns>
public async Task<GithubRelease?> GetLastedReleaseAsync(CancellationToken cancellationToken) public async Task<GithubRelease?> GetLastedReleaseAsync(CancellationToken cancellationToken)
{ {
using var httpClient = new HttpClient(new ReverseProxyHttpHandler()) var domainConfig = new DomainConfig
{ {
Timeout = TimeSpan.FromSeconds(30d), TlsSni = false,
TlsIgnoreNameMismatch = true,
Timeout = TimeSpan.FromSeconds(30d)
}; };
httpClient.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("*/*"));
httpClient.DefaultRequestHeaders.UserAgent.Add(new ProductInfoHeaderValue(nameof(FastGithub), "1.0"));
var releases = await httpClient.GetFromJsonAsync<GithubRelease[]>(ReleasesUri, cancellationToken); using var request = new GithubRequestMessage
{
RequestUri = this.releasesUri
};
using var httpClient = new HttpClient(domainConfig, this.domainResolver);
var response = await httpClient.SendAsync(request, cancellationToken);
var releases = await response.Content.ReadFromJsonAsync<GithubRelease[]>(cancellationToken: cancellationToken);
return releases?.FirstOrDefault(item => item.Prerelease == false); return releases?.FirstOrDefault(item => item.Prerelease == false);
} }
} }

View File

@ -16,6 +16,7 @@ namespace FastGithub
public static IServiceCollection AddAppUpgrade(this IServiceCollection services) public static IServiceCollection AddAppUpgrade(this IServiceCollection services)
{ {
return services return services
.AddHttpClient()
.AddSingleton<UpgradeService>() .AddSingleton<UpgradeService>()
.AddHostedService<UpgradeHostedService>(); .AddHostedService<UpgradeHostedService>();
} }

View File

@ -15,6 +15,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "FastGithub.ReverseProxy", "
EndProject EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "FastGithub.DnscryptProxy", "FastGithub.DnscryptProxy\FastGithub.DnscryptProxy.csproj", "{26BB826F-4117-4746-9BD0-C6D8043E2808}" Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "FastGithub.DnscryptProxy", "FastGithub.DnscryptProxy\FastGithub.DnscryptProxy.csproj", "{26BB826F-4117-4746-9BD0-C6D8043E2808}"
EndProject EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "FastGithub.Http", "FastGithub.Http\FastGithub.Http.csproj", "{B5DCB3E4-5094-4170-B844-6F395002CA42}"
EndProject
Global Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU Debug|Any CPU = Debug|Any CPU
@ -45,6 +47,10 @@ Global
{26BB826F-4117-4746-9BD0-C6D8043E2808}.Debug|Any CPU.Build.0 = Debug|Any CPU {26BB826F-4117-4746-9BD0-C6D8043E2808}.Debug|Any CPU.Build.0 = Debug|Any CPU
{26BB826F-4117-4746-9BD0-C6D8043E2808}.Release|Any CPU.ActiveCfg = Release|Any CPU {26BB826F-4117-4746-9BD0-C6D8043E2808}.Release|Any CPU.ActiveCfg = Release|Any CPU
{26BB826F-4117-4746-9BD0-C6D8043E2808}.Release|Any CPU.Build.0 = Release|Any CPU {26BB826F-4117-4746-9BD0-C6D8043E2808}.Release|Any CPU.Build.0 = Release|Any CPU
{B5DCB3E4-5094-4170-B844-6F395002CA42}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{B5DCB3E4-5094-4170-B844-6F395002CA42}.Debug|Any CPU.Build.0 = Debug|Any CPU
{B5DCB3E4-5094-4170-B844-6F395002CA42}.Release|Any CPU.ActiveCfg = Release|Any CPU
{B5DCB3E4-5094-4170-B844-6F395002CA42}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection EndGlobalSection
GlobalSection(SolutionProperties) = preSolution GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE HideSolutionNode = FALSE