合并HttpClientHanlder

This commit is contained in:
xljiulang 2021-07-18 23:09:10 +08:00
parent 106b789c80
commit d50c35466b
7 changed files with 137 additions and 84 deletions

View File

@ -0,0 +1,40 @@
using System;
using System.Net.Http;
using System.Threading;
using System.Threading.Tasks;
namespace FastGithub.ReverseProxy
{
/// <summary>
/// YARP的HttpClient
/// </summary>
class HttpClient : HttpMessageInvoker
{
private readonly bool tlsSni;
/// <summary>
/// YARP的HttpClient
/// </summary>
/// <param name="handler"></param>
/// <param name="disposeHandler"></param>
/// <param name="tlsSni"></param>
public HttpClient(HttpMessageHandler handler, bool disposeHandler, bool tlsSni) :
base(handler, disposeHandler)
{
this.tlsSni = tlsSni;
}
/// <summary>
/// 发送数据
/// </summary>
/// <param name="request"></param>
/// <param name="cancellationToken"></param>
/// <returns></returns>
public override Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
{
var isHttps = request.RequestUri?.Scheme == Uri.UriSchemeHttps;
request.SetSniContext(new SniContext(isHttps, this.tlsSni));
return base.SendAsync(request, cancellationToken);
}
}
}

View File

@ -9,20 +9,20 @@ using System.Threading.Tasks;
namespace FastGithub.ReverseProxy
{
/// <summary>
/// 不发送NoSni的HttpClientHandler
/// YARP的HttpClientHandler
/// </summary>
class NoSniHttpClientHanlder : DelegatingHandler
class HttpClientHanlder : DelegatingHandler
{
private readonly DomainResolver domainResolver;
private readonly ILogger<NoSniHttpClientHanlder> logger;
private readonly ILogger<HttpClientHanlder> logger;
/// <summary>
/// 不发送NoSni的HttpClientHandler
/// YARP的HttpClientHandler
/// </summary>
/// <param name="domainResolver"></param>
public NoSniHttpClientHanlder(
public HttpClientHanlder(
DomainResolver domainResolver,
ILogger<NoSniHttpClientHanlder> logger)
ILogger<HttpClientHanlder> logger)
{
this.domainResolver = domainResolver;
this.logger = logger;
@ -45,7 +45,8 @@ namespace FastGithub.ReverseProxy
var socket = new Socket(SocketType.Stream, ProtocolType.Tcp);
await socket.ConnectAsync(ctx.DnsEndPoint, ct);
var stream = new NetworkStream(socket, ownsSocket: true);
if (ctx.InitialRequestMessage.Headers.Host == null)
var sniContext = ctx.InitialRequestMessage.GetSniContext();
if (sniContext.IsHttps == false)
{
return stream;
}
@ -53,7 +54,7 @@ namespace FastGithub.ReverseProxy
var sslStream = new SslStream(stream, leaveInnerStreamOpen: false);
await sslStream.AuthenticateAsClientAsync(new SslClientAuthenticationOptions
{
TargetHost = string.Empty,
TargetHost = sniContext.TlsSniValue,
RemoteCertificateValidationCallback = delegate { return true; }
}, ct);
return sslStream;
@ -74,16 +75,25 @@ namespace FastGithub.ReverseProxy
if (uri != null && uri.HostNameType == UriHostNameType.Dns)
{
var address = await this.domainResolver.ResolveAsync(uri.Host, cancellationToken);
this.logger.LogInformation($"[{address}--NoSni->{uri.Host}]");
var builder = new UriBuilder(uri)
{
Scheme = Uri.UriSchemeHttp,
Host = address.ToString(),
Port = 443
};
request.RequestUri = builder.Uri;
request.Headers.Host = uri.Host;
// 计算Sni
var context = request.GetSniContext();
if (context.IsHttps && context.TlsSni)
{
context.TlsSniValue = uri.Host;
this.logger.LogInformation($"[{address}--Sni->{uri.Host}]");
}
else
{
this.logger.LogInformation($"[{address}--NoSni->{uri.Host}]");
}
}
return await base.SendAsync(request, cancellationToken);
}

View File

@ -1,7 +1,6 @@
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Logging;
using System;
using System.Net.Http;
using System.Threading.Tasks;
using Yarp.ReverseProxy.Forwarder;
@ -13,21 +12,18 @@ namespace FastGithub.ReverseProxy
sealed class ReverseProxyMiddleware
{
private readonly IHttpForwarder httpForwarder;
private readonly SniHttpClientHanlder sniHttpClientHanlder;
private readonly NoSniHttpClientHanlder noSniHttpClientHanlder;
private readonly HttpClientHanlder httpClientHanlder;
private readonly FastGithubConfig fastGithubConfig;
private readonly ILogger<ReverseProxyMiddleware> logger;
public ReverseProxyMiddleware(
IHttpForwarder httpForwarder,
SniHttpClientHanlder sniHttpClientHanlder,
NoSniHttpClientHanlder noSniHttpClientHanlder,
HttpClientHanlder httpClientHanlder,
FastGithubConfig fastGithubConfig,
ILogger<ReverseProxyMiddleware> logger)
{
this.httpForwarder = httpForwarder;
this.sniHttpClientHanlder = sniHttpClientHanlder;
this.noSniHttpClientHanlder = noSniHttpClientHanlder;
this.httpClientHanlder = httpClientHanlder;
this.fastGithubConfig = fastGithubConfig;
this.logger = logger;
}
@ -59,10 +55,7 @@ namespace FastGithub.ReverseProxy
{
var destinationPrefix = GetDestinationPrefix(host, domainConfig.Destination);
var requestConfig = new ForwarderRequestConfig { Timeout = domainConfig.Timeout };
var httpClient = domainConfig.TlsSni
? new HttpMessageInvoker(this.sniHttpClientHanlder, disposeHandler: false)
: new HttpMessageInvoker(this.noSniHttpClientHanlder, disposeHandler: false);
var httpClient = new HttpClient(this.httpClientHanlder, false, domainConfig.TlsSni);
var error = await httpForwarder.SendAsync(context, destinationPrefix, httpClient, requestConfig);
await ResponseErrorAsync(context, error);

View File

@ -19,8 +19,7 @@ namespace FastGithub
.AddMemoryCache()
.AddHttpForwarder()
.AddSingleton<DomainResolver>()
.AddTransient<SniHttpClientHanlder>()
.AddTransient<NoSniHttpClientHanlder>()
.AddTransient<HttpClientHanlder>()
.AddSingleton<ReverseProxyMiddleware>();
}
}

View File

@ -0,0 +1,34 @@
namespace FastGithub.ReverseProxy
{
/// <summary>
/// Sni上下文
/// </summary>
sealed class SniContext
{
/// <summary>
/// 获取请求是否为https
/// </summary>
public bool IsHttps { get; }
/// <summary>
/// 获取是否发送Sni
/// </summary>
public bool TlsSni { get; }
/// <summary>
/// Sni值
/// </summary>
public string TlsSniValue { get; set; } = string.Empty;
/// <summary>
/// Sni上下文
/// </summary>
/// <param name="isHttps"></param>
/// <param name="tlsSni"></param>
public SniContext(bool isHttps, bool tlsSni)
{
this.IsHttps = isHttps;
this.TlsSni = tlsSni;
}
}
}

View File

@ -0,0 +1,37 @@
using System;
using System.Net.Http;
namespace FastGithub.ReverseProxy
{
/// <summary>
/// SniContext扩展
/// </summary>
static class SniContextExtensions
{
private static readonly HttpRequestOptionsKey<SniContext> key = new(nameof(SniContext));
/// <summary>
/// 设置SniContext
/// </summary>
/// <param name="httpRequestMessage"></param>
/// <param name="context"></param>
public static void SetSniContext(this HttpRequestMessage httpRequestMessage, SniContext context)
{
httpRequestMessage.Options.Set(key, context);
}
/// <summary>
/// 获取SniContext
/// </summary>
/// <param name="httpRequestMessage"></param>
/// <returns></returns>
public static SniContext GetSniContext(this HttpRequestMessage httpRequestMessage)
{
if (httpRequestMessage.Options.TryGetValue(key, out var value))
{
return value;
}
throw new InvalidOperationException($"请先调用{nameof(SetSniContext)}");
}
}
}

View File

@ -1,60 +0,0 @@
using Microsoft.Extensions.Logging;
using System;
using System.Net.Http;
using System.Threading;
using System.Threading.Tasks;
namespace FastGithub.ReverseProxy
{
/// <summary>
/// 携带Sni的的HttpClientHandler
/// </summary>
class SniHttpClientHanlder : DelegatingHandler
{
private readonly DomainResolver domainResolver;
private readonly ILogger<SniHttpClientHanlder> logger;
/// <summary>
/// 携带Sni的HttpClientHandler
/// </summary>
/// <param name="domainResolver"></param>
public SniHttpClientHanlder(
DomainResolver domainResolver,
ILogger<SniHttpClientHanlder> logger)
{
this.domainResolver = domainResolver;
this.logger = logger;
this.InnerHandler = new SocketsHttpHandler
{
Proxy = null,
UseProxy = false,
AllowAutoRedirect = 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 && uri.HostNameType == UriHostNameType.Dns)
{
var address = await this.domainResolver.ResolveAsync(uri.Host, cancellationToken);
this.logger.LogInformation($"[{address}--Sni->{uri.Host}]");
var builder = new UriBuilder(uri)
{
Host = address.ToString()
};
request.RequestUri = builder.Uri;
request.Headers.Host = uri.Host;
}
return await base.SendAsync(request, cancellationToken);
}
}
}