2
0

HttpProxyMiddleware.cs 5.5 KB

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