HttpProxyMiddleware.cs 6.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175
  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.Extensions.Options;
  7. using System.IO.Pipelines;
  8. using System.Net;
  9. using System.Net.Http;
  10. using System.Net.Sockets;
  11. using System.Text;
  12. using System.Threading.Tasks;
  13. using Yarp.ReverseProxy.Forwarder;
  14. namespace FastGithub.HttpServer
  15. {
  16. /// <summary>
  17. /// http代理中间件
  18. /// </summary>
  19. sealed class HttpProxyMiddleware
  20. {
  21. private const string LOOPBACK = "127.0.0.1";
  22. private const string LOCALHOST = "localhost";
  23. private readonly FastGithubConfig fastGithubConfig;
  24. private readonly IDomainResolver domainResolver;
  25. private readonly IHttpForwarder httpForwarder;
  26. private readonly IOptions<FastGithubOptions> options;
  27. private readonly HttpMessageInvoker httpClient;
  28. /// <summary>
  29. /// http代理中间件
  30. /// </summary>
  31. /// <param name="fastGithubConfig"></param>
  32. /// <param name="domainResolver"></param>
  33. /// <param name="httpForwarder"></param>
  34. /// <param name="options"></param>
  35. public HttpProxyMiddleware(
  36. FastGithubConfig fastGithubConfig,
  37. IDomainResolver domainResolver,
  38. IHttpForwarder httpForwarder,
  39. IOptions<FastGithubOptions> options)
  40. {
  41. this.fastGithubConfig = fastGithubConfig;
  42. this.domainResolver = domainResolver;
  43. this.httpForwarder = httpForwarder;
  44. this.options = options;
  45. this.httpClient = new HttpMessageInvoker(CreateHttpHandler(), disposeHandler: false);
  46. }
  47. /// <summary>
  48. /// 处理请求
  49. /// </summary>
  50. /// <param name="context"></param>
  51. /// <returns></returns>
  52. public async Task InvokeAsync(HttpContext context)
  53. {
  54. var host = context.Request.Host;
  55. if (this.IsFastGithubServer(host) == true)
  56. {
  57. var proxyPac = this.GetProxyPacString(host);
  58. context.Response.ContentType = "application/x-ns-proxy-autoconfig";
  59. context.Response.Headers.Add("Content-Disposition", $"attachment;filename=proxy.pac");
  60. await context.Response.WriteAsync(proxyPac);
  61. }
  62. else if (context.Request.Method == HttpMethods.Connect)
  63. {
  64. var endpoint = await this.GetTargetEndPointAsync(host);
  65. using var targetSocket = new Socket(SocketType.Stream, ProtocolType.Tcp);
  66. await targetSocket.ConnectAsync(endpoint);
  67. context.Response.StatusCode = StatusCodes.Status200OK;
  68. context.Features.Get<IHttpResponseFeature>().ReasonPhrase = "Connection Established";
  69. await context.Response.CompleteAsync();
  70. var transport = context.Features.Get<IConnectionTransportFeature>()?.Transport;
  71. if (transport != null)
  72. {
  73. var targetStream = new NetworkStream(targetSocket, ownsSocket: false);
  74. var task1 = targetStream.CopyToAsync(transport.Output);
  75. var task2 = transport.Input.CopyToAsync(targetStream);
  76. await Task.WhenAny(task1, task2);
  77. }
  78. }
  79. else
  80. {
  81. var destinationPrefix = $"{context.Request.Scheme}://{context.Request.Host}";
  82. await this.httpForwarder.SendAsync(context, destinationPrefix, this.httpClient);
  83. }
  84. }
  85. /// <summary>
  86. /// 是否为fastgithub服务
  87. /// </summary>
  88. /// <param name="host"></param>
  89. /// <returns></returns>
  90. private bool IsFastGithubServer(HostString host)
  91. {
  92. if (host.Host == LOOPBACK || host.Host == LOCALHOST)
  93. {
  94. return host.Port == this.options.Value.HttpProxyPort;
  95. }
  96. return false;
  97. }
  98. /// <summary>
  99. /// 获取proxypac脚本
  100. /// </summary>
  101. /// <param name="host"></param>
  102. /// <returns></returns>
  103. private string GetProxyPacString(HostString host)
  104. {
  105. var buidler = new StringBuilder();
  106. buidler.AppendLine("function FindProxyForURL(url, host){");
  107. buidler.AppendLine($" var proxy = 'PROXY {host.Host}';");
  108. foreach (var domain in this.fastGithubConfig.GetDomainPatterns())
  109. {
  110. buidler.AppendLine($" if (shExpMatch(host, '{domain}')) return proxy;");
  111. }
  112. buidler.AppendLine(" return 'DIRECT';");
  113. buidler.AppendLine("}");
  114. return buidler.ToString();
  115. }
  116. /// <summary>
  117. /// 获取目标终节点
  118. /// </summary>
  119. /// <param name="host"></param>
  120. /// <returns></returns>
  121. private async Task<EndPoint> GetTargetEndPointAsync(HostString host)
  122. {
  123. const int HTTPS_PORT = 443;
  124. var targetHost = host.Host;
  125. var targetPort = host.Port ?? HTTPS_PORT;
  126. if (IPAddress.TryParse(targetHost, out var address) == true)
  127. {
  128. return new IPEndPoint(address, targetPort);
  129. }
  130. // 不关心的域名,直接使用系统dns
  131. if (this.fastGithubConfig.IsMatch(targetHost) == false)
  132. {
  133. return new DnsEndPoint(targetHost, targetPort);
  134. }
  135. // 目标端口为443,走https代理中间人
  136. if (targetPort == HTTPS_PORT)
  137. {
  138. return new IPEndPoint(IPAddress.Loopback, HttpsReverseProxyPort.Value);
  139. }
  140. // dns优选
  141. address = await this.domainResolver.ResolveAsync(new DnsEndPoint(targetHost, targetPort));
  142. return new IPEndPoint(address, targetPort);
  143. }
  144. /// <summary>
  145. /// 创建httpHandler
  146. /// </summary>
  147. /// <returns></returns>
  148. private static SocketsHttpHandler CreateHttpHandler()
  149. {
  150. return new()
  151. {
  152. Proxy = null,
  153. UseProxy = false,
  154. UseCookies = false,
  155. AllowAutoRedirect = false,
  156. AutomaticDecompression = DecompressionMethods.None
  157. };
  158. }
  159. }
  160. }