diff --git a/FastGithub.HttpServer/ApplicationBuilderExtensions.cs b/FastGithub.HttpServer/ApplicationBuilderExtensions.cs index b52daa5..ada408f 100644 --- a/FastGithub.HttpServer/ApplicationBuilderExtensions.cs +++ b/FastGithub.HttpServer/ApplicationBuilderExtensions.cs @@ -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 { /// - /// 使用http代理中间件 + /// 使用http代理策略中间件 /// /// /// - public static IApplicationBuilder UseHttpProxy(this IApplicationBuilder app) + public static IApplicationBuilder UseHttpProxyPac(this IApplicationBuilder app) { - var middleware = app.ApplicationServices.GetRequiredService(); - return app.Use(next => context => middleware.InvokeAsync(context)); + var middleware = app.ApplicationServices.GetRequiredService(); + return app.Use(next => context => middleware.InvokeAsync(context, next)); } /// diff --git a/FastGithub.HttpServer/CaCertInstallerOfLinux.cs b/FastGithub.HttpServer/Certs/CaCertInstallers/CaCertInstallerOfLinux.cs similarity index 68% rename from FastGithub.HttpServer/CaCertInstallerOfLinux.cs rename to FastGithub.HttpServer/Certs/CaCertInstallers/CaCertInstallerOfLinux.cs index 0c5b5a3..d12017c 100644 --- a/FastGithub.HttpServer/CaCertInstallerOfLinux.cs +++ b/FastGithub.HttpServer/Certs/CaCertInstallers/CaCertInstallerOfLinux.cs @@ -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 /// public bool IsSupported() { - return OperatingSystem.IsLinux() && File.Exists(this.CaCertUpdatePath); + return OperatingSystem.IsLinux() && File.Exists(CaCertUpdatePath); } /// @@ -44,7 +45,7 @@ namespace FastGithub.HttpServer /// 证书文件路径 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证书异常"); } } } diff --git a/FastGithub.HttpServer/CaCertInstallerOfLinuxDebian.cs b/FastGithub.HttpServer/Certs/CaCertInstallers/CaCertInstallerOfLinuxDebian.cs similarity index 88% rename from FastGithub.HttpServer/CaCertInstallerOfLinuxDebian.cs rename to FastGithub.HttpServer/Certs/CaCertInstallers/CaCertInstallerOfLinuxDebian.cs index 950dd8c..5e74ae6 100644 --- a/FastGithub.HttpServer/CaCertInstallerOfLinuxDebian.cs +++ b/FastGithub.HttpServer/Certs/CaCertInstallers/CaCertInstallerOfLinuxDebian.cs @@ -1,6 +1,6 @@ using Microsoft.Extensions.Logging; -namespace FastGithub.HttpServer +namespace FastGithub.HttpServer.Certs.CaCertInstallers { sealed class CaCertInstallerOfLinuxDebian : CaCertInstallerOfLinux { diff --git a/FastGithub.HttpServer/CaCertInstallerOfLinuxRedHat.cs b/FastGithub.HttpServer/Certs/CaCertInstallers/CaCertInstallerOfLinuxRedHat.cs similarity index 88% rename from FastGithub.HttpServer/CaCertInstallerOfLinuxRedHat.cs rename to FastGithub.HttpServer/Certs/CaCertInstallers/CaCertInstallerOfLinuxRedHat.cs index d78f4d8..4fad5c4 100644 --- a/FastGithub.HttpServer/CaCertInstallerOfLinuxRedHat.cs +++ b/FastGithub.HttpServer/Certs/CaCertInstallers/CaCertInstallerOfLinuxRedHat.cs @@ -1,6 +1,6 @@ using Microsoft.Extensions.Logging; -namespace FastGithub.HttpServer +namespace FastGithub.HttpServer.Certs.CaCertInstallers { sealed class CaCertInstallerOfLinuxRedHat : CaCertInstallerOfLinux { diff --git a/FastGithub.HttpServer/CaCertInstallerOfMacOS.cs b/FastGithub.HttpServer/Certs/CaCertInstallers/CaCertInstallerOfMacOS.cs similarity index 82% rename from FastGithub.HttpServer/CaCertInstallerOfMacOS.cs rename to FastGithub.HttpServer/Certs/CaCertInstallers/CaCertInstallerOfMacOS.cs index 8aeb009..23dcad8 100644 --- a/FastGithub.HttpServer/CaCertInstallerOfMacOS.cs +++ b/FastGithub.HttpServer/Certs/CaCertInstallers/CaCertInstallerOfMacOS.cs @@ -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 /// 证书文件路径 public void Install(string caCertFilePath) { - this.logger.LogWarning($"请手动安装CA证书然后设置信任CA证书{caCertFilePath}"); + logger.LogWarning($"请手动安装CA证书然后设置信任CA证书{caCertFilePath}"); } } } diff --git a/FastGithub.HttpServer/CaCertInstallerOfWindows.cs b/FastGithub.HttpServer/Certs/CaCertInstallers/CaCertInstallerOfWindows.cs similarity index 88% rename from FastGithub.HttpServer/CaCertInstallerOfWindows.cs rename to FastGithub.HttpServer/Certs/CaCertInstallers/CaCertInstallerOfWindows.cs index 7b87e2e..72eb956 100644 --- a/FastGithub.HttpServer/CaCertInstallerOfWindows.cs +++ b/FastGithub.HttpServer/Certs/CaCertInstallers/CaCertInstallerOfWindows.cs @@ -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}到“将所有的证书都放入下列存储”\\“受信任的根证书颁发机构”"); } } } diff --git a/FastGithub.HttpServer/CertGenerator.cs b/FastGithub.HttpServer/Certs/CertGenerator.cs similarity index 99% rename from FastGithub.HttpServer/CertGenerator.cs rename to FastGithub.HttpServer/Certs/CertGenerator.cs index 4ddd01b..9162680 100644 --- a/FastGithub.HttpServer/CertGenerator.cs +++ b/FastGithub.HttpServer/Certs/CertGenerator.cs @@ -19,7 +19,7 @@ using System.Net; using System.Text; using X509Certificate2 = System.Security.Cryptography.X509Certificates.X509Certificate2; -namespace FastGithub.HttpServer +namespace FastGithub.HttpServer.Certs { /// /// 证书生成器 diff --git a/FastGithub.HttpServer/CertService.cs b/FastGithub.HttpServer/Certs/CertService.cs similarity index 86% rename from FastGithub.HttpServer/CertService.cs rename to FastGithub.HttpServer/Certs/CertService.cs index 7aa2c66..0b04d5d 100644 --- a/FastGithub.HttpServer/CertService.cs +++ b/FastGithub.HttpServer/Certs/CertService.cs @@ -8,7 +8,7 @@ using System.Linq; using System.Net; using System.Security.Cryptography.X509Certificates; -namespace FastGithub.HttpServer +namespace FastGithub.HttpServer.Certs { /// /// 证书服务 @@ -54,17 +54,17 @@ namespace FastGithub.HttpServer /// 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 /// 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); } } diff --git a/FastGithub.HttpServer/ICaCertInstaller.cs b/FastGithub.HttpServer/Certs/ICaCertInstaller.cs similarity index 91% rename from FastGithub.HttpServer/ICaCertInstaller.cs rename to FastGithub.HttpServer/Certs/ICaCertInstaller.cs index 7e7e6f5..16f8fd6 100644 --- a/FastGithub.HttpServer/ICaCertInstaller.cs +++ b/FastGithub.HttpServer/Certs/ICaCertInstaller.cs @@ -1,4 +1,4 @@ -namespace FastGithub.HttpServer +namespace FastGithub.HttpServer.Certs { /// /// CA证书安装器 diff --git a/FastGithub.HttpServer/FastGithub.HttpServer.csproj b/FastGithub.HttpServer/FastGithub.HttpServer.csproj index 3d21f0d..2d5a9d5 100644 --- a/FastGithub.HttpServer/FastGithub.HttpServer.csproj +++ b/FastGithub.HttpServer/FastGithub.HttpServer.csproj @@ -7,7 +7,7 @@ - + diff --git a/FastGithub.HttpServer/HttpMiddlewares/HttpProxyPacMiddleware.cs b/FastGithub.HttpServer/HttpMiddlewares/HttpProxyPacMiddleware.cs new file mode 100644 index 0000000..3bf01b4 --- /dev/null +++ b/FastGithub.HttpServer/HttpMiddlewares/HttpProxyPacMiddleware.cs @@ -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 +{ + /// + /// http代理策略中间件 + /// + sealed class HttpProxyPacMiddleware + { + private readonly FastGithubConfig fastGithubConfig; + + /// + /// http代理策略中间件 + /// + /// + public HttpProxyPacMiddleware(FastGithubConfig fastGithubConfig) + { + this.fastGithubConfig = fastGithubConfig; + } + + /// + /// 处理请求 + /// + /// + /// + /// + public async Task InvokeAsync(HttpContext context, RequestDelegate next) + { + // http请求经过了httpProxy中间件 + var proxyFeature = context.Features.Get(); + 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); + } + } + + /// + /// 创建proxypac脚本 + /// + /// + /// + 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(); + } + } +} \ No newline at end of file diff --git a/FastGithub.HttpServer/HttpReverseProxyMiddleware.cs b/FastGithub.HttpServer/HttpMiddlewares/HttpReverseProxyMiddleware.cs similarity index 88% rename from FastGithub.HttpServer/HttpReverseProxyMiddleware.cs rename to FastGithub.HttpServer/HttpMiddlewares/HttpReverseProxyMiddleware.cs index c37eef0..d39ada9 100644 --- a/FastGithub.HttpServer/HttpReverseProxyMiddleware.cs +++ b/FastGithub.HttpServer/HttpMiddlewares/HttpReverseProxyMiddleware.cs @@ -8,7 +8,7 @@ using System.Net; using System.Threading.Tasks; using Yarp.ReverseProxy.Forwarder; -namespace FastGithub.HttpServer +namespace FastGithub.HttpServer.HttpMiddlewares { /// /// 反向代理中间件 @@ -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 /// 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; } diff --git a/FastGithub.HttpServer/IRequestLoggingFeature.cs b/FastGithub.HttpServer/HttpMiddlewares/IRequestLoggingFeature.cs similarity index 81% rename from FastGithub.HttpServer/IRequestLoggingFeature.cs rename to FastGithub.HttpServer/HttpMiddlewares/IRequestLoggingFeature.cs index 6a0a52f..c23e6fe 100644 --- a/FastGithub.HttpServer/IRequestLoggingFeature.cs +++ b/FastGithub.HttpServer/HttpMiddlewares/IRequestLoggingFeature.cs @@ -1,4 +1,4 @@ -namespace FastGithub.HttpServer +namespace FastGithub.HttpServer.HttpMiddlewares { /// /// 请求日志特性 diff --git a/FastGithub.HttpServer/RequestLoggingMilldeware.cs b/FastGithub.HttpServer/HttpMiddlewares/RequestLoggingMilldeware.cs similarity index 83% rename from FastGithub.HttpServer/RequestLoggingMilldeware.cs rename to FastGithub.HttpServer/HttpMiddlewares/RequestLoggingMilldeware.cs index 0450058..9959d8a 100644 --- a/FastGithub.HttpServer/RequestLoggingMilldeware.cs +++ b/FastGithub.HttpServer/HttpMiddlewares/RequestLoggingMilldeware.cs @@ -6,7 +6,7 @@ using System.Diagnostics; using System.Text; using System.Threading.Tasks; -namespace FastGithub.HttpServer +namespace FastGithub.HttpServer.HttpMiddlewares { /// /// 请求日志中间件 @@ -51,19 +51,19 @@ namespace FastGithub.HttpServer } var request = context.Request; - var response = context.Response; + var response = context.Response; 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)}"); } } diff --git a/FastGithub.HttpServer/HttpProxyMiddleware.cs b/FastGithub.HttpServer/HttpProxyMiddleware.cs deleted file mode 100644 index 86cb801..0000000 --- a/FastGithub.HttpServer/HttpProxyMiddleware.cs +++ /dev/null @@ -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 -{ - /// - /// http代理中间件 - /// - 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; - } - } - - /// - /// http代理中间件 - /// - /// - /// - /// - /// - 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); - } - - /// - /// 处理请求 - /// - /// - /// - 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(); - if (responseFeature != null) - { - responseFeature.ReasonPhrase = "Connection Established"; - } - context.Response.StatusCode = StatusCodes.Status200OK; - await context.Response.CompleteAsync(); - - var transport = context.Features.Get()?.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); - }); - } - } - - /// - /// 是否为fastgithub服务 - /// - /// - /// - 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); - } - - /// - /// 创建proxypac脚本 - /// - /// - /// - 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(); - } - - /// - /// 创建连接 - /// - /// - /// - /// - /// - private async Task CreateConnectionAsync(HostString host, CancellationToken cancellationToken) - { - var innerExceptions = new List(); - 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); - } - - /// - /// 获取目标终节点 - /// - /// - /// - /// - private async IAsyncEnumerable 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); - } - } - } - - /// - /// 创建httpHandler - /// - /// - private static SocketsHttpHandler CreateDefaultHttpHandler() - { - return new() - { - Proxy = null, - UseProxy = false, - UseCookies = false, - AllowAutoRedirect = false, - AutomaticDecompression = DecompressionMethods.None - }; - } - } -} \ No newline at end of file diff --git a/FastGithub.HttpServer/KestrelServerOptionsExtensions.cs b/FastGithub.HttpServer/KestrelServerExtensions.cs similarity index 65% rename from FastGithub.HttpServer/KestrelServerOptionsExtensions.cs rename to FastGithub.HttpServer/KestrelServerExtensions.cs index a9d46c7..ab1f7ed 100644 --- a/FastGithub.HttpServer/KestrelServerOptionsExtensions.cs +++ b/FastGithub.HttpServer/KestrelServerExtensions.cs @@ -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 /// /// Kestrel扩展 /// - public static class KestrelServerOptionsExtensions + public static class KestrelServerExtensions { /// /// 无限制 @@ -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(); + var tunnelMiddleware = kestrel.ApplicationServices.GetRequiredService(); + + 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代理服务启动完成"); } /// - /// 监听ssh反向代理 + /// 监听ssh协议代理 /// /// public static void ListenSshReverseProxy(this KestrelServerOptions kestrel) @@ -62,7 +73,7 @@ namespace FastGithub } /// - /// 监听git反向代理 + /// 监听git协议代理代理 /// /// public static void ListenGitReverseProxy(this KestrelServerOptions kestrel) @@ -99,10 +110,6 @@ namespace FastGithub /// public static void ListenHttpsReverseProxy(this KestrelServerOptions kestrel) { - var certService = kestrel.ApplicationServices.GetRequiredService(); - 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(); return loggerFactory.CreateLogger($"{nameof(FastGithub)}.{nameof(HttpServer)}"); } + + /// + /// 使用Tls中间件 + /// + /// + /// https配置 + /// + public static ListenOptions UseTls(this ListenOptions listen) + { + var certService = listen.ApplicationServices.GetRequiredService(); + certService.CreateCaCertIfNotExists(); + certService.InstallAndTrustCaCert(); + return listen.UseTls(https => https.ServerCertificateSelector = (ctx, domain) => certService.GetOrCreateServerCert(domain)); + } + + /// + /// 使用Tls中间件 + /// + /// + /// https配置 + /// + private static ListenOptions UseTls(this ListenOptions listen, Action configureOptions) + { + var invadeMiddleware = listen.ApplicationServices.GetRequiredService(); + var restoreMiddleware = listen.ApplicationServices.GetRequiredService(); + + listen.Use(next => context => invadeMiddleware.InvokeAsync(next, context)); + listen.UseHttps(configureOptions); + listen.Use(next => context => restoreMiddleware.InvokeAsync(next, context)); + return listen; + } } } diff --git a/FastGithub.HttpServer/ServiceCollectionExtensions.cs b/FastGithub.HttpServer/ServiceCollectionExtensions.cs index a3ba4d5..162786e 100644 --- a/FastGithub.HttpServer/ServiceCollectionExtensions.cs +++ b/FastGithub.HttpServer/ServiceCollectionExtensions.cs @@ -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() .AddSingleton() .AddSingleton() + + // tcp .AddSingleton() + .AddSingleton() + + // tls + .AddSingleton() + .AddSingleton() + + // http + .AddSingleton() .AddSingleton() .AddSingleton(); } diff --git a/FastGithub.HttpServer/GithubGitReverseProxyHandler.cs b/FastGithub.HttpServer/TcpMiddlewares/GithubGitReverseProxyHandler.cs similarity index 91% rename from FastGithub.HttpServer/GithubGitReverseProxyHandler.cs rename to FastGithub.HttpServer/TcpMiddlewares/GithubGitReverseProxyHandler.cs index 2888afc..f725a55 100644 --- a/FastGithub.HttpServer/GithubGitReverseProxyHandler.cs +++ b/FastGithub.HttpServer/TcpMiddlewares/GithubGitReverseProxyHandler.cs @@ -1,6 +1,6 @@ using FastGithub.DomainResolve; -namespace FastGithub.HttpServer +namespace FastGithub.HttpServer.TcpMiddlewares { /// /// github的git代理处理者 diff --git a/FastGithub.HttpServer/GithubSshReverseProxyHandler.cs b/FastGithub.HttpServer/TcpMiddlewares/GithubSshReverseProxyHandler.cs similarity index 91% rename from FastGithub.HttpServer/GithubSshReverseProxyHandler.cs rename to FastGithub.HttpServer/TcpMiddlewares/GithubSshReverseProxyHandler.cs index f6325ac..beb80cd 100644 --- a/FastGithub.HttpServer/GithubSshReverseProxyHandler.cs +++ b/FastGithub.HttpServer/TcpMiddlewares/GithubSshReverseProxyHandler.cs @@ -1,6 +1,6 @@ using FastGithub.DomainResolve; -namespace FastGithub.HttpServer +namespace FastGithub.HttpServer.TcpMiddlewares { /// /// github的ssh代理处理者 diff --git a/FastGithub.HttpServer/TcpMiddlewares/HttpProxyMiddleware.cs b/FastGithub.HttpServer/TcpMiddlewares/HttpProxyMiddleware.cs new file mode 100644 index 0000000..57eb764 --- /dev/null +++ b/FastGithub.HttpServer/TcpMiddlewares/HttpProxyMiddleware.cs @@ -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 +{ + /// + /// 正向代理中间件 + /// + sealed class HttpProxyMiddleware + { + private readonly HttpParser 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"); + + /// + /// 执行中间件 + /// + /// + /// + /// + 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(httpRequest); + await next(context); + } + } + + + /// + /// 获取http请求处理者 + /// + /// + /// + /// + private HttpRequestHandler GetHttpRequestHandler(ReadResult result, out long consumed) + { + var handler = new HttpRequestHandler(); + var reader = new SequenceReader(result.Buffer); + + if (this.httpParser.ParseRequestLine(handler, ref reader) && + this.httpParser.ParseHeaders(handler, ref reader)) + { + consumed = reader.Consumed; + } + else + { + consumed = 0L; + } + return handler; + } + + + /// + /// 代理请求处理器 + /// + 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 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 name, ReadOnlySpan value) + { + } + void IHttpHeadersHandler.OnHeadersComplete(bool endStream) + { + } + void IHttpHeadersHandler.OnStaticIndexedHeader(int index) + { + } + void IHttpHeadersHandler.OnStaticIndexedHeader(int index, ReadOnlySpan value) + { + } + } + } +} \ No newline at end of file diff --git a/FastGithub.HttpServer/TcpMiddlewares/IHttpProxyFeature.cs b/FastGithub.HttpServer/TcpMiddlewares/IHttpProxyFeature.cs new file mode 100644 index 0000000..1b4e9c6 --- /dev/null +++ b/FastGithub.HttpServer/TcpMiddlewares/IHttpProxyFeature.cs @@ -0,0 +1,11 @@ +using Microsoft.AspNetCore.Http; + +namespace FastGithub.HttpServer.TcpMiddlewares +{ + interface IHttpProxyFeature + { + HostString ProxyHost { get; } + + ProxyProtocol ProxyProtocol { get; } + } +} diff --git a/FastGithub.HttpServer/TcpMiddlewares/ProxyProtocol.cs b/FastGithub.HttpServer/TcpMiddlewares/ProxyProtocol.cs new file mode 100644 index 0000000..f16268c --- /dev/null +++ b/FastGithub.HttpServer/TcpMiddlewares/ProxyProtocol.cs @@ -0,0 +1,23 @@ +namespace FastGithub.HttpServer.TcpMiddlewares +{ + /// + /// 代理协议 + /// + enum ProxyProtocol + { + /// + /// 无代理 + /// + None, + + /// + /// http代理 + /// + HttpProxy, + + /// + /// 隧道代理 + /// + TunnelProxy + } +} diff --git a/FastGithub.HttpServer/TcpReverseProxyHandler.cs b/FastGithub.HttpServer/TcpMiddlewares/TcpReverseProxyHandler.cs similarity index 81% rename from FastGithub.HttpServer/TcpReverseProxyHandler.cs rename to FastGithub.HttpServer/TcpMiddlewares/TcpReverseProxyHandler.cs index 1456c99..a3c8d2e 100644 --- a/FastGithub.HttpServer/TcpReverseProxyHandler.cs +++ b/FastGithub.HttpServer/TcpMiddlewares/TcpReverseProxyHandler.cs @@ -9,10 +9,10 @@ using System.Net.Sockets; using System.Threading; using System.Threading.Tasks; -namespace FastGithub.HttpServer +namespace FastGithub.HttpServer.TcpMiddlewares { /// - /// tcp反射代理处理者 + /// tcp协议代理处理者 /// abstract class TcpReverseProxyHandler : ConnectionHandler { @@ -21,7 +21,7 @@ namespace FastGithub.HttpServer private readonly TimeSpan connectTimeout = TimeSpan.FromSeconds(10d); /// - /// tcp反射代理处理者 + /// tcp协议代理处理者 /// /// /// @@ -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 CreateConnectionAsync(CancellationToken cancellationToken) { var innerExceptions = new List(); - 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); } } } diff --git a/FastGithub.HttpServer/TcpMiddlewares/TunnelMiddleware.cs b/FastGithub.HttpServer/TcpMiddlewares/TunnelMiddleware.cs new file mode 100644 index 0000000..7f9af9f --- /dev/null +++ b/FastGithub.HttpServer/TcpMiddlewares/TunnelMiddleware.cs @@ -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 +{ + /// + /// 隧道中间件 + /// + sealed class TunnelMiddleware + { + private readonly FastGithubConfig fastGithubConfig; + private readonly IDomainResolver domainResolver; + private readonly TimeSpan connectTimeout = TimeSpan.FromSeconds(10d); + + /// + /// 隧道中间件 + /// + /// + /// + public TunnelMiddleware( + FastGithubConfig fastGithubConfig, + IDomainResolver domainResolver) + { + this.fastGithubConfig = fastGithubConfig; + this.domainResolver = domainResolver; + } + + /// + /// 执行中间件 + /// + /// + /// + /// + public async Task InvokeAsync(ConnectionDelegate next, ConnectionContext context) + { + var proxyFeature = context.Features.Get(); + if (proxyFeature == null || // 非代理 + proxyFeature.ProxyProtocol != ProxyProtocol.TunnelProxy || //非隧道代理 + context.Features.Get() != null) // 经过隧道的https + { + await next(context); + } + else + { + var transport = context.Features.Get()?.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); + } + } + } + + + /// + /// 创建连接 + /// + /// + /// + /// + /// + private async Task CreateConnectionAsync(HostString host, CancellationToken cancellationToken) + { + var innerExceptions = new List(); + 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); + } + + /// + /// 获取目标终节点 + /// + /// + /// + /// + private async IAsyncEnumerable 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); + } + } + } + } +} diff --git a/FastGithub.HttpServer/TlsMiddlewares/FakeTlsConnectionFeature.cs b/FastGithub.HttpServer/TlsMiddlewares/FakeTlsConnectionFeature.cs new file mode 100644 index 0000000..9dcbc89 --- /dev/null +++ b/FastGithub.HttpServer/TlsMiddlewares/FakeTlsConnectionFeature.cs @@ -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 +{ + /// + /// 假冒的TlsConnectionFeature + /// + sealed class FakeTlsConnectionFeature : ITlsConnectionFeature + { + public static FakeTlsConnectionFeature Instance { get; } = new FakeTlsConnectionFeature(); + + public X509Certificate2? ClientCertificate + { + get => throw new NotImplementedException(); + set => throw new NotImplementedException(); + } + + public Task GetClientCertificateAsync(CancellationToken cancellationToken) + { + throw new NotImplementedException(); + } + } +} diff --git a/FastGithub.HttpServer/TlsMiddlewares/TlsInvadeMiddleware.cs b/FastGithub.HttpServer/TlsMiddlewares/TlsInvadeMiddleware.cs new file mode 100644 index 0000000..548da17 --- /dev/null +++ b/FastGithub.HttpServer/TlsMiddlewares/TlsInvadeMiddleware.cs @@ -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 +{ + /// + /// https入侵中间件 + /// + sealed class TlsInvadeMiddleware + { + /// + /// 执行中间件 + /// + /// + /// + public async Task InvokeAsync(ConnectionDelegate next, ConnectionContext context) + { + // 连接不是tls + if (await IsTlsConnectionAsync(context) == false) + { + // 没有任何tls中间件执行过 + if (context.Features.Get() == null) + { + // 设置假的ITlsConnectionFeature,迫使https中间件跳过自身的工作 + context.Features.Set(FakeTlsConnectionFeature.Instance); + } + } + await next(context); + } + + + /// + /// 是否为tls协议 + /// + /// + /// + private static async Task 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(result.Buffer); + return reader.TryRead(out var firstByte) && + reader.TryRead(out var nextByte) && + firstByte == 0x16 && + nextByte == 0x3; + } + } + } +} diff --git a/FastGithub.HttpServer/TlsMiddlewares/TlsRestoreMiddleware.cs b/FastGithub.HttpServer/TlsMiddlewares/TlsRestoreMiddleware.cs new file mode 100644 index 0000000..c9df0ff --- /dev/null +++ b/FastGithub.HttpServer/TlsMiddlewares/TlsRestoreMiddleware.cs @@ -0,0 +1,27 @@ +using Microsoft.AspNetCore.Connections; +using Microsoft.AspNetCore.Http.Features; +using System.Threading.Tasks; + +namespace FastGithub.HttpServer.TlsMiddlewares +{ + /// + /// https恢复中间件 + /// + sealed class TlsRestoreMiddleware + { + /// + /// 执行中间件 + /// + /// + /// + public async Task InvokeAsync(ConnectionDelegate next, ConnectionContext context) + { + if (context.Features.Get() == FakeTlsConnectionFeature.Instance) + { + // 擦除入侵 + context.Features.Set(null); + } + await next(context); + } + } +} diff --git a/FastGithub/Program.cs b/FastGithub/Program.cs index bb94608..2c2129b 100644 --- a/FastGithub/Program.cs +++ b/FastGithub/Program.cs @@ -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(); } diff --git a/FastGithub/Startup.cs b/FastGithub/Startup.cs index c58df1f..1e6b29c 100644 --- a/FastGithub/Startup.cs +++ b/FastGithub/Startup.cs @@ -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,26 +52,18 @@ namespace FastGithub /// public void Configure(IApplicationBuilder app) { - var httpProxyPort = app.ApplicationServices.GetRequiredService>().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 => + app.UseRouting(); + app.DisableRequestLogging(); + app.UseEndpoints(endpoint => { - appBuilder.UseRequestLogging(); - appBuilder.UseHttpReverseProxy(); - - appBuilder.UseRouting(); - appBuilder.DisableRequestLogging(); - appBuilder.UseEndpoints(endpoint => + endpoint.MapGet("/flowStatistics", context => { - endpoint.MapGet("/flowStatistics", context => - { - var flowStatistics = context.RequestServices.GetRequiredService().GetFlowStatistics(); - return context.Response.WriteAsJsonAsync(flowStatistics); - }); + var flowStatistics = context.RequestServices.GetRequiredService().GetFlowStatistics(); + return context.Response.WriteAsJsonAsync(flowStatistics); }); }); }