65.9K
CodeProject 正在变化。 阅读更多。
Home

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

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.85/5 (19投票s)

2008 年 11 月 28 日

CPOL

3分钟阅读

viewsIcon

175490

downloadIcon

7203

本文介绍了如何使用 IIS HTTPHandlers 在 C# 中开发反向代理。

Structure of Reverse Proxy

引言

本文介绍了如何使用 IIS HTTPHandlers 在 C# 中开发反向代理,不是通过操作传入的 HTTP 请求,而是仅通过将所有请求转发到内部服务器(远程服务器)。

背景

维基百科说:“反向代理或代理服务器是安装在一个或多个服务器附近的代理服务器。通常,反向代理用于 Web 服务器的前端。来自 Internet 并寻址到其中一个 Web 服务器的所有连接都通过代理服务器路由,代理服务器可以自己处理该请求,也可以将请求全部或部分传递到主 Web 服务器。反向代理将入站网络流量分派到一组服务器,从而向调用者呈现单个接口。(...)”。

为了编写代码,我受到了 CodeProject 上 Vincent Brossier 和 Paramesh Gunasekaran 文章的启发。

Using the Code

下面的代码是反向代理服务器的核心。

ReverseProxy 类是应用程序的核心,管理导航器和代理服务器之间的通信……上图中的 [1] 和 [4] 点。RemoteServer 类管理代理服务器(Internet 前端)和远程服务器(内部服务器)之间的通信……上图中的 [2] 和 [3] 点。

ReverseProxy

  1. 创建与远程服务器的连接以重定向所有请求。
  2. 使用导航器请求中的相同数据创建请求。
  3. 将请求发送到远程服务器并返回响应。
  4. 将响应发送给客户端(并处理 cookie,如果可用)。
  5. 关闭流
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 使用前一个对象并从远程服务器获取响应。
  • GetResponseStreamBytesHttpWebResponse(由前一个方法返回)转换为字节数组。
  • 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 中设置反向代理服务器,需要执行以下步骤

  1. 编译项目以获取 .NET 程序集,并创建一个web.config配置文件。
  2. 在 IIS 中创建一个新的虚拟目录(或一个新的网站),并将 .NET 程序集复制到“bin”文件夹,并将web.config复制到根文件夹。
  3. 右键单击刚刚创建的虚拟目录,然后转到“属性 / 主目录 / 配置 > 映射”(参见下图),并将“通配符应用程序映射”添加到“aspnet_isapi.dll”(取消选中*验证文件是否存在*)。
  4. 单击“确定”,直到关闭“属性”对话框。
  5. web.config中设置远程服务器的正确 IP。

IIS Configuration

关注点

本文解释了 HTTPHandler 的工作原理,以及如何捕获 Web 服务器 IIS 接收到的流量并将其(不变地)传输到另一台服务器。

历史

  • 2008 年 11 月 21 日 - 基线(1.0 版)。
© . All rights reserved.