C# 2.0 中的简单反向代理(描述和部署)






4.85/5 (19投票s)
本文介绍了如何使用 IIS HTTPHandlers 在 C# 中开发反向代理。
引言
本文介绍了如何使用 IIS HTTPHandlers 在 C# 中开发反向代理,不是通过操作传入的 HTTP 请求,而是仅通过将所有请求转发到内部服务器(远程服务器)。
背景
维基百科说:“反向代理或代理服务器是安装在一个或多个服务器附近的代理服务器。通常,反向代理用于 Web 服务器的前端。来自 Internet 并寻址到其中一个 Web 服务器的所有连接都通过代理服务器路由,代理服务器可以自己处理该请求,也可以将请求全部或部分传递到主 Web 服务器。反向代理将入站网络流量分派到一组服务器,从而向调用者呈现单个接口。(...)”。
为了编写代码,我受到了 CodeProject 上 Vincent Brossier 和 Paramesh Gunasekaran 文章的启发。
Using the Code
下面的代码是反向代理服务器的核心。
ReverseProxy
类是应用程序的核心,管理导航器和代理服务器之间的通信……上图中的 [1] 和 [4] 点。RemoteServer
类管理代理服务器(Internet 前端)和远程服务器(内部服务器)之间的通信……上图中的 [2] 和 [3] 点。
ReverseProxy
类
- 创建与远程服务器的连接以重定向所有请求。
- 使用导航器请求中的相同数据创建请求。
- 将请求发送到远程服务器并返回响应。
- 将响应发送给客户端(并处理 cookie,如果可用)。
- 关闭流
namespace ReverseProxy
{
/// <summary>
/// Handler all Client's requests and deliver the web site
/// </summary>
public class ReverseProxy : IHttpHandler,
System.Web.SessionState.IRequiresSessionState
{
/// <summary>
/// Method calls when client request the server
/// </summary>
/// <param name="context">HTTP context for client</param>
public void ProcessRequest(HttpContext context)
{
// Create a connexion to the Remote Server to redirect all requests
RemoteServer server = new RemoteServer(context);
// Create a request with same data in navigator request
HttpWebRequest request = server.GetRequest();
// Send the request to the remote server and return the response
HttpWebResponse response = server.GetResponse(request);
byte[] responseData = server.GetResponseStreamBytes(response);
// Send the response to client
context.Response.ContentEncoding = Encoding.UTF8;
context.Response.ContentType = response.ContentType;
context.Response.OutputStream.Write(responseData, 0,
responseData.Length);
// Handle cookies to navigator
server.SetContextCookies(response);
// Close streams
response.Close();
context.Response.End();
}
public bool IsReusable
{
get { return true; }
}
}
}
RemoteServer
类包含许多方法
Constructor
用于初始化与远程服务器(由 URL 定义)的通信。GetRequest
创建一个连接到远程服务器的HttpWebRequest
对象,并发送所有“参数”(参数、POST 数据、cookie 等)。GetResponse
使用前一个对象并从远程服务器获取响应。GetResponseStreamBytes
将HttpWebResponse
(由前一个方法返回)转换为字节数组。SetContextCookies
将 cookie 发送到导航器上下文。
namespace ReverseProxy
{
/// <summary>
/// Manage communication between the proxy server and the remote server
/// </summary>
internal class RemoteServer
{
string _remoteUrl;
HttpContext _context;
/// <summary>
/// Initialize the communication with the Remote Server
/// </summary>
/// <param name="context">Context </param>
public RemoteServer(HttpContext context)
{
_context = context;
// Convert the URL received from navigator to URL for server
string serverUrl = ConfigurationSettings.AppSettings["RemoteWebSite"];
_remoteUrl = context.Request.Url.AbsoluteUri.Replace("http://" +
context.Request.Url.Host +
context.Request.ApplicationPath, serverUrl);
}
/// <summary>
/// Return address to communicate to the remote server
/// </summary>
public string RemoteUrl
{
get
{
return _remoteUrl;
}
}
/// <summary>
/// Create a request the remote server
/// </summary>
/// <returns>Request to send to the server </returns>
public HttpWebRequest GetRequest()
{
CookieContainer cookieContainer = new CookieContainer();
// Create a request to the server
HttpWebRequest request = (HttpWebRequest)WebRequest.Create(_remoteUrl);
// Set some options
request.Method = _context.Request.HttpMethod;
request.UserAgent = _context.Request.UserAgent;
request.KeepAlive = true;
request.CookieContainer = cookieContainer;
// Send Cookie extracted from the incoming request
for (int i = 0; i < _context.Request.Cookies.Count; i++)
{
HttpCookie navigatorCookie = _context.Request.Cookies[i];
Cookie c = new Cookie(navigatorCookie.Name, navigatorCookie.Value);
c.Domain = new Uri(_remoteUrl).Host;
c.Expires = navigatorCookie.Expires;
c.HttpOnly = navigatorCookie.HttpOnly;
c.Path = navigatorCookie.Path;
c.Secure = navigatorCookie.Secure;
cookieContainer.Add(c);
}
// For POST, write the post data extracted from the incoming request
if (request.Method == "POST")
{
Stream clientStream = _context.Request.InputStream;
byte[] clientPostData = new byte[_context.Request.InputStream.Length];
clientStream.Read(clientPostData, 0,
(int)_context.Request.InputStream.Length);
request.ContentType = _context.Request.ContentType;
request.ContentLength = clientPostData.Length;
Stream stream = request.GetRequestStream();
stream.Write(clientPostData, 0, clientPostData.Length);
stream.Close();
}
return request;
}
/// <summary>
/// Send the request to the remote server and return the response
/// </summary>
/// <param name="request">Request to send to the server </param>
/// <returns>Response received from the remote server
/// or null if page not found </returns>
public HttpWebResponse GetResponse(HttpWebRequest request)
{
HttpWebResponse response;
try
{
response = (HttpWebResponse)request.GetResponse();
}
catch (System.Net.WebException)
{
// Send 404 to client
_context.Response.StatusCode = 404;
_context.Response.StatusDescription = "Page Not Found";
_context.Response.Write("Page not found");
_context.Response.End();
return null;
}
return response;
}
/// <summary>
/// Return the response in bytes array format
/// </summary>
/// <param name="response">Response received
/// from the remote server </param>
/// <returns>Response in bytes </returns>
public byte[] GetResponseStreamBytes(HttpWebResponse response)
{
int bufferSize = 256;
byte[] buffer = new byte[bufferSize];
Stream responseStream;
MemoryStream memoryStream = new MemoryStream();
int remoteResponseCount;
byte[] responseData;
responseStream = response.GetResponseStream();
remoteResponseCount = responseStream.Read(buffer, 0, bufferSize);
while (remoteResponseCount > 0)
{
memoryStream.Write(buffer, 0, remoteResponseCount);
remoteResponseCount = responseStream.Read(buffer, 0, bufferSize);
}
responseData = memoryStream.ToArray();
memoryStream.Close();
responseStream.Close();
memoryStream.Dispose();
responseStream.Dispose();
return responseData;
}
/// <summary>
/// Set cookies received from remote server to response of navigator
/// </summary>
/// <param name="response">Response received
/// from the remote server</param>
public void SetContextCookies(HttpWebResponse response)
{
_context.Response.Cookies.Clear();
foreach (Cookie receivedCookie in response.Cookies)
{
HttpCookie c = new HttpCookie(receivedCookie.Name,
receivedCookie.Value);
c.Domain = _context.Request.Url.Host;
c.Expires = receivedCookie.Expires;
c.HttpOnly = receivedCookie.HttpOnly;
c.Path = receivedCookie.Path;
c.Secure = receivedCookie.Secure;
_context.Response.Cookies.Add(c);
}
}
}
}
示例 "Web.Config" 文件
配置文件设置反向代理服务器接收到的所有请求将被发送到哪里(例如:端口 81 上的 192.168.1.90)。
<configuration>
<appSettings>
<add key="RemoteWebSite" value="http://192.168.1.90:81" />
</appSettings>
<system.web>
<httpHandlers>
<add verb="*" path="*"
type="ReverseProxy.ReverseProxy, ReverseProxy"/>
</httpHandlers>
</system.web>
</configuration>
部署
为了在 IIS 中设置反向代理服务器,需要执行以下步骤
- 编译项目以获取 .NET 程序集,并创建一个web.config配置文件。
- 在 IIS 中创建一个新的虚拟目录(或一个新的网站),并将 .NET 程序集复制到“bin”文件夹,并将web.config复制到根文件夹。
- 右键单击刚刚创建的虚拟目录,然后转到“属性 / 主目录 / 配置 > 映射”(参见下图),并将“通配符应用程序映射”添加到“aspnet_isapi.dll”(取消选中*验证文件是否存在*)。
- 单击“确定”,直到关闭“属性”对话框。
- 在web.config中设置远程服务器的正确 IP。
关注点
本文解释了 HTTPHandler
的工作原理,以及如何捕获 Web 服务器 IIS 接收到的流量并将其(不变地)传输到另一台服务器。
历史
- 2008 年 11 月 21 日 - 基线(1.0 版)。