Skip to main content
Email Password   helpLost your password?

Screenshot - Article.gif

Introduction

This article illustrates how a reverse proxy server can be developed in C# .NET v2.0 using HTTP Handler in IIS. The idea is to intercept and manipulate incoming HTTP requests to the IIS web server. I've developed a simple server with a basic, HTTP Reverse Proxy functionality, but there is still a lot more to add.

Background

A reverse proxy differs from an ordinary forward proxy. A forward proxy is an intermediate server that accepts requests addressed to it. It then requests content from the origin server and returns it to the client. The client must be configured to use the forward proxy. The reverse proxy does not require any special configuration by the client. The client requests content from the reverse proxy. The reverse proxy then decides where to send those requests, and returns the content as if it was itself the origin. Reverse proxies can be used to bring several servers into the same URL space.

Using the code

The below code is the core of the Reverse Proxy Server. I've used a HTML parser written by Jeff Heaton for rewriting the URLs in the HTML page rendered by the Reverse Proxy Server.

Points to Ponder

  1. IHttpHandler, the abstract HTTP Handler base class of .NET must be inherited to define custom HTTP Handler.
  2. IRequiresSessionState must be inherited if we are required to manipulate session variables.
  3. IsReusable is a read-only property which can be used to define where the application instance can be pooled/reused.
/*
 * REVERSE PROXY SERVER - IIS HTTP HANDLER - C# .NET v2.0
 * 
 * FILE NAME    :    Program.cs
 * 
 * DATE CREATED :    March 26, 2007, 16:15:05
 * CREATED BY   :    Gunasekaran Paramesh
 * 
 * LAST UPDATED :    April 16, 2007, 3:10:09 PM
 * UPDATED BY   :    Gunasekaran Paramesh
 * 
 * DESCRIPTION  :    Implementation of Reverse Proxy Server through IIS HTTP 
                     handler in C# .NET v2.0
*/

using System;
using System.IO;
using System.Net;
using System.Web;
using System.Text;
using System.Web.Mail;
using System.Collections;
using System.Configuration;
using System.Web.SessionState;
using System.DirectoryServices;

namespace TryHTTPHandler
{
public class SyncHandler : IHttpHandler, IRequiresSessionState
{
    public bool IsReusable { get { return true; } }

    // Process incoming request

    public void ProcessRequest(HttpContext Context)
    {            
        string ServerURL = "";            

        try
        {
            // Parsing incoming URL and extracting original server URL

            char[] URL_Separator = { '/' };
            string[] URL_List = Context.Request.Url.AbsoluteUri.Remove(0, 
                7).Split(URL_Separator);
            ServerURL = "http://" + 
                URL_List[2].Remove(URL_List[2].Length - 5, 5) + @"/";
            string URLPrefix = @"/" + URL_List[1] + @"/" + 
                URL_List[2]; // Eg. "/handler/stg2web.sync";

            for ( int i = 3; i < URL_List.Length; i++ )
                ServerURL += URL_List[i] + @"/";
            ServerURL = ServerURL.Remove(ServerURL.Length -1, 1);
            WriteLog(ServerURL + " (" + 
                Context.Request.Url.ToString() + ")");

            // Extracting POST data from incoming request

            Stream RequestStream = Context.Request.InputStream;
            byte[] PostData = new byte[Context.Request.InputStream.Length];
            RequestStream.Read(PostData, 0,
                (int) Context.Request.InputStream.Length);

            // Creating proxy web request

            HttpWebRequest ProxyRequest = (
                HttpWebRequest) WebRequest.Create(ServerURL);
            if ( ConfigurationManager.AppSettings["UpchainProxy"] == 
                "true" )
                ProxyRequest.Proxy = new WebProxy(
                    ConfigurationManager.AppSettings["Proxy"], true);

            ProxyRequest.Method = Context.Request.HttpMethod;
            ProxyRequest.UserAgent = Context.Request.UserAgent;                
            CookieContainer ProxyCookieContainer = new CookieContainer();
            ProxyRequest.CookieContainer = new CookieContainer();
            ProxyRequest.CookieContainer.Add(
                ProxyCookieContainer.GetCookies(new Uri(ServerURL)));
            ProxyRequest.KeepAlive = true;

            //For POST, write the post data extracted from the incoming request

            if ( ProxyRequest.Method == "POST" )
            {
                ProxyRequest.ContentType = 
                    "application/x-www-form-urlencoded";
                ProxyRequest.ContentLength = PostData.Length;
                Stream ProxyRequestStream = ProxyRequest.GetRequestStream();
                ProxyRequestStream.Write(PostData, 0, PostData.Length);
                ProxyRequestStream.Close();
            }

            // Getting response from the proxy request                

            HttpWebResponse ProxyResponse = (
                HttpWebResponse) ProxyRequest.GetResponse();

            if (ProxyRequest.HaveResponse)
            {
                // Handle cookies

                foreach(Cookie ReturnCookie in ProxyResponse.Cookies)
                {
                    bool CookieFound = false;
                    foreach(Cookie OldCookie in 
                        ProxyCookieContainer.GetCookies(new Uri(ServerURL)))
                    {
                        if (ReturnCookie.Name.Equals(OldCookie.Name))
                        {
                            OldCookie.Value = ReturnCookie.Value;
                            CookieFound = true;
                        }
                    }
                    if (!CookieFound)
                        ProxyCookieContainer.Add(ReturnCookie);
                }
            }

            Stream StreamResponse = ProxyResponse.GetResponseStream();
            int ResponseReadBufferSize = 256;
            byte[] ResponseReadBuffer = new byte[ResponseReadBufferSize];
            MemoryStream MemoryStreamResponse = new MemoryStream();

            int ResponseCount = StreamResponse.Read(ResponseReadBuffer, 0, 
                ResponseReadBufferSize);
            while ( ResponseCount > 0 )
            {
                MemoryStreamResponse.Write(ResponseReadBuffer, 0, 
                    ResponseCount);
                ResponseCount = StreamResponse.Read(ResponseReadBuffer, 0, 
                    ResponseReadBufferSize);
            }

            byte[] ResponseData = MemoryStreamResponse.ToArray();
            string ResponseDataString = Encoding.ASCII.GetString(ResponseData);

            // While rendering HTML, parse and modify the URLs present

            if ( ProxyResponse.ContentType.StartsWith("text/html") )
            {
                HTML.ParseHTML Parser = new HTML.ParseHTML();
                Parser.Source = ResponseDataString;                    
                while( !Parser.Eof() )
                {
                    char ch = Parser.Parse();
                    if ( ch == 0 )
                    {
                        HTML.AttributeList Tag = Parser.GetTag();
                        if ( Tag["href"] != null )
                        {
                            if ( Tag["href"].Value.StartsWith(
                                @"/") )
                            {
                                WriteLog("URL " +  
                                    Tag["href"].Value + 
                                    " modified to " + URLPrefix + 
                                    Tag["href"].Value);
                                ResponseDataString = 
                                    ResponseDataString.Replace(
                                    "\"" + 
                                    Tag["href"].Value + 
                                    "\"", "\"" + 
                                    URLPrefix + Tag["href"].Value + 
                                    "\"");
                            }
                        }

                        if ( Tag["src"] != null )
                        {
                            if ( Tag["src"].Value.StartsWith(
                                @"/") )
                            {
                                WriteLog("URL " +  
                                    Tag["src"].Value + 
                                    " modified to " + 
                                    URLPrefix + Tag["src"].Value);
                                ResponseDataString = 
                                    ResponseDataString.Replace(
                                    "\"" + 
                                    Tag["src"].Value + 
                                    "\"", "\"" + 
                                    URLPrefix + Tag["src"].Value + 
                                    "\"");
                            }
                        }
                    }
                }
                Context.Response.Write(ResponseDataString);
            }
            else
                Context.Response.OutputStream.Write(ResponseData, 0, 
                    ResponseData.Length);

            MemoryStreamResponse.Close();
            StreamResponse.Close();
            ProxyResponse.Close();
        }
        catch ( Exception Ex )
        {
            Context.Response.Write(Ex.Message.ToString());
            WriteLog("An error has occurred while requesting the URL 
                " + ServerURL + "(" + 
                Context.Request.Url.ToString() + ")\n" + 
                Ex.ToString());
        }
    }        

    // Write debug log message

    private void WriteLog(string Message)
    {
        FileStream FS = new FileStream(ConfigurationManager.AppSettings[
            "Log"], FileMode.Append, FileAccess.Write);
        string DateTimeString = DateTime.Now.ToString();
        Message = "[" + DateTimeString + "] " + Message + 
            "\n";
        byte[] FileBuffer = Encoding.ASCII.GetBytes(Message);
        FS.Write(FileBuffer, 0, (int)FileBuffer.Length);
        FS.Flush(); FS.Close();
    }
}
}

Sample web.config Configuration File

<configuration>
 <system.web>
  <httpHandlers>
   <add verb="*" path="*.sync" type="TryHTTPHandler.SyncHandler, 
       TryHTTPHandler" />
  </httpHandlers>
  <customErrors mode="Off" />
  <appSettings>
   <add key="UpchainProxy" value="true"/>
   <add key="Proxy" value="proxy1:80"/>
   <add key="Log" value="D:\\HTTPHandlerLog.rtf"/>
  </appSettings>
 </system.web>
</configuration>

Reverse Proxy Server Setup

In order to setup the Reverse Proxy Server in IIS, the following steps need to be performed.

  1. Compile the project to get .NET assemblies and create a web.config configuration file.
  2. Create a virtual directory in IIS, say "Handler" and copy the .NET assemblies into the "bin" folder of the virtual directory.
  3. Also copy the web.config configuration file to the virtual directory.
  4. Right-click the virtual directory just created and go to Properties>Directory>Configuration>Mappings>Add
  5. Specify the new application extension that will be handled by the Reverse Proxy Server, say ".sync" in the "Extension" field.
  6. In the "Add/Edit Applications Mapping" dialog box, browse and specify aspnet_isapi.dll in the "Executable" field. (For example: c:\windows\microsoft.net\framework\v2.0.50727\aspnet_isapi.dll)
  7. Set the "Verbs" to "All Verbs".
  8. Ensure that "Verify that files exist" is unchecked.
  9. Click "OK" until you close the "Properties" dialog box.
  10. Navigate to the reverse proxy URL in IE. (For example: http://localhost/handler/stg2web.sync)
  11. The filename with ".sync" extension will be taken as the back-end server name.

If you navigate to say, http://localhost/handler/stg2web.sync/tso5/logon.cfm, you will get the response from the back-end server, http://stg2web/tso5/logon.cfm

Specifications

Please note that these proxy features HTTP specifications and DO NOT support HTTPS. The following features are supported.

  1. HTTP GET
  2. HTTP POST
  3. HTTP Cookies
  4. URL Rewriting/Remapping
  5. Debug Logging

History

April 18, 2007 - Baseline (Version 1.0)

You must Sign In to use this message board.
 
 
Per page   
 FirstPrevNext
GeneralOpen Source Reverse Proxy Pin
Paramesh Gunasekaran
4:02 9 Dec '08  
Generalproblem with appSettings Pin
Prateek Bahl
16:01 14 Sep '08  
QuestionRegarding Host Header Pin
pmselvan_2000
9:27 5 Jul '08  
GeneralContent Type Pin
tauchert
4:18 22 May '08  
QuestionAjax support Pin
easyal
6:46 13 Mar '08  
Generalcan you develope it if get paid? Pin
nolovelust
10:50 20 Oct '07  
GeneralStrange results with google.com Pin
neil young
13:42 13 Oct '07  
GeneralThe proxy name could not be resolved: 'proxy1' Pin
matchupsports
10:37 26 Jun '07  
GeneralRe: The proxy name could not be resolved: 'proxy1' Pin
Gunasekaran Paramesh
13:33 26 Jun '07  
GeneralRe: The proxy name could not be resolved: 'proxy1' Pin
matchupsports
14:22 26 Jun '07  
GeneralRe: The proxy name could not be resolved: 'proxy1' Pin
Gunasekaran Paramesh
6:36 13 Jul '07  
QuestionWhy IRequireSessionState? Pin
Holger Hansen
0:40 22 Jun '07  
AnswerRe: Why IRequireSessionState? Pin
Gunasekaran Paramesh
6:38 13 Jul '07  
QuestionOT, where did you take the images? Pin
Simone Busoli
2:45 25 Apr '07  
AnswerRe: OT, where did you take the images? Pin
Gunasekaran Paramesh
1:02 29 May '07  
GeneralHow to use it Pin
Khaled Al-Noami
20:29 23 Apr '07  
GeneralRe: How to use it Pin
Gunasekaran Paramesh
0:04 24 Apr '07  
Generalwill it handle https Pin
DaveAnand
16:35 23 Apr '07  
GeneralRe: will it handle https Pin
SeanKinsey
22:18 23 Apr '07  
GeneralRe: will it handle https Pin
Gunasekaran Paramesh
0:01 24 Apr '07  
GeneralRe: will it handle https Pin
tripletdad99
6:44 31 Oct '07  


Last Updated 20 Apr 2007 | Advertise | Privacy | Terms of Use | Copyright © CodeProject, 1999-2009