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

View File

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

View File

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