2
0

HttpProxyMiddleware.cs 7.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206
  1. using FastGithub.Configuration;
  2. using FastGithub.DomainResolve;
  3. using Microsoft.AspNetCore.Connections.Features;
  4. using Microsoft.AspNetCore.Http;
  5. using Microsoft.AspNetCore.Http.Features;
  6. using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http;
  7. using System.IO.Pipelines;
  8. using System.Net;
  9. using System.Net.Http;
  10. using System.Net.Sockets;
  11. using System.Reflection;
  12. using System.Text;
  13. using System.Threading.Tasks;
  14. using Yarp.ReverseProxy.Forwarder;
  15. namespace FastGithub.HttpServer
  16. {
  17. /// <summary>
  18. /// http代理中间件
  19. /// </summary>
  20. sealed class HttpProxyMiddleware
  21. {
  22. private const string LOCALHOST = "localhost";
  23. private const int HTTP_PORT = 80;
  24. private const int HTTPS_PORT = 443;
  25. private readonly FastGithubConfig fastGithubConfig;
  26. private readonly IDomainResolver domainResolver;
  27. private readonly IHttpForwarder httpForwarder;
  28. private readonly HttpReverseProxyMiddleware httpReverseProxy;
  29. private readonly HttpMessageInvoker defaultHttpClient;
  30. static HttpProxyMiddleware()
  31. {
  32. // https://github.com/dotnet/aspnetcore/issues/37421
  33. var authority = typeof(HttpParser<>).Assembly
  34. .GetType("Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Infrastructure.HttpCharacters")?
  35. .GetField("_authority", BindingFlags.NonPublic | BindingFlags.Static)?
  36. .GetValue(null);
  37. if (authority is bool[] authorityArray)
  38. {
  39. authorityArray['-'] = true;
  40. }
  41. }
  42. /// <summary>
  43. /// http代理中间件
  44. /// </summary>
  45. /// <param name="fastGithubConfig"></param>
  46. /// <param name="domainResolver"></param>
  47. /// <param name="httpForwarder"></param>
  48. /// <param name="httpReverseProxy"></param>
  49. public HttpProxyMiddleware(
  50. FastGithubConfig fastGithubConfig,
  51. IDomainResolver domainResolver,
  52. IHttpForwarder httpForwarder,
  53. HttpReverseProxyMiddleware httpReverseProxy)
  54. {
  55. this.fastGithubConfig = fastGithubConfig;
  56. this.domainResolver = domainResolver;
  57. this.httpForwarder = httpForwarder;
  58. this.httpReverseProxy = httpReverseProxy;
  59. this.defaultHttpClient = new HttpMessageInvoker(CreateDefaultHttpHandler(), disposeHandler: false);
  60. }
  61. /// <summary>
  62. /// 处理请求
  63. /// </summary>
  64. /// <param name="context"></param>
  65. /// <returns></returns>
  66. public async Task InvokeAsync(HttpContext context)
  67. {
  68. var host = context.Request.Host;
  69. if (this.IsFastGithubServer(host) == true)
  70. {
  71. var proxyPac = this.CreateProxyPac(host);
  72. context.Response.ContentType = "application/x-ns-proxy-autoconfig";
  73. context.Response.Headers.Add("Content-Disposition", $"attachment;filename=proxy.pac");
  74. await context.Response.WriteAsync(proxyPac);
  75. }
  76. else if (context.Request.Method == HttpMethods.Connect)
  77. {
  78. var endpoint = await this.GetTargetEndPointAsync(host);
  79. using var targetSocket = new Socket(SocketType.Stream, ProtocolType.Tcp);
  80. await targetSocket.ConnectAsync(endpoint);
  81. var responseFeature = context.Features.Get<IHttpResponseFeature>();
  82. if (responseFeature != null)
  83. {
  84. responseFeature.ReasonPhrase = "Connection Established";
  85. }
  86. context.Response.StatusCode = StatusCodes.Status200OK;
  87. await context.Response.CompleteAsync();
  88. var transport = context.Features.Get<IConnectionTransportFeature>()?.Transport;
  89. if (transport != null)
  90. {
  91. var targetStream = new NetworkStream(targetSocket, ownsSocket: false);
  92. await Task.WhenAny(targetStream.CopyToAsync(transport.Output), transport.Input.CopyToAsync(targetStream));
  93. }
  94. }
  95. else
  96. {
  97. await this.httpReverseProxy.InvokeAsync(context, async next =>
  98. {
  99. var destinationPrefix = $"{context.Request.Scheme}://{context.Request.Host}";
  100. await this.httpForwarder.SendAsync(context, destinationPrefix, this.defaultHttpClient);
  101. });
  102. }
  103. }
  104. /// <summary>
  105. /// 是否为fastgithub服务
  106. /// </summary>
  107. /// <param name="host"></param>
  108. /// <returns></returns>
  109. private bool IsFastGithubServer(HostString host)
  110. {
  111. if (host.Port != this.fastGithubConfig.HttpProxyPort)
  112. {
  113. return false;
  114. }
  115. if (host.Host == LOCALHOST)
  116. {
  117. return true;
  118. }
  119. return IPAddress.TryParse(host.Host, out var address) && IPAddress.IsLoopback(address);
  120. }
  121. /// <summary>
  122. /// 创建proxypac脚本
  123. /// </summary>
  124. /// <param name="proxyHost"></param>
  125. /// <returns></returns>
  126. private string CreateProxyPac(HostString proxyHost)
  127. {
  128. var buidler = new StringBuilder();
  129. buidler.AppendLine("function FindProxyForURL(url, host){");
  130. buidler.AppendLine($" var fastgithub = 'PROXY {proxyHost}';");
  131. foreach (var domain in this.fastGithubConfig.GetDomainPatterns())
  132. {
  133. buidler.AppendLine($" if (shExpMatch(host, '{domain}')) return fastgithub;");
  134. }
  135. buidler.AppendLine(" return 'DIRECT';");
  136. buidler.AppendLine("}");
  137. return buidler.ToString();
  138. }
  139. /// <summary>
  140. /// 获取目标终节点
  141. /// </summary>
  142. /// <param name="host"></param>
  143. /// <returns></returns>
  144. private async Task<EndPoint> GetTargetEndPointAsync(HostString host)
  145. {
  146. var targetHost = host.Host;
  147. var targetPort = host.Port ?? HTTPS_PORT;
  148. if (IPAddress.TryParse(targetHost, out var address) == true)
  149. {
  150. return new IPEndPoint(address, targetPort);
  151. }
  152. // 不关心的域名,直接使用系统dns
  153. if (this.fastGithubConfig.IsMatch(targetHost) == false)
  154. {
  155. return new DnsEndPoint(targetHost, targetPort);
  156. }
  157. if (targetPort == HTTP_PORT)
  158. {
  159. return new IPEndPoint(IPAddress.Loopback, ReverseProxyPort.Http);
  160. }
  161. if (targetPort == HTTPS_PORT)
  162. {
  163. return new IPEndPoint(IPAddress.Loopback, ReverseProxyPort.Https);
  164. }
  165. // 不使用系统dns
  166. address = await this.domainResolver.ResolveAnyAsync(new DnsEndPoint(targetHost, targetPort));
  167. return new IPEndPoint(address, targetPort);
  168. }
  169. /// <summary>
  170. /// 创建httpHandler
  171. /// </summary>
  172. /// <returns></returns>
  173. private static SocketsHttpHandler CreateDefaultHttpHandler()
  174. {
  175. return new()
  176. {
  177. Proxy = null,
  178. UseProxy = false,
  179. UseCookies = false,
  180. AllowAutoRedirect = false,
  181. AutomaticDecompression = DecompressionMethods.None
  182. };
  183. }
  184. }
  185. }