.NET Framework实现反向代理:全请求转发与自定义请求修改
Hey there! Since you're looking for a .NET Framework-based reverse proxy (no external libraries) to forward all requests from your DMZ web server to your internal one—while supporting all HTTP methods, content types, and allowing request manipulation—I've got you covered.
This solution leans entirely on .NET Framework's built-in HttpWebRequest and ASP.NET pipeline components, perfect for your security architecture where the DMZ server can't directly access the database, but the internal server can.
We'll use an ASP.NET Generic Handler (.ashx) for the proxy logic—it's lightweight and easy to route all requests through. Create a new handler named ProxyHandler.ashx in your .NET Framework web project.
Here's the full handler code, with comments explaining each step. It handles request forwarding, header/body manipulation, and response propagation:
using System; using System.IO; using System.Net; using System.Web; public class ProxyHandler : IHttpHandler { // Configure your internal server base URL here (or pull from web.config) private readonly string _internalServerBaseUrl = "http://your-internal-web-server-address/"; public void ProcessRequest(HttpContext context) { var incomingReq = context.Request; var outgoingResp = context.Response; // Build the full target URL using the incoming request's path/query var targetUrl = $"{_internalServerBaseUrl}{incomingReq.Url.PathAndQuery.TrimStart('/')}"; // Initialize the request to the internal server var targetReq = (HttpWebRequest)WebRequest.Create(targetUrl); targetReq.Method = incomingReq.HttpMethod; // Copy incoming request headers (skip restricted headers that HttpWebRequest manages) foreach (string headerName in incomingReq.Headers) { if (headerName.Equals("Host", StringComparison.OrdinalIgnoreCase) || headerName.Equals("Content-Length", StringComparison.OrdinalIgnoreCase) || headerName.Equals("Transfer-Encoding", StringComparison.OrdinalIgnoreCase)) { continue; } targetReq.Headers[headerName] = incomingReq.Headers[headerName]; } // Add custom headers to the forwarded request (example) targetReq.Headers.Add("X-DMZ-Proxy", "Forwarded-from-public-server"); // Handle request body (for POST/PUT/PATCH etc.) if (!string.IsNullOrEmpty(incomingReq.ContentType) && incomingReq.ContentLength > 0) { targetReq.ContentType = incomingReq.ContentType; // Optional: Edit the request body here before forwarding // Example below shows reading, modifying, and re-writing the body using (var incomingStream = incomingReq.InputStream) using (var memoryStream = new MemoryStream()) { incomingStream.CopyTo(memoryStream); memoryStream.Position = 0; // Modify the body (e.g., replace a value in JSON/Form data) var bodyContent = new StreamReader(memoryStream).ReadToEnd(); bodyContent = bodyContent.Replace("placeholder-value", "dmz-modified-value"); // Write the modified body to the target request var modifiedBytes = System.Text.Encoding.UTF8.GetBytes(bodyContent); targetReq.ContentLength = modifiedBytes.Length; using (var targetStream = targetReq.GetRequestStream()) { targetStream.Write(modifiedBytes, 0, modifiedBytes.Length); } } } // Fetch and forward the internal server's response try { using (var targetResp = (HttpWebResponse)targetReq.GetResponse()) { // Propagate status code and headers outgoingResp.StatusCode = (int)targetResp.StatusCode; foreach (string headerName in targetResp.Headers) { outgoingResp.Headers[headerName] = targetResp.Headers[headerName]; } // Propagate response body using (var responseStream = targetResp.GetResponseStream()) { responseStream.CopyTo(outgoingResp.OutputStream); } } } catch (WebException ex) { // Handle errors from the internal server (e.g., 404, 500) if (ex.Response is HttpWebResponse errorResp) { outgoingResp.StatusCode = (int)errorResp.StatusCode; using (var errorStream = errorResp.GetResponseStream()) { errorStream.CopyTo(outgoingResp.OutputStream); } } else { // Handle connection failures outgoingResp.StatusCode = 503; outgoingResp.Write("Proxy error: Could not reach internal server."); } } } public bool IsReusable => false; }
To make the proxy handle every incoming request (instead of just when accessing /ProxyHandler.ashx), add URL rewriting in your web.config or Global.asax.
Option 1: Web.config URL Rewrite
Add this to your <system.webServer> section to rewrite all non-file/directory requests to the proxy handler:
<system.webServer> <rewrite> <rules> <rule name="DMZ Reverse Proxy" stopProcessing="true"> <match url="^(.*)$" /> <conditions> <add input="{REQUEST_FILENAME}" matchType="IsFile" negate="true" /> <add input="{REQUEST_FILENAME}" matchType="IsDirectory" negate="true" /> </conditions> <action type="Rewrite" url="ProxyHandler.ashx/{R:1}" /> </rule> </rules> </rewrite> </system.webServer>
Option 2: Global.asax Routing
Add this to your Global.asax.cs Application_BeginRequest method:
void Application_BeginRequest(object sender, EventArgs e) { var context = HttpContext.Current; var requestPath = context.Request.Url.LocalPath; // Skip static files (adjust extensions as needed) if (!requestPath.StartsWith("/ProxyHandler.ashx") && !requestPath.EndsWith(".css") && !requestPath.EndsWith(".js") && !requestPath.EndsWith(".png")) { context.RewritePath("~/ProxyHandler.ashx"); } }
- Full HTTP Method Support: The code forwards the original request method (GET, POST, PUT, DELETE, PATCH, etc.) directly to the internal server.
- All Content-Types: Works with JSON, form data, binary files, and more—since we're streaming the request/response bodies directly.
- Request Manipulation: Customize headers or modify the request body (as shown in the code comment) to add security tokens, sanitize data, or inject context.
- Security: Restrict access to the proxy via IP whitelisting (add checks in
ProcessRequest), and filter sensitive headers if needed (e.g., skip forwardingAuthorizationunless explicitly required). - Performance: Adjust
ServicePointManager.DefaultConnectionLimitin your application startup to handle high concurrency (default is low for .NET Framework).
内容的提问来源于stack exchange,提问作者Ogglas

