kestrel层实现http代理
This commit is contained in:
parent
27c2bf27bd
commit
58f79ddc19
@ -1,4 +1,4 @@
|
||||
using FastGithub.HttpServer;
|
||||
using FastGithub.HttpServer.HttpMiddlewares;
|
||||
using Microsoft.AspNetCore.Builder;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
|
||||
@ -10,14 +10,14 @@ namespace FastGithub
|
||||
public static class ApplicationBuilderExtensions
|
||||
{
|
||||
/// <summary>
|
||||
/// 使用http代理中间件
|
||||
/// 使用http代理策略中间件
|
||||
/// </summary>
|
||||
/// <param name="app"></param>
|
||||
/// <returns></returns>
|
||||
public static IApplicationBuilder UseHttpProxy(this IApplicationBuilder app)
|
||||
public static IApplicationBuilder UseHttpProxyPac(this IApplicationBuilder app)
|
||||
{
|
||||
var middleware = app.ApplicationServices.GetRequiredService<HttpProxyMiddleware>();
|
||||
return app.Use(next => context => middleware.InvokeAsync(context));
|
||||
var middleware = app.ApplicationServices.GetRequiredService<HttpProxyPacMiddleware>();
|
||||
return app.Use(next => context => middleware.InvokeAsync(context, next));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
||||
@ -1,11 +1,12 @@
|
||||
using Microsoft.Extensions.Logging;
|
||||
using FastGithub;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using System;
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace FastGithub.HttpServer
|
||||
namespace FastGithub.HttpServer.Certs.CaCertInstallers
|
||||
{
|
||||
abstract class CaCertInstallerOfLinux : ICaCertInstaller
|
||||
{
|
||||
@ -35,7 +36,7 @@ namespace FastGithub.HttpServer
|
||||
/// <returns></returns>
|
||||
public bool IsSupported()
|
||||
{
|
||||
return OperatingSystem.IsLinux() && File.Exists(this.CaCertUpdatePath);
|
||||
return OperatingSystem.IsLinux() && File.Exists(CaCertUpdatePath);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@ -44,7 +45,7 @@ namespace FastGithub.HttpServer
|
||||
/// <param name="caCertFilePath">证书文件路径</param>
|
||||
public void Install(string caCertFilePath)
|
||||
{
|
||||
var destCertFilePath = Path.Combine(this.CaCertStorePath, Path.GetFileName(caCertFilePath));
|
||||
var destCertFilePath = Path.Combine(CaCertStorePath, Path.GetFileName(caCertFilePath));
|
||||
if (File.Exists(destCertFilePath) && File.ReadAllBytes(caCertFilePath).SequenceEqual(File.ReadAllBytes(destCertFilePath)))
|
||||
{
|
||||
return;
|
||||
@ -52,25 +53,25 @@ namespace FastGithub.HttpServer
|
||||
|
||||
if (geteuid() != 0)
|
||||
{
|
||||
this.logger.LogWarning($"无法自动安装CA证书{caCertFilePath}:没有root权限");
|
||||
logger.LogWarning($"无法自动安装CA证书{caCertFilePath}:没有root权限");
|
||||
return;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
Directory.CreateDirectory(this.CaCertStorePath);
|
||||
foreach (var item in Directory.GetFiles(this.CaCertStorePath, "fastgithub.*"))
|
||||
Directory.CreateDirectory(CaCertStorePath);
|
||||
foreach (var item in Directory.GetFiles(CaCertStorePath, "fastgithub.*"))
|
||||
{
|
||||
File.Delete(item);
|
||||
}
|
||||
File.Copy(caCertFilePath, destCertFilePath, overwrite: true);
|
||||
Process.Start(this.CaCertUpdatePath).WaitForExit();
|
||||
this.logger.LogInformation($"已自动向系统安装CA证书{caCertFilePath}");
|
||||
Process.Start(CaCertUpdatePath).WaitForExit();
|
||||
logger.LogInformation($"已自动向系统安装CA证书{caCertFilePath}");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
File.Delete(destCertFilePath);
|
||||
this.logger.LogWarning(ex.Message, "自动安装CA证书异常");
|
||||
logger.LogWarning(ex.Message, "自动安装CA证书异常");
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1,6 +1,6 @@
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
namespace FastGithub.HttpServer
|
||||
namespace FastGithub.HttpServer.Certs.CaCertInstallers
|
||||
{
|
||||
sealed class CaCertInstallerOfLinuxDebian : CaCertInstallerOfLinux
|
||||
{
|
||||
@ -1,6 +1,6 @@
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
namespace FastGithub.HttpServer
|
||||
namespace FastGithub.HttpServer.Certs.CaCertInstallers
|
||||
{
|
||||
sealed class CaCertInstallerOfLinuxRedHat : CaCertInstallerOfLinux
|
||||
{
|
||||
@ -1,7 +1,7 @@
|
||||
using Microsoft.Extensions.Logging;
|
||||
using System;
|
||||
|
||||
namespace FastGithub.HttpServer
|
||||
namespace FastGithub.HttpServer.Certs.CaCertInstallers
|
||||
{
|
||||
sealed class CaCertInstallerOfMacOS : ICaCertInstaller
|
||||
{
|
||||
@ -27,7 +27,7 @@ namespace FastGithub.HttpServer
|
||||
/// <param name="caCertFilePath">证书文件路径</param>
|
||||
public void Install(string caCertFilePath)
|
||||
{
|
||||
this.logger.LogWarning($"请手动安装CA证书然后设置信任CA证书{caCertFilePath}");
|
||||
logger.LogWarning($"请手动安装CA证书然后设置信任CA证书{caCertFilePath}");
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -2,7 +2,7 @@
|
||||
using System;
|
||||
using System.Security.Cryptography.X509Certificates;
|
||||
|
||||
namespace FastGithub.HttpServer
|
||||
namespace FastGithub.HttpServer.Certs.CaCertInstallers
|
||||
{
|
||||
sealed class CaCertInstallerOfWindows : ICaCertInstaller
|
||||
{
|
||||
@ -50,7 +50,7 @@ namespace FastGithub.HttpServer
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
this.logger.LogWarning($"请手动安装CA证书{caCertFilePath}到“将所有的证书都放入下列存储”\\“受信任的根证书颁发机构”");
|
||||
logger.LogWarning($"请手动安装CA证书{caCertFilePath}到“将所有的证书都放入下列存储”\\“受信任的根证书颁发机构”");
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -19,7 +19,7 @@ using System.Net;
|
||||
using System.Text;
|
||||
using X509Certificate2 = System.Security.Cryptography.X509Certificates.X509Certificate2;
|
||||
|
||||
namespace FastGithub.HttpServer
|
||||
namespace FastGithub.HttpServer.Certs
|
||||
{
|
||||
/// <summary>
|
||||
/// 证书生成器
|
||||
@ -8,7 +8,7 @@ using System.Linq;
|
||||
using System.Net;
|
||||
using System.Security.Cryptography.X509Certificates;
|
||||
|
||||
namespace FastGithub.HttpServer
|
||||
namespace FastGithub.HttpServer.Certs
|
||||
{
|
||||
/// <summary>
|
||||
/// 证书服务
|
||||
@ -54,17 +54,17 @@ namespace FastGithub.HttpServer
|
||||
/// </summary>
|
||||
public bool CreateCaCertIfNotExists()
|
||||
{
|
||||
if (File.Exists(this.CaCerFilePath) && File.Exists(this.CaKeyFilePath))
|
||||
if (File.Exists(CaCerFilePath) && File.Exists(CaKeyFilePath))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
File.Delete(this.CaCerFilePath);
|
||||
File.Delete(this.CaKeyFilePath);
|
||||
File.Delete(CaCerFilePath);
|
||||
File.Delete(CaKeyFilePath);
|
||||
|
||||
var validFrom = DateTime.Today.AddDays(-1);
|
||||
var validTo = DateTime.Today.AddYears(10);
|
||||
CertGenerator.GenerateBySelf(new[] { nameof(FastGithub) }, KEY_SIZE_BITS, validFrom, validTo, this.CaCerFilePath, this.CaKeyFilePath);
|
||||
CertGenerator.GenerateBySelf(new[] { nameof(FastGithub) }, KEY_SIZE_BITS, validFrom, validTo, CaCerFilePath, CaKeyFilePath);
|
||||
return true;
|
||||
}
|
||||
|
||||
@ -73,14 +73,14 @@ namespace FastGithub.HttpServer
|
||||
/// </summary>
|
||||
public void InstallAndTrustCaCert()
|
||||
{
|
||||
var installer = this.certInstallers.FirstOrDefault(item => item.IsSupported());
|
||||
var installer = certInstallers.FirstOrDefault(item => item.IsSupported());
|
||||
if (installer != null)
|
||||
{
|
||||
installer.Install(this.CaCerFilePath);
|
||||
installer.Install(CaCerFilePath);
|
||||
}
|
||||
else
|
||||
{
|
||||
this.logger.LogWarning($"请根据你的系统平台手动安装和信任CA证书{this.CaCerFilePath}");
|
||||
logger.LogWarning($"请根据你的系统平台手动安装和信任CA证书{CaCerFilePath}");
|
||||
}
|
||||
|
||||
GitConfigSslverify(false);
|
||||
@ -119,7 +119,7 @@ namespace FastGithub.HttpServer
|
||||
public X509Certificate2 GetOrCreateServerCert(string? domain)
|
||||
{
|
||||
var key = $"{nameof(CertService)}:{domain}";
|
||||
return this.serverCertCache.GetOrCreate(key, GetOrCreateCert);
|
||||
return serverCertCache.GetOrCreate(key, GetOrCreateCert);
|
||||
|
||||
// 生成域名的1年证书
|
||||
X509Certificate2 GetOrCreateCert(ICacheEntry entry)
|
||||
@ -129,7 +129,7 @@ namespace FastGithub.HttpServer
|
||||
var validTo = DateTime.Today.AddYears(1);
|
||||
|
||||
entry.SetAbsoluteExpiration(validTo);
|
||||
return CertGenerator.GenerateByCa(domains, KEY_SIZE_BITS, validFrom, validTo, this.CaCerFilePath, this.CaKeyFilePath);
|
||||
return CertGenerator.GenerateByCa(domains, KEY_SIZE_BITS, validFrom, validTo, CaCerFilePath, CaKeyFilePath);
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
namespace FastGithub.HttpServer
|
||||
namespace FastGithub.HttpServer.Certs
|
||||
{
|
||||
/// <summary>
|
||||
/// CA证书安装器
|
||||
@ -7,7 +7,7 @@
|
||||
<ItemGroup>
|
||||
<FrameworkReference Include="Microsoft.AspNetCore.App" />
|
||||
<PackageReference Include="Portable.BouncyCastle" Version="1.9.0" />
|
||||
<PackageReference Include="Yarp.ReverseProxy" Version="1.0.0" />
|
||||
<PackageReference Include="Yarp.ReverseProxy" Version="1.1.0" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
||||
@ -0,0 +1,68 @@
|
||||
using FastGithub.Configuration;
|
||||
using FastGithub.HttpServer.TcpMiddlewares;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using System.IO;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace FastGithub.HttpServer.HttpMiddlewares
|
||||
{
|
||||
/// <summary>
|
||||
/// http代理策略中间件
|
||||
/// </summary>
|
||||
sealed class HttpProxyPacMiddleware
|
||||
{
|
||||
private readonly FastGithubConfig fastGithubConfig;
|
||||
|
||||
/// <summary>
|
||||
/// http代理策略中间件
|
||||
/// </summary>
|
||||
/// <param name="fastGithubConfig"></param>
|
||||
public HttpProxyPacMiddleware(FastGithubConfig fastGithubConfig)
|
||||
{
|
||||
this.fastGithubConfig = fastGithubConfig;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 处理请求
|
||||
/// </summary>
|
||||
/// <param name="context"></param>
|
||||
/// <param name="next"></param>
|
||||
/// <returns></returns>
|
||||
public async Task InvokeAsync(HttpContext context, RequestDelegate next)
|
||||
{
|
||||
// http请求经过了httpProxy中间件
|
||||
var proxyFeature = context.Features.Get<IHttpProxyFeature>();
|
||||
if (proxyFeature != null && proxyFeature.ProxyProtocol == ProxyProtocol.None)
|
||||
{
|
||||
var proxyPac = this.CreateProxyPac(context.Request.Host);
|
||||
context.Response.ContentType = "application/x-ns-proxy-autoconfig";
|
||||
context.Response.Headers.Add("Content-Disposition", $"attachment;filename=proxy.pac");
|
||||
await context.Response.WriteAsync(proxyPac);
|
||||
}
|
||||
else
|
||||
{
|
||||
await next(context);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 创建proxypac脚本
|
||||
/// </summary>
|
||||
/// <param name="proxyHost"></param>
|
||||
/// <returns></returns>
|
||||
private string CreateProxyPac(HostString proxyHost)
|
||||
{
|
||||
var buidler = new StringBuilder();
|
||||
buidler.AppendLine("function FindProxyForURL(url, host){");
|
||||
buidler.AppendLine($" var fastgithub = 'PROXY {proxyHost}';");
|
||||
foreach (var domain in fastGithubConfig.GetDomainPatterns())
|
||||
{
|
||||
buidler.AppendLine($" if (shExpMatch(host, '{domain}')) return fastgithub;");
|
||||
}
|
||||
buidler.AppendLine(" return 'DIRECT';");
|
||||
buidler.AppendLine("}");
|
||||
return buidler.ToString();
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -8,7 +8,7 @@ using System.Net;
|
||||
using System.Threading.Tasks;
|
||||
using Yarp.ReverseProxy.Forwarder;
|
||||
|
||||
namespace FastGithub.HttpServer
|
||||
namespace FastGithub.HttpServer.HttpMiddlewares
|
||||
{
|
||||
/// <summary>
|
||||
/// 反向代理中间件
|
||||
@ -43,7 +43,7 @@ namespace FastGithub.HttpServer
|
||||
public async Task InvokeAsync(HttpContext context, RequestDelegate next)
|
||||
{
|
||||
var host = context.Request.Host;
|
||||
if (this.TryGetDomainConfig(host, out var domainConfig) == false)
|
||||
if (TryGetDomainConfig(host, out var domainConfig) == false)
|
||||
{
|
||||
await next(context);
|
||||
}
|
||||
@ -51,8 +51,8 @@ namespace FastGithub.HttpServer
|
||||
{
|
||||
var scheme = context.Request.Scheme;
|
||||
var destinationPrefix = GetDestinationPrefix(scheme, host, domainConfig.Destination);
|
||||
var httpClient = this.httpClientFactory.CreateHttpClient(host.Host, domainConfig);
|
||||
var error = await httpForwarder.SendAsync(context, destinationPrefix, httpClient);
|
||||
var httpClient = httpClientFactory.CreateHttpClient(host.Host, domainConfig);
|
||||
var error = await httpForwarder.SendAsync(context, destinationPrefix, httpClient, ForwarderRequestConfig.Empty, HttpTransformer.Empty);
|
||||
await HandleErrorAsync(context, error);
|
||||
}
|
||||
else
|
||||
@ -74,7 +74,7 @@ namespace FastGithub.HttpServer
|
||||
/// <returns></returns>
|
||||
private bool TryGetDomainConfig(HostString host, [MaybeNullWhen(false)] out DomainConfig domainConfig)
|
||||
{
|
||||
if (this.fastGithubConfig.TryGetDomainConfig(host.Host, out domainConfig) == true)
|
||||
if (fastGithubConfig.TryGetDomainConfig(host.Host, out domainConfig) == true)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
@ -82,7 +82,7 @@ namespace FastGithub.HttpServer
|
||||
// 未配置的域名,但仍然被解析到本机ip的域名
|
||||
if (OperatingSystem.IsWindows() && IsDomain(host.Host))
|
||||
{
|
||||
this.logger.LogWarning($"域名{host.Host}可能已经被DNS污染,如果域名为本机域名,请解析为非回环IP");
|
||||
logger.LogWarning($"域名{host.Host}可能已经被DNS污染,如果域名为本机域名,请解析为非回环IP");
|
||||
domainConfig = defaultDomainConfig;
|
||||
return true;
|
||||
}
|
||||
@ -113,7 +113,7 @@ namespace FastGithub.HttpServer
|
||||
|
||||
var baseUri = new Uri(defaultValue);
|
||||
var result = new Uri(baseUri, destination).ToString();
|
||||
this.logger.LogInformation($"{defaultValue} => {result}");
|
||||
logger.LogInformation($"{defaultValue} => {result}");
|
||||
return result;
|
||||
}
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
namespace FastGithub.HttpServer
|
||||
namespace FastGithub.HttpServer.HttpMiddlewares
|
||||
{
|
||||
/// <summary>
|
||||
/// 请求日志特性
|
||||
@ -6,7 +6,7 @@ using System.Diagnostics;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace FastGithub.HttpServer
|
||||
namespace FastGithub.HttpServer.HttpMiddlewares
|
||||
{
|
||||
/// <summary>
|
||||
/// 请求日志中间件
|
||||
@ -55,15 +55,15 @@ namespace FastGithub.HttpServer
|
||||
var exception = context.GetForwarderErrorFeature()?.Exception;
|
||||
if (exception == null)
|
||||
{
|
||||
this.logger.LogInformation($"{request.Method} {request.Scheme}://{request.Host}{request.Path} responded {response.StatusCode} in {stopwatch.Elapsed.TotalMilliseconds} ms");
|
||||
logger.LogInformation($"{request.Method} {request.Scheme}://{request.Host}{request.Path} responded {response.StatusCode} in {stopwatch.Elapsed.TotalMilliseconds} ms");
|
||||
}
|
||||
else if (IsError(exception))
|
||||
{
|
||||
this.logger.LogError($"{request.Method} {request.Scheme}://{request.Host}{request.Path} responded {response.StatusCode} in {stopwatch.Elapsed.TotalMilliseconds} ms{Environment.NewLine}{exception}");
|
||||
logger.LogError($"{request.Method} {request.Scheme}://{request.Host}{request.Path} responded {response.StatusCode} in {stopwatch.Elapsed.TotalMilliseconds} ms{Environment.NewLine}{exception}");
|
||||
}
|
||||
else
|
||||
{
|
||||
this.logger.LogWarning($"{request.Method} {request.Scheme}://{request.Host}{request.Path} responded {response.StatusCode} in {stopwatch.Elapsed.TotalMilliseconds} ms{Environment.NewLine}{GetMessage(exception)}");
|
||||
logger.LogWarning($"{request.Method} {request.Scheme}://{request.Host}{request.Path} responded {response.StatusCode} in {stopwatch.Elapsed.TotalMilliseconds} ms{Environment.NewLine}{GetMessage(exception)}");
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,242 +0,0 @@
|
||||
using FastGithub.Configuration;
|
||||
using FastGithub.DomainResolve;
|
||||
using Microsoft.AspNetCore.Connections.Features;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Http.Features;
|
||||
using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.IO.Pipelines;
|
||||
using System.Net;
|
||||
using System.Net.Http;
|
||||
using System.Net.Sockets;
|
||||
using System.Reflection;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Text;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Yarp.ReverseProxy.Forwarder;
|
||||
|
||||
namespace FastGithub.HttpServer
|
||||
{
|
||||
/// <summary>
|
||||
/// http代理中间件
|
||||
/// </summary>
|
||||
sealed class HttpProxyMiddleware
|
||||
{
|
||||
private const string LOCALHOST = "localhost";
|
||||
private const int HTTP_PORT = 80;
|
||||
private const int HTTPS_PORT = 443;
|
||||
|
||||
private readonly FastGithubConfig fastGithubConfig;
|
||||
private readonly IDomainResolver domainResolver;
|
||||
private readonly IHttpForwarder httpForwarder;
|
||||
private readonly HttpReverseProxyMiddleware httpReverseProxy;
|
||||
|
||||
private readonly HttpMessageInvoker defaultHttpClient;
|
||||
private readonly TimeSpan connectTimeout = TimeSpan.FromSeconds(10d);
|
||||
|
||||
static HttpProxyMiddleware()
|
||||
{
|
||||
// https://github.com/dotnet/aspnetcore/issues/37421
|
||||
var authority = typeof(HttpParser<>).Assembly
|
||||
.GetType("Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Infrastructure.HttpCharacters")?
|
||||
.GetField("_authority", BindingFlags.NonPublic | BindingFlags.Static)?
|
||||
.GetValue(null);
|
||||
|
||||
if (authority is bool[] authorityArray)
|
||||
{
|
||||
authorityArray['-'] = true;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// http代理中间件
|
||||
/// </summary>
|
||||
/// <param name="fastGithubConfig"></param>
|
||||
/// <param name="domainResolver"></param>
|
||||
/// <param name="httpForwarder"></param>
|
||||
/// <param name="httpReverseProxy"></param>
|
||||
public HttpProxyMiddleware(
|
||||
FastGithubConfig fastGithubConfig,
|
||||
IDomainResolver domainResolver,
|
||||
IHttpForwarder httpForwarder,
|
||||
HttpReverseProxyMiddleware httpReverseProxy)
|
||||
{
|
||||
this.fastGithubConfig = fastGithubConfig;
|
||||
this.domainResolver = domainResolver;
|
||||
this.httpForwarder = httpForwarder;
|
||||
this.httpReverseProxy = httpReverseProxy;
|
||||
|
||||
this.defaultHttpClient = new HttpMessageInvoker(CreateDefaultHttpHandler(), disposeHandler: false);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 处理请求
|
||||
/// </summary>
|
||||
/// <param name="context"></param>
|
||||
/// <returns></returns>
|
||||
public async Task InvokeAsync(HttpContext context)
|
||||
{
|
||||
var host = context.Request.Host;
|
||||
if (this.IsFastGithubServer(host) == true)
|
||||
{
|
||||
var proxyPac = this.CreateProxyPac(host);
|
||||
context.Response.ContentType = "application/x-ns-proxy-autoconfig";
|
||||
context.Response.Headers.Add("Content-Disposition", $"attachment;filename=proxy.pac");
|
||||
await context.Response.WriteAsync(proxyPac);
|
||||
}
|
||||
else if (context.Request.Method == HttpMethods.Connect)
|
||||
{
|
||||
var cancellationToken = context.RequestAborted;
|
||||
using var connection = await this.CreateConnectionAsync(host, cancellationToken);
|
||||
var responseFeature = context.Features.Get<IHttpResponseFeature>();
|
||||
if (responseFeature != null)
|
||||
{
|
||||
responseFeature.ReasonPhrase = "Connection Established";
|
||||
}
|
||||
context.Response.StatusCode = StatusCodes.Status200OK;
|
||||
await context.Response.CompleteAsync();
|
||||
|
||||
var transport = context.Features.Get<IConnectionTransportFeature>()?.Transport;
|
||||
if (transport != null)
|
||||
{
|
||||
var task1 = connection.CopyToAsync(transport.Output, cancellationToken);
|
||||
var task2 = transport.Input.CopyToAsync(connection, cancellationToken);
|
||||
await Task.WhenAny(task1, task2);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
await this.httpReverseProxy.InvokeAsync(context, async next =>
|
||||
{
|
||||
var destinationPrefix = $"{context.Request.Scheme}://{context.Request.Host}";
|
||||
await this.httpForwarder.SendAsync(context, destinationPrefix, this.defaultHttpClient);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 是否为fastgithub服务
|
||||
/// </summary>
|
||||
/// <param name="host"></param>
|
||||
/// <returns></returns>
|
||||
private bool IsFastGithubServer(HostString host)
|
||||
{
|
||||
if (host.Port != this.fastGithubConfig.HttpProxyPort)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (host.Host == LOCALHOST)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
return IPAddress.TryParse(host.Host, out var address) && IPAddress.IsLoopback(address);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 创建proxypac脚本
|
||||
/// </summary>
|
||||
/// <param name="proxyHost"></param>
|
||||
/// <returns></returns>
|
||||
private string CreateProxyPac(HostString proxyHost)
|
||||
{
|
||||
var buidler = new StringBuilder();
|
||||
buidler.AppendLine("function FindProxyForURL(url, host){");
|
||||
buidler.AppendLine($" var fastgithub = 'PROXY {proxyHost}';");
|
||||
foreach (var domain in this.fastGithubConfig.GetDomainPatterns())
|
||||
{
|
||||
buidler.AppendLine($" if (shExpMatch(host, '{domain}')) return fastgithub;");
|
||||
}
|
||||
buidler.AppendLine(" return 'DIRECT';");
|
||||
buidler.AppendLine("}");
|
||||
return buidler.ToString();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 创建连接
|
||||
/// </summary>
|
||||
/// <param name="host"></param>
|
||||
/// <param name="cancellationToken"></param>
|
||||
/// <returns></returns>
|
||||
/// <exception cref="AggregateException"></exception>
|
||||
private async Task<Stream> CreateConnectionAsync(HostString host, CancellationToken cancellationToken)
|
||||
{
|
||||
var innerExceptions = new List<Exception>();
|
||||
await foreach (var endPoint in this.GetUpstreamEndPointsAsync(host, cancellationToken))
|
||||
{
|
||||
var socket = new Socket(SocketType.Stream, ProtocolType.Tcp);
|
||||
try
|
||||
{
|
||||
using var timeoutTokenSource = new CancellationTokenSource(this.connectTimeout);
|
||||
using var linkedTokenSource = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken, timeoutTokenSource.Token);
|
||||
await socket.ConnectAsync(endPoint, linkedTokenSource.Token);
|
||||
return new NetworkStream(socket, ownsSocket: false);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
socket.Dispose();
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
innerExceptions.Add(ex);
|
||||
}
|
||||
}
|
||||
throw new AggregateException($"无法连接到{host}", innerExceptions);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取目标终节点
|
||||
/// </summary>
|
||||
/// <param name="host"></param>
|
||||
/// <param name="cancellationToken"></param>
|
||||
/// <returns></returns>
|
||||
private async IAsyncEnumerable<EndPoint> GetUpstreamEndPointsAsync(HostString host, [EnumeratorCancellation] CancellationToken cancellationToken)
|
||||
{
|
||||
var targetHost = host.Host;
|
||||
var targetPort = host.Port ?? HTTPS_PORT;
|
||||
|
||||
if (IPAddress.TryParse(targetHost, out var address) == true)
|
||||
{
|
||||
yield return new IPEndPoint(address, targetPort);
|
||||
}
|
||||
else if (this.fastGithubConfig.IsMatch(targetHost) == false)
|
||||
{
|
||||
yield return new DnsEndPoint(targetHost, targetPort);
|
||||
}
|
||||
else if (targetPort == HTTP_PORT)
|
||||
{
|
||||
yield return new IPEndPoint(IPAddress.Loopback, GlobalListener.HttpPort);
|
||||
}
|
||||
else if (targetPort == HTTPS_PORT)
|
||||
{
|
||||
yield return new IPEndPoint(IPAddress.Loopback, GlobalListener.HttpsPort);
|
||||
}
|
||||
else
|
||||
{
|
||||
var dnsEndPoint = new DnsEndPoint(targetHost, targetPort);
|
||||
await foreach (var item in this.domainResolver.ResolveAsync(dnsEndPoint, cancellationToken))
|
||||
{
|
||||
yield return new IPEndPoint(item, targetPort);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 创建httpHandler
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
private static SocketsHttpHandler CreateDefaultHttpHandler()
|
||||
{
|
||||
return new()
|
||||
{
|
||||
Proxy = null,
|
||||
UseProxy = false,
|
||||
UseCookies = false,
|
||||
AllowAutoRedirect = false,
|
||||
AutomaticDecompression = DecompressionMethods.None
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1,8 +1,11 @@
|
||||
using FastGithub.Configuration;
|
||||
using FastGithub.HttpServer;
|
||||
using FastGithub.HttpServer.Certs;
|
||||
using FastGithub.HttpServer.TcpMiddlewares;
|
||||
using FastGithub.HttpServer.TlsMiddlewares;
|
||||
using Microsoft.AspNetCore.Connections;
|
||||
using Microsoft.AspNetCore.Hosting;
|
||||
using Microsoft.AspNetCore.Server.Kestrel.Core;
|
||||
using Microsoft.AspNetCore.Server.Kestrel.Https;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Microsoft.Extensions.Options;
|
||||
@ -13,7 +16,7 @@ namespace FastGithub
|
||||
/// <summary>
|
||||
/// Kestrel扩展
|
||||
/// </summary>
|
||||
public static class KestrelServerOptionsExtensions
|
||||
public static class KestrelServerExtensions
|
||||
{
|
||||
/// <summary>
|
||||
/// 无限制
|
||||
@ -40,13 +43,21 @@ namespace FastGithub
|
||||
throw new FastGithubException($"tcp端口{httpProxyPort}已经被其它进程占用,请在配置文件更换{nameof(FastGithubOptions.HttpProxyPort)}为其它端口");
|
||||
}
|
||||
|
||||
var logger = kestrel.GetLogger();
|
||||
kestrel.ListenLocalhost(httpProxyPort);
|
||||
logger.LogInformation($"已监听http://localhost:{httpProxyPort},http代理服务启动完成");
|
||||
kestrel.ListenLocalhost(httpProxyPort, listen =>
|
||||
{
|
||||
var proxyMiddleware = kestrel.ApplicationServices.GetRequiredService<HttpProxyMiddleware>();
|
||||
var tunnelMiddleware = kestrel.ApplicationServices.GetRequiredService<TunnelMiddleware>();
|
||||
|
||||
listen.Use(next => context => proxyMiddleware.InvokeAsync(next, context));
|
||||
listen.UseTls();
|
||||
listen.Use(next => context => tunnelMiddleware.InvokeAsync(next, context));
|
||||
});
|
||||
|
||||
kestrel.GetLogger().LogInformation($"已监听http://localhost:{httpProxyPort},http代理服务启动完成");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 监听ssh反向代理
|
||||
/// 监听ssh协议代理
|
||||
/// </summary>
|
||||
/// <param name="kestrel"></param>
|
||||
public static void ListenSshReverseProxy(this KestrelServerOptions kestrel)
|
||||
@ -62,7 +73,7 @@ namespace FastGithub
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 监听git反向代理
|
||||
/// 监听git协议代理代理
|
||||
/// </summary>
|
||||
/// <param name="kestrel"></param>
|
||||
public static void ListenGitReverseProxy(this KestrelServerOptions kestrel)
|
||||
@ -99,10 +110,6 @@ namespace FastGithub
|
||||
/// <exception cref="FastGithubException"></exception>
|
||||
public static void ListenHttpsReverseProxy(this KestrelServerOptions kestrel)
|
||||
{
|
||||
var certService = kestrel.ApplicationServices.GetRequiredService<CertService>();
|
||||
certService.CreateCaCertIfNotExists();
|
||||
certService.InstallAndTrustCaCert();
|
||||
|
||||
var httpsPort = GlobalListener.HttpsPort;
|
||||
kestrel.ListenLocalhost(httpsPort, listen =>
|
||||
{
|
||||
@ -110,10 +117,7 @@ namespace FastGithub
|
||||
{
|
||||
listen.UseFlowAnalyze();
|
||||
}
|
||||
listen.UseHttps(https =>
|
||||
{
|
||||
https.ServerCertificateSelector = (ctx, domain) => certService.GetOrCreateServerCert(domain);
|
||||
});
|
||||
listen.UseTls();
|
||||
});
|
||||
|
||||
if (OperatingSystem.IsWindows())
|
||||
@ -133,5 +137,36 @@ namespace FastGithub
|
||||
var loggerFactory = kestrel.ApplicationServices.GetRequiredService<ILoggerFactory>();
|
||||
return loggerFactory.CreateLogger($"{nameof(FastGithub)}.{nameof(HttpServer)}");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 使用Tls中间件
|
||||
/// </summary>
|
||||
/// <param name="listen"></param>
|
||||
/// <param name="configureOptions">https配置</param>
|
||||
/// <returns></returns>
|
||||
public static ListenOptions UseTls(this ListenOptions listen)
|
||||
{
|
||||
var certService = listen.ApplicationServices.GetRequiredService<CertService>();
|
||||
certService.CreateCaCertIfNotExists();
|
||||
certService.InstallAndTrustCaCert();
|
||||
return listen.UseTls(https => https.ServerCertificateSelector = (ctx, domain) => certService.GetOrCreateServerCert(domain));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 使用Tls中间件
|
||||
/// </summary>
|
||||
/// <param name="listen"></param>
|
||||
/// <param name="configureOptions">https配置</param>
|
||||
/// <returns></returns>
|
||||
private static ListenOptions UseTls(this ListenOptions listen, Action<HttpsConnectionAdapterOptions> configureOptions)
|
||||
{
|
||||
var invadeMiddleware = listen.ApplicationServices.GetRequiredService<TlsInvadeMiddleware>();
|
||||
var restoreMiddleware = listen.ApplicationServices.GetRequiredService<TlsRestoreMiddleware>();
|
||||
|
||||
listen.Use(next => context => invadeMiddleware.InvokeAsync(next, context));
|
||||
listen.UseHttps(configureOptions);
|
||||
listen.Use(next => context => restoreMiddleware.InvokeAsync(next, context));
|
||||
return listen;
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1,4 +1,8 @@
|
||||
using FastGithub.HttpServer;
|
||||
using FastGithub.HttpServer.Certs;
|
||||
using FastGithub.HttpServer.Certs.CaCertInstallers;
|
||||
using FastGithub.HttpServer.HttpMiddlewares;
|
||||
using FastGithub.HttpServer.TcpMiddlewares;
|
||||
using FastGithub.HttpServer.TlsMiddlewares;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
namespace FastGithub
|
||||
{
|
||||
@ -22,7 +26,17 @@ namespace FastGithub
|
||||
.AddSingleton<ICaCertInstaller, CaCertInstallerOfWindows>()
|
||||
.AddSingleton<ICaCertInstaller, CaCertInstallerOfLinuxRedHat>()
|
||||
.AddSingleton<ICaCertInstaller, CaCertInstallerOfLinuxDebian>()
|
||||
|
||||
// tcp
|
||||
.AddSingleton<HttpProxyMiddleware>()
|
||||
.AddSingleton<TunnelMiddleware>()
|
||||
|
||||
// tls
|
||||
.AddSingleton<TlsInvadeMiddleware>()
|
||||
.AddSingleton<TlsRestoreMiddleware>()
|
||||
|
||||
// http
|
||||
.AddSingleton<HttpProxyPacMiddleware>()
|
||||
.AddSingleton<RequestLoggingMiddleware>()
|
||||
.AddSingleton<HttpReverseProxyMiddleware>();
|
||||
}
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
using FastGithub.DomainResolve;
|
||||
|
||||
namespace FastGithub.HttpServer
|
||||
namespace FastGithub.HttpServer.TcpMiddlewares
|
||||
{
|
||||
/// <summary>
|
||||
/// github的git代理处理者
|
||||
@ -1,6 +1,6 @@
|
||||
using FastGithub.DomainResolve;
|
||||
|
||||
namespace FastGithub.HttpServer
|
||||
namespace FastGithub.HttpServer.TcpMiddlewares
|
||||
{
|
||||
/// <summary>
|
||||
/// github的ssh代理处理者
|
||||
135
FastGithub.HttpServer/TcpMiddlewares/HttpProxyMiddleware.cs
Normal file
135
FastGithub.HttpServer/TcpMiddlewares/HttpProxyMiddleware.cs
Normal file
@ -0,0 +1,135 @@
|
||||
using Microsoft.AspNetCore.Connections;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http;
|
||||
using System;
|
||||
using System.Buffers;
|
||||
using System.IO.Pipelines;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace FastGithub.HttpServer.TcpMiddlewares
|
||||
{
|
||||
/// <summary>
|
||||
/// 正向代理中间件
|
||||
/// </summary>
|
||||
sealed class HttpProxyMiddleware
|
||||
{
|
||||
private readonly HttpParser<HttpRequestHandler> httpParser = new();
|
||||
private readonly byte[] http200 = Encoding.ASCII.GetBytes("HTTP/1.1 200 Connection Established\r\n\r\n");
|
||||
private readonly byte[] http400 = Encoding.ASCII.GetBytes("HTTP/1.1 400 Bad Request\r\n\r\n");
|
||||
|
||||
/// <summary>
|
||||
/// 执行中间件
|
||||
/// </summary>
|
||||
/// <param name="next"></param>
|
||||
/// <param name="context"></param>
|
||||
/// <returns></returns>
|
||||
public async Task InvokeAsync(ConnectionDelegate next, ConnectionContext context)
|
||||
{
|
||||
var result = await context.Transport.Input.ReadAsync();
|
||||
var httpRequest = this.GetHttpRequestHandler(result, out var consumed);
|
||||
|
||||
// 协议错误
|
||||
if (consumed == 0L)
|
||||
{
|
||||
await context.Transport.Output.WriteAsync(this.http400, context.ConnectionClosed);
|
||||
}
|
||||
else
|
||||
{
|
||||
// 隧道代理连接请求
|
||||
if (httpRequest.ProxyProtocol == ProxyProtocol.TunnelProxy)
|
||||
{
|
||||
var position = result.Buffer.GetPosition(consumed);
|
||||
context.Transport.Input.AdvanceTo(position);
|
||||
await context.Transport.Output.WriteAsync(this.http200, context.ConnectionClosed);
|
||||
}
|
||||
else
|
||||
{
|
||||
var position = result.Buffer.Start;
|
||||
context.Transport.Input.AdvanceTo(position);
|
||||
}
|
||||
|
||||
context.Features.Set<IHttpProxyFeature>(httpRequest);
|
||||
await next(context);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// 获取http请求处理者
|
||||
/// </summary>
|
||||
/// <param name="result"></param>
|
||||
/// <param name="consumed"></param>
|
||||
/// <returns></returns>
|
||||
private HttpRequestHandler GetHttpRequestHandler(ReadResult result, out long consumed)
|
||||
{
|
||||
var handler = new HttpRequestHandler();
|
||||
var reader = new SequenceReader<byte>(result.Buffer);
|
||||
|
||||
if (this.httpParser.ParseRequestLine(handler, ref reader) &&
|
||||
this.httpParser.ParseHeaders(handler, ref reader))
|
||||
{
|
||||
consumed = reader.Consumed;
|
||||
}
|
||||
else
|
||||
{
|
||||
consumed = 0L;
|
||||
}
|
||||
return handler;
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// 代理请求处理器
|
||||
/// </summary>
|
||||
private class HttpRequestHandler : IHttpRequestLineHandler, IHttpHeadersHandler, IHttpProxyFeature
|
||||
{
|
||||
private HttpMethod method;
|
||||
|
||||
public HostString ProxyHost { get; private set; }
|
||||
|
||||
public ProxyProtocol ProxyProtocol
|
||||
{
|
||||
get
|
||||
{
|
||||
if (this.ProxyHost.HasValue == false)
|
||||
{
|
||||
return ProxyProtocol.None;
|
||||
}
|
||||
if (this.method == HttpMethod.Connect)
|
||||
{
|
||||
return ProxyProtocol.TunnelProxy;
|
||||
}
|
||||
return ProxyProtocol.HttpProxy;
|
||||
}
|
||||
}
|
||||
|
||||
void IHttpRequestLineHandler.OnStartLine(HttpVersionAndMethod versionAndMethod, TargetOffsetPathLength targetPath, Span<byte> startLine)
|
||||
{
|
||||
this.method = versionAndMethod.Method;
|
||||
var host = Encoding.ASCII.GetString(startLine.Slice(targetPath.Offset, targetPath.Length));
|
||||
if (versionAndMethod.Method == HttpMethod.Connect)
|
||||
{
|
||||
this.ProxyHost = HostString.FromUriComponent(host);
|
||||
}
|
||||
else if (Uri.TryCreate(host, UriKind.Absolute, out var uri))
|
||||
{
|
||||
this.ProxyHost = HostString.FromUriComponent(uri);
|
||||
}
|
||||
}
|
||||
|
||||
void IHttpHeadersHandler.OnHeader(ReadOnlySpan<byte> name, ReadOnlySpan<byte> value)
|
||||
{
|
||||
}
|
||||
void IHttpHeadersHandler.OnHeadersComplete(bool endStream)
|
||||
{
|
||||
}
|
||||
void IHttpHeadersHandler.OnStaticIndexedHeader(int index)
|
||||
{
|
||||
}
|
||||
void IHttpHeadersHandler.OnStaticIndexedHeader(int index, ReadOnlySpan<byte> value)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
11
FastGithub.HttpServer/TcpMiddlewares/IHttpProxyFeature.cs
Normal file
11
FastGithub.HttpServer/TcpMiddlewares/IHttpProxyFeature.cs
Normal file
@ -0,0 +1,11 @@
|
||||
using Microsoft.AspNetCore.Http;
|
||||
|
||||
namespace FastGithub.HttpServer.TcpMiddlewares
|
||||
{
|
||||
interface IHttpProxyFeature
|
||||
{
|
||||
HostString ProxyHost { get; }
|
||||
|
||||
ProxyProtocol ProxyProtocol { get; }
|
||||
}
|
||||
}
|
||||
23
FastGithub.HttpServer/TcpMiddlewares/ProxyProtocol.cs
Normal file
23
FastGithub.HttpServer/TcpMiddlewares/ProxyProtocol.cs
Normal file
@ -0,0 +1,23 @@
|
||||
namespace FastGithub.HttpServer.TcpMiddlewares
|
||||
{
|
||||
/// <summary>
|
||||
/// 代理协议
|
||||
/// </summary>
|
||||
enum ProxyProtocol
|
||||
{
|
||||
/// <summary>
|
||||
/// 无代理
|
||||
/// </summary>
|
||||
None,
|
||||
|
||||
/// <summary>
|
||||
/// http代理
|
||||
/// </summary>
|
||||
HttpProxy,
|
||||
|
||||
/// <summary>
|
||||
/// 隧道代理
|
||||
/// </summary>
|
||||
TunnelProxy
|
||||
}
|
||||
}
|
||||
@ -9,10 +9,10 @@ using System.Net.Sockets;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace FastGithub.HttpServer
|
||||
namespace FastGithub.HttpServer.TcpMiddlewares
|
||||
{
|
||||
/// <summary>
|
||||
/// tcp反射代理处理者
|
||||
/// tcp协议代理处理者
|
||||
/// </summary>
|
||||
abstract class TcpReverseProxyHandler : ConnectionHandler
|
||||
{
|
||||
@ -21,7 +21,7 @@ namespace FastGithub.HttpServer
|
||||
private readonly TimeSpan connectTimeout = TimeSpan.FromSeconds(10d);
|
||||
|
||||
/// <summary>
|
||||
/// tcp反射代理处理者
|
||||
/// tcp协议代理处理者
|
||||
/// </summary>
|
||||
/// <param name="domainResolver"></param>
|
||||
/// <param name="endPoint"></param>
|
||||
@ -39,7 +39,7 @@ namespace FastGithub.HttpServer
|
||||
public override async Task OnConnectedAsync(ConnectionContext context)
|
||||
{
|
||||
var cancellationToken = context.ConnectionClosed;
|
||||
using var connection = await this.CreateConnectionAsync(cancellationToken);
|
||||
using var connection = await CreateConnectionAsync(cancellationToken);
|
||||
var task1 = connection.CopyToAsync(context.Transport.Output, cancellationToken);
|
||||
var task2 = context.Transport.Input.CopyToAsync(connection, cancellationToken);
|
||||
await Task.WhenAny(task1, task2);
|
||||
@ -54,14 +54,14 @@ namespace FastGithub.HttpServer
|
||||
private async Task<Stream> CreateConnectionAsync(CancellationToken cancellationToken)
|
||||
{
|
||||
var innerExceptions = new List<Exception>();
|
||||
await foreach (var address in this.domainResolver.ResolveAsync(this.endPoint, cancellationToken))
|
||||
await foreach (var address in domainResolver.ResolveAsync(endPoint, cancellationToken))
|
||||
{
|
||||
var socket = new Socket(address.AddressFamily, SocketType.Stream, ProtocolType.Tcp);
|
||||
try
|
||||
{
|
||||
using var timeoutTokenSource = new CancellationTokenSource(this.connectTimeout);
|
||||
using var timeoutTokenSource = new CancellationTokenSource(connectTimeout);
|
||||
using var linkedTokenSource = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken, timeoutTokenSource.Token);
|
||||
await socket.ConnectAsync(address, this.endPoint.Port, linkedTokenSource.Token);
|
||||
await socket.ConnectAsync(address, endPoint.Port, linkedTokenSource.Token);
|
||||
return new NetworkStream(socket, ownsSocket: false);
|
||||
}
|
||||
catch (Exception ex)
|
||||
@ -71,7 +71,7 @@ namespace FastGithub.HttpServer
|
||||
innerExceptions.Add(ex);
|
||||
}
|
||||
}
|
||||
throw new AggregateException($"无法连接到{this.endPoint.Host}:{this.endPoint.Port}", innerExceptions);
|
||||
throw new AggregateException($"无法连接到{endPoint.Host}:{endPoint.Port}", innerExceptions);
|
||||
}
|
||||
}
|
||||
}
|
||||
132
FastGithub.HttpServer/TcpMiddlewares/TunnelMiddleware.cs
Normal file
132
FastGithub.HttpServer/TcpMiddlewares/TunnelMiddleware.cs
Normal file
@ -0,0 +1,132 @@
|
||||
using FastGithub.Configuration;
|
||||
using FastGithub.DomainResolve;
|
||||
using Microsoft.AspNetCore.Connections;
|
||||
using Microsoft.AspNetCore.Connections.Features;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Http.Features;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.IO.Pipelines;
|
||||
using System.Net;
|
||||
using System.Net.Sockets;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace FastGithub.HttpServer.TcpMiddlewares
|
||||
{
|
||||
/// <summary>
|
||||
/// 隧道中间件
|
||||
/// </summary>
|
||||
sealed class TunnelMiddleware
|
||||
{
|
||||
private readonly FastGithubConfig fastGithubConfig;
|
||||
private readonly IDomainResolver domainResolver;
|
||||
private readonly TimeSpan connectTimeout = TimeSpan.FromSeconds(10d);
|
||||
|
||||
/// <summary>
|
||||
/// 隧道中间件
|
||||
/// </summary>
|
||||
/// <param name="fastGithubConfig"></param>
|
||||
/// <param name="domainResolver"></param>
|
||||
public TunnelMiddleware(
|
||||
FastGithubConfig fastGithubConfig,
|
||||
IDomainResolver domainResolver)
|
||||
{
|
||||
this.fastGithubConfig = fastGithubConfig;
|
||||
this.domainResolver = domainResolver;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 执行中间件
|
||||
/// </summary>
|
||||
/// <param name="next"></param>
|
||||
/// <param name="context"></param>
|
||||
/// <returns></returns>
|
||||
public async Task InvokeAsync(ConnectionDelegate next, ConnectionContext context)
|
||||
{
|
||||
var proxyFeature = context.Features.Get<IHttpProxyFeature>();
|
||||
if (proxyFeature == null || // 非代理
|
||||
proxyFeature.ProxyProtocol != ProxyProtocol.TunnelProxy || //非隧道代理
|
||||
context.Features.Get<ITlsConnectionFeature>() != null) // 经过隧道的https
|
||||
{
|
||||
await next(context);
|
||||
}
|
||||
else
|
||||
{
|
||||
var transport = context.Features.Get<IConnectionTransportFeature>()?.Transport;
|
||||
if (transport != null)
|
||||
{
|
||||
var cancellationToken = context.ConnectionClosed;
|
||||
using var connection = await this.CreateConnectionAsync(proxyFeature.ProxyHost, cancellationToken);
|
||||
|
||||
var task1 = connection.CopyToAsync(transport.Output, cancellationToken);
|
||||
var task2 = transport.Input.CopyToAsync(connection, cancellationToken);
|
||||
await Task.WhenAny(task1, task2);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// 创建连接
|
||||
/// </summary>
|
||||
/// <param name="host"></param>
|
||||
/// <param name="cancellationToken"></param>
|
||||
/// <returns></returns>
|
||||
/// <exception cref="AggregateException"></exception>
|
||||
private async Task<Stream> CreateConnectionAsync(HostString host, CancellationToken cancellationToken)
|
||||
{
|
||||
var innerExceptions = new List<Exception>();
|
||||
await foreach (var endPoint in this.GetUpstreamEndPointsAsync(host, cancellationToken))
|
||||
{
|
||||
var socket = new Socket(SocketType.Stream, ProtocolType.Tcp);
|
||||
try
|
||||
{
|
||||
using var timeoutTokenSource = new CancellationTokenSource(this.connectTimeout);
|
||||
using var linkedTokenSource = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken, timeoutTokenSource.Token);
|
||||
await socket.ConnectAsync(endPoint, linkedTokenSource.Token);
|
||||
return new NetworkStream(socket, ownsSocket: true);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
socket.Dispose();
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
innerExceptions.Add(ex);
|
||||
}
|
||||
}
|
||||
throw new AggregateException($"无法连接到{host}", innerExceptions);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取目标终节点
|
||||
/// </summary>
|
||||
/// <param name="host"></param>
|
||||
/// <param name="cancellationToken"></param>
|
||||
/// <returns></returns>
|
||||
private async IAsyncEnumerable<EndPoint> GetUpstreamEndPointsAsync(HostString host, [EnumeratorCancellation] CancellationToken cancellationToken)
|
||||
{
|
||||
const int HTTPS_PORT = 443;
|
||||
var targetHost = host.Host;
|
||||
var targetPort = host.Port ?? HTTPS_PORT;
|
||||
|
||||
if (IPAddress.TryParse(targetHost, out var address) == true)
|
||||
{
|
||||
yield return new IPEndPoint(address, targetPort);
|
||||
}
|
||||
else if (this.fastGithubConfig.IsMatch(targetHost) == false)
|
||||
{
|
||||
yield return new DnsEndPoint(targetHost, targetPort);
|
||||
}
|
||||
else
|
||||
{
|
||||
var dnsEndPoint = new DnsEndPoint(targetHost, targetPort);
|
||||
await foreach (var item in this.domainResolver.ResolveAsync(dnsEndPoint, cancellationToken))
|
||||
{
|
||||
yield return new IPEndPoint(item, targetPort);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,27 @@
|
||||
using Microsoft.AspNetCore.Http.Features;
|
||||
using System;
|
||||
using System.Security.Cryptography.X509Certificates;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace FastGithub.HttpServer.TlsMiddlewares
|
||||
{
|
||||
/// <summary>
|
||||
/// 假冒的TlsConnectionFeature
|
||||
/// </summary>
|
||||
sealed class FakeTlsConnectionFeature : ITlsConnectionFeature
|
||||
{
|
||||
public static FakeTlsConnectionFeature Instance { get; } = new FakeTlsConnectionFeature();
|
||||
|
||||
public X509Certificate2? ClientCertificate
|
||||
{
|
||||
get => throw new NotImplementedException();
|
||||
set => throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public Task<X509Certificate2?> GetClientCertificateAsync(CancellationToken cancellationToken)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
}
|
||||
}
|
||||
64
FastGithub.HttpServer/TlsMiddlewares/TlsInvadeMiddleware.cs
Normal file
64
FastGithub.HttpServer/TlsMiddlewares/TlsInvadeMiddleware.cs
Normal file
@ -0,0 +1,64 @@
|
||||
using Microsoft.AspNetCore.Connections;
|
||||
using Microsoft.AspNetCore.Http.Features;
|
||||
using System.Buffers;
|
||||
using System.IO.Pipelines;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace FastGithub.HttpServer.TlsMiddlewares
|
||||
{
|
||||
/// <summary>
|
||||
/// https入侵中间件
|
||||
/// </summary>
|
||||
sealed class TlsInvadeMiddleware
|
||||
{
|
||||
/// <summary>
|
||||
/// 执行中间件
|
||||
/// </summary>
|
||||
/// <param name="context"></param>
|
||||
/// <returns></returns>
|
||||
public async Task InvokeAsync(ConnectionDelegate next, ConnectionContext context)
|
||||
{
|
||||
// 连接不是tls
|
||||
if (await IsTlsConnectionAsync(context) == false)
|
||||
{
|
||||
// 没有任何tls中间件执行过
|
||||
if (context.Features.Get<ITlsConnectionFeature>() == null)
|
||||
{
|
||||
// 设置假的ITlsConnectionFeature,迫使https中间件跳过自身的工作
|
||||
context.Features.Set<ITlsConnectionFeature>(FakeTlsConnectionFeature.Instance);
|
||||
}
|
||||
}
|
||||
await next(context);
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// 是否为tls协议
|
||||
/// </summary>
|
||||
/// <param name="context"></param>
|
||||
/// <returns></returns>
|
||||
private static async Task<bool> IsTlsConnectionAsync(ConnectionContext context)
|
||||
{
|
||||
try
|
||||
{
|
||||
var result = await context.Transport.Input.ReadAtLeastAsync(2, context.ConnectionClosed);
|
||||
var state = IsTlsProtocol(result);
|
||||
context.Transport.Input.AdvanceTo(result.Buffer.Start);
|
||||
return state;
|
||||
}
|
||||
catch
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
static bool IsTlsProtocol(ReadResult result)
|
||||
{
|
||||
var reader = new SequenceReader<byte>(result.Buffer);
|
||||
return reader.TryRead(out var firstByte) &&
|
||||
reader.TryRead(out var nextByte) &&
|
||||
firstByte == 0x16 &&
|
||||
nextByte == 0x3;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
27
FastGithub.HttpServer/TlsMiddlewares/TlsRestoreMiddleware.cs
Normal file
27
FastGithub.HttpServer/TlsMiddlewares/TlsRestoreMiddleware.cs
Normal file
@ -0,0 +1,27 @@
|
||||
using Microsoft.AspNetCore.Connections;
|
||||
using Microsoft.AspNetCore.Http.Features;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace FastGithub.HttpServer.TlsMiddlewares
|
||||
{
|
||||
/// <summary>
|
||||
/// https恢复中间件
|
||||
/// </summary>
|
||||
sealed class TlsRestoreMiddleware
|
||||
{
|
||||
/// <summary>
|
||||
/// 执行中间件
|
||||
/// </summary>
|
||||
/// <param name="context"></param>
|
||||
/// <returns></returns>
|
||||
public async Task InvokeAsync(ConnectionDelegate next, ConnectionContext context)
|
||||
{
|
||||
if (context.Features.Get<ITlsConnectionFeature>() == FakeTlsConnectionFeature.Instance)
|
||||
{
|
||||
// 擦除入侵
|
||||
context.Features.Set<ITlsConnectionFeature>(null);
|
||||
}
|
||||
await next(context);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -67,11 +67,10 @@ namespace FastGithub
|
||||
webBuilder.UseKestrel(kestrel =>
|
||||
{
|
||||
kestrel.NoLimit();
|
||||
kestrel.ListenHttpsReverseProxy();
|
||||
kestrel.ListenHttpReverseProxy();
|
||||
|
||||
if (OperatingSystem.IsWindows())
|
||||
{
|
||||
kestrel.ListenHttpsReverseProxy();
|
||||
kestrel.ListenHttpReverseProxy();
|
||||
kestrel.ListenSshReverseProxy();
|
||||
kestrel.ListenGitReverseProxy();
|
||||
}
|
||||
|
||||
@ -4,7 +4,6 @@ using Microsoft.AspNetCore.Builder;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.Extensions.Configuration;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.Options;
|
||||
using System;
|
||||
|
||||
namespace FastGithub
|
||||
@ -53,20 +52,13 @@ namespace FastGithub
|
||||
/// <param name="app"></param>
|
||||
public void Configure(IApplicationBuilder app)
|
||||
{
|
||||
var httpProxyPort = app.ApplicationServices.GetRequiredService<IOptions<FastGithubOptions>>().Value.HttpProxyPort;
|
||||
app.MapWhen(context => context.Connection.LocalPort == httpProxyPort, appBuilder =>
|
||||
{
|
||||
appBuilder.UseHttpProxy();
|
||||
});
|
||||
app.UseHttpProxyPac();
|
||||
app.UseRequestLogging();
|
||||
app.UseHttpReverseProxy();
|
||||
|
||||
app.MapWhen(context => context.Connection.LocalPort != httpProxyPort, appBuilder =>
|
||||
{
|
||||
appBuilder.UseRequestLogging();
|
||||
appBuilder.UseHttpReverseProxy();
|
||||
|
||||
appBuilder.UseRouting();
|
||||
appBuilder.DisableRequestLogging();
|
||||
appBuilder.UseEndpoints(endpoint =>
|
||||
app.UseRouting();
|
||||
app.DisableRequestLogging();
|
||||
app.UseEndpoints(endpoint =>
|
||||
{
|
||||
endpoint.MapGet("/flowStatistics", context =>
|
||||
{
|
||||
@ -74,7 +66,6 @@ namespace FastGithub
|
||||
return context.Response.WriteAsJsonAsync(flowStatistics);
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Loading…
Reference in New Issue
Block a user