From d13dec650df2840dad3ef588fa98a0ebae077468 Mon Sep 17 00:00:00 2001 From: Elivo Date: Fri, 22 Aug 2025 15:49:34 +0800 Subject: [PATCH] =?UTF-8?q?=E6=B7=BB=E5=8A=A0=20HttpProxy=20=E6=97=A7?= =?UTF-8?q?=E4=BB=A3=E7=A0=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Apewer.Web/Web/HttpProxy/HttpHandler.cs | 285 ++++++++++++++++++++++++ Apewer.Web/Web/HttpProxy/HttpModule.cs | 53 +++++ 2 files changed, 338 insertions(+) create mode 100644 Apewer.Web/Web/HttpProxy/HttpHandler.cs create mode 100644 Apewer.Web/Web/HttpProxy/HttpModule.cs diff --git a/Apewer.Web/Web/HttpProxy/HttpHandler.cs b/Apewer.Web/Web/HttpProxy/HttpHandler.cs new file mode 100644 index 0000000..a022393 --- /dev/null +++ b/Apewer.Web/Web/HttpProxy/HttpHandler.cs @@ -0,0 +1,285 @@ +#if NETFRAMEWORK + +using System; +using System.Collections.Generic; +using System.IO; +using System.Net; +using System.Net.Security; +using System.Security.Cryptography.X509Certificates; +using System.Web; + +namespace Apewer.Web.HttpProxy +{ + + sealed class HttpHandler : IHttpHandler + { + + public bool IsReusable { get { return false; } } + + public void ProcessRequest(HttpContext context) + { + if (context.Request.HttpMethod.ToLower().Contains("options")) return; + + context.Response.Buffer = true; + try + { + Proxy(context); + } + catch (Exception exception) + { + try + { + context.Response.Clear(); + context.Response.ContentType = "text/plain"; + context.Response.Write(exception.ToString()); + } + catch { } + } + } + + // const int SegmentalUrlOffset = 0; + + static void Proxy(HttpContext context) + { + var url = GetUrl(context.Request.Url); + + context.Response.Headers.Add("-Proxy-Url", url); + context.Response.Headers.Add("-Proxy-Http-X-Forwarded-For", Variable("http_x_forwarded_for")); + context.Response.Headers.Add("-Proxy-Remote-Addr", Variable("remote_addr")); + + // 忽略跨域选项请求。 + if (context.Request.HttpMethod.ToLower().Contains("options")) return; + + // 阻止浏览器请求网站图标。 + if (context.Request.Url.AbsolutePath.ToLower() == "/favicon.ico") return; + + // 阻止搜索引擎收录。 + if (context.Request.Url.AbsolutePath.ToLower() == "/robot.txt") + { + context.Response.ContentType = "text/plain"; + context.Response.Write("User-agent: *\nDisallow: / \n"); + return; + } + + var ShowStack = false; + try + { + context.Response.Buffer = false; + + var request = GetRequest(url, context.Request); + var response = GetResponse(request); + + WriteHeaders(response, context.Response); + WriteStream(response, context.Response); + } + catch (System.Net.Sockets.SocketException exception) // 无法连接服务器。 + { + context.Response.ContentType = "text/plain"; + context.Response.Write(ShowStack ? exception.ToString() : exception.Message); + } + catch (System.Net.WebException exception) // 无法连接服务器;超时。 + { + context.Response.ContentType = "text/plain"; + context.Response.Write(ShowStack ? exception.ToString() : exception.Message); + } + catch (System.UriFormatException exception) // 链接无效。 + { + context.Response.ContentType = "text/plain"; + context.Response.Write(ShowStack ? exception.ToString() : exception.Message); + } + + context.Response.Flush(); + context.Response.Close(); + } + + static string GetUrl(Uri uri) + { + var paq = uri.PathAndQuery; + var array = uri.PathAndQuery.Split('/'); + if (array.Length < 2) return null; + + var lower = string.IsNullOrEmpty(array[1]) ? null : array[1].ToLower(); + var start = 1; + var fragments = new List(); + switch (lower) + { + case "http:": + case "https:": + fragments.Add(lower); + start = 2; + break; + default: + start = 1; + var scheme = uri.Scheme.ToLower(); + if (!scheme.EndsWith(":")) scheme = scheme + ":"; + fragments.Add(scheme); + break; + } + fragments.Add(""); + + for (var i = start; i < array.Length; i++) fragments.Add(array[i]); + var url = string.Join("/", fragments.ToArray()); + return url; + } + + static HttpWebRequest GetRequest(string url, HttpRequest input) + { + var https = url.ToLower().Trim().StartsWith("https"); + if (https) ServicePointManager.ServerCertificateValidationCallback = new RemoteCertificateValidationCallback(ValidateCallback); + + var request = (HttpWebRequest)WebRequest.Create(url); + request.Method = input.HttpMethod; + request.AllowAutoRedirect = true; + + request.Timeout = 60000; + + // Headers + foreach (var key in input.Headers.AllKeys) + { + try + { + if (string.IsNullOrEmpty(key)) continue; + + var value = input.Headers[key]; + if (string.IsNullOrEmpty(value)) continue; + + var lower = key.ToLower(); + if (lower == "accept") continue; + else if (lower == "accept-encoding") continue; + else if (lower == "ali-swift-log-host") continue; + else if (lower == "ali-swift-stat-host") continue; + else if (lower == "cache-control") continue; + else if (lower == "connection") continue; + else if (lower == "dnt") continue; + else if (lower == "eagleeye-traceid") continue; + else if (lower == "host") continue; + else if (lower == "if-modified-since") request.IfModifiedSince = new DateTime(1970, 1, 1, 0, 0, 0, 0); + else if (lower == "referer") request.Referer = value; + else if (lower == "timing-allow-origin") continue; + else if (lower == "user-agent") request.UserAgent = value; + else if (lower == "via") continue; + else if (lower == "x-alicdn-da-via") continue; + else request.Headers.Add(key, value); + } + catch { } + } + + // POST + if (request.Method.ToLower().Contains("post")) ReadStream(input.InputStream, request.GetRequestStream()); + + return request; + } + + static HttpWebResponse GetResponse(HttpWebRequest request) + { + return (HttpWebResponse)request.GetResponse(); + } + + static void WriteHeaders(HttpWebResponse response, HttpResponse output) + { + foreach (var key in response.Headers.AllKeys) + { + try + { + if (string.IsNullOrEmpty(key)) continue; + + var value = response.Headers[key]; + if (string.IsNullOrEmpty(value)) continue; + + output.Headers.Add("-Origin-" + key, value); + + var lower = key.ToLower(); + if (lower == "access-control-allow-origin") continue; + else if (lower == "access-control-allow-methods") continue; + else if (lower == "connection") continue; + else if (lower == "content-type") output.ContentType = value; + else if (lower == "timing-allow-origin") continue; + else if (lower == "transfer-encoding") continue; + else if (lower == "x-content-type-options") continue; + else output.Headers.Add(key, value); + } + catch { } + } + } + + static void WriteStream(HttpWebResponse response, HttpResponse output) + { + ReadStream(response.GetResponseStream(), output.OutputStream); + } + + static bool ValidateCallback(object sender, X509Certificate certificate, X509Chain chain, SslPolicyErrors errors) + { + //return (errors == SslPolicyErrors.None) ? true : false; + return true; + } + + static string QueryString(string name) + { + try + { + if (!string.IsNullOrEmpty(name)) return HttpContext.Current.Request.QueryString[name]; + } + finally { } + return ""; + } + + static Int32 GetInt32(string text) + { + try + { + if (!string.IsNullOrEmpty(text)) return Convert.ToInt32(text); + } + catch { } + return 0; + } + + static long ReadStream(Stream source, Stream destination) + { + var buffer = 1024; + + if (source == null) return 0; + if (destination == null) return 0; + if (!source.CanRead) return 0; + if (!destination.CanWrite) return 0; + if (buffer < 1) return 0; + + long result = 0; + try + { + while (true) + { + var temp = new byte[buffer]; + var count = source.Read(temp, 0, temp.Length); + if (count == 0) break; + destination.Write(temp, 0, count); + result += count; + } + } + finally { } + return result; + } + + static string Variable(string name) + { + var result = ""; + if (!string.IsNullOrEmpty(name)) + { + string vname = name.ToLower(); + try { result = HttpContext.Current.Request.ServerVariables[vname]; } + finally { } + } + return result ?? ""; + } + + static string ClientIP() + { + string vip = Variable("http_x_forwarded_for"); + if (string.IsNullOrEmpty(vip)) vip = Variable("remote_addr"); + return vip; + } + + } + +} + +#endif diff --git a/Apewer.Web/Web/HttpProxy/HttpModule.cs b/Apewer.Web/Web/HttpProxy/HttpModule.cs new file mode 100644 index 0000000..1d6ee61 --- /dev/null +++ b/Apewer.Web/Web/HttpProxy/HttpModule.cs @@ -0,0 +1,53 @@ +#if NETFRAMEWORK + +using System; +using System.Collections.Generic; +using System.Web; + +namespace Apewer.Web.HttpProxy +{ + + /// + sealed class HttpModule : IHttpModule + { + + /// + public void Dispose() { } + + /// + public void Init(HttpApplication context) + { + context.PreSendRequestHeaders += new EventHandler(ByContext); + } + + /// + void ByContext(object sender, EventArgs e) + { + var context = HttpContext.Current; + if (context == null) return; + + var keys = new List(); + keys.AddRange(context.Response.Headers.AllKeys); + if (keys.Contains("Server")) context.Response.Headers.Remove("Server"); + if (keys.Contains("X-Powered-By")) context.Response.Headers.Remove("X-Powered-By"); + } + + /// + void ByApplication(object sender, EventArgs e) + { + var app = sender as HttpApplication; + if (app == null) return; + if (app.Request == null) return; + if (app.Request.IsLocal == false) return; + if (app.Context == null) return; + if (app.Context.Response == null) return; + if (app.Context.Response.Headers == null) return; + var headers = app.Context.Response.Headers; + app.Context.Response.Headers.Remove("Server"); + } + + } + +} + +#endif