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.IO.Pipelines;
using System.Net;
using System.Net.Http;
using System.Net.Sockets;
using System.Reflection;
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 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;
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 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)
{
return host.Port == this.fastGithubConfig.HttpProxyPort && (host.Host == LOOPBACK || host.Host == LOCALHOST);
}
///
/// 创建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)
{
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(new DnsEndPoint(targetHost, targetPort));
return new IPEndPoint(address, targetPort);
}
///
/// 创建httpHandler
///
///
private static SocketsHttpHandler CreateDefaultHttpHandler()
{
return new()
{
Proxy = null,
UseProxy = false,
UseCookies = false,
AllowAutoRedirect = false,
AutomaticDecompression = DecompressionMethods.None
};
}
}
}