using FastGithub.Configuration; using FastGithub.DomainResolve; using Microsoft.AspNetCore.Connections.Features; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Http.Features; using System.IO.Pipelines; using System.Net; using System.Net.Http; using System.Net.Sockets; using System.Text; using System.Threading.Tasks; using Yarp.ReverseProxy.Forwarder; namespace FastGithub.HttpServer { /// /// http代理中间件 /// sealed class HttpProxyMiddleware { private const string LOOPBACK = "127.0.0.1"; private const string LOCALHOST = "localhost"; private readonly FastGithubConfig fastGithubConfig; private readonly IDomainResolver domainResolver; private readonly IHttpForwarder httpForwarder; private readonly HttpReverseProxyMiddleware httpReverseProxy; private readonly HttpMessageInvoker defaultHttpClient; /// /// 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 endpoint = await this.GetTargetEndPointAsync(host); using var targetSocket = new Socket(SocketType.Stream, ProtocolType.Tcp); await targetSocket.ConnectAsync(endpoint); context.Response.StatusCode = StatusCodes.Status200OK; context.Features.Get().ReasonPhrase = "Connection Established"; await context.Response.CompleteAsync(); var transport = context.Features.Get()?.Transport; if (transport != null) { var targetStream = new NetworkStream(targetSocket, ownsSocket: false); await Task.WhenAny(targetStream.CopyToAsync(transport.Output), transport.Input.CopyToAsync(targetStream)); } } else { await this.httpReverseProxy.InvokeAsync(context, async ctx => { var destinationPrefix = $"{ctx.Request.Scheme}://{ctx.Request.Host}"; await this.httpForwarder.SendAsync(ctx, destinationPrefix, this.defaultHttpClient); }); } } /// /// 是否为fastgithub服务 /// /// /// private bool IsFastGithubServer(HostString host) { if (host.Port == this.fastGithubConfig.HttpProxyPort) { return host.Host == LOOPBACK || host.Host == LOCALHOST; } return false; } /// /// 创建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 GetTargetEndPointAsync(HostString host) { const int HTTP_PORT = 80; const int HTTPS_PORT = 443; var targetHost = host.Host; var targetPort = host.Port ?? HTTPS_PORT; if (IPAddress.TryParse(targetHost, out var address) == true) { return new IPEndPoint(address, targetPort); } // 不关心的域名,直接使用系统dns if (this.fastGithubConfig.IsMatch(targetHost) == false) { return new DnsEndPoint(targetHost, targetPort); } if (targetPort == HTTP_PORT) { return new IPEndPoint(IPAddress.Loopback, ReverseProxyPort.Http); } if (targetPort == HTTPS_PORT) { return new IPEndPoint(IPAddress.Loopback, ReverseProxyPort.Https); } // 不使用系统dns address = await this.domainResolver.ResolveAnyAsync(targetHost); return new IPEndPoint(address, targetPort); } /// /// 创建httpHandler /// /// private static SocketsHttpHandler CreateDefaultHttpHandler() { return new() { Proxy = null, UseProxy = false, UseCookies = false, AllowAutoRedirect = false, AutomaticDecompression = DecompressionMethods.None }; } } }