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); yield break; } // 不关心的域名,直接使用系统dns if (this.fastGithubConfig.IsMatch(targetHost) == false) { yield return new DnsEndPoint(targetHost, targetPort); yield break; } if (targetPort == HTTP_PORT) { yield return new IPEndPoint(IPAddress.Loopback, GlobalListener.HttpPort); yield break; } if (targetPort == HTTPS_PORT) { yield return new IPEndPoint(IPAddress.Loopback, GlobalListener.HttpsPort); yield break; } 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 }; } } }