Click here to Skip to main content
15,867,686 members
Articles / Programming Languages / C#
Article

Reverse Proxy in C# .NET v2.0

Rate me:
Please Sign up or sign in to vote.
4.53/5 (17 votes)
20 Apr 2007CPOL2 min read 216.9K   4.1K   86   28
Implementing Reverse Proxy Server in C# .NET v2.0 through HTTP Handler in IIS

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.
C#
/*
 * 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)

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Technical Lead HCL Technologies
India India
Paramesh Gunasekaran is currently working as a Software Engineer in HCL Technologies, India. He obtained his Bachelor's degree in Information Technology from Anna University, India. His research areas include Computational Biology, Artificial Neural Networks and Network Engineering. He has also received international acclaim for authoring industry papers in these areas. He is a Microsoft Certified Professional in ASP.NET/C# and has also been working in .NET technologies for more than 8 years.

Web: http://www.paramg.com

Comments and Discussions

 
QuestionIssues with the sub directory folder access in website Pin
Maggi120-Sep-11 9:29
Maggi120-Sep-11 9:29 
QuestionNothing after ".sync/" works Pin
London Bentley7-Sep-11 10:24
London Bentley7-Sep-11 10:24 
GeneralQueries : Reverse Proxy in C# .NET 2.0 Pin
lihkink12-Oct-10 1:48
lihkink12-Oct-10 1:48 
GeneralRe: Queries : Reverse Proxy in C# .NET 2.0 Pin
Paramesh Gunasekaran29-Nov-10 6:39
Paramesh Gunasekaran29-Nov-10 6:39 
GeneralMy vote of 1 Pin
Syed J Hashmi26-Aug-10 0:38
Syed J Hashmi26-Aug-10 0:38 
GeneralRe: My vote of 1 Pin
Bill Seddon12-Nov-11 1:44
Bill Seddon12-Nov-11 1:44 
QuestionHow to bypass the processrequest? Pin
vitiris4-Jan-10 18:10
vitiris4-Jan-10 18:10 
GeneralOpen Source Reverse Proxy Pin
Paramesh Gunasekaran9-Dec-08 3:02
Paramesh Gunasekaran9-Dec-08 3:02 
Generalproblem with appSettings Pin
Pr@teek B@h!14-Sep-08 15:01
Pr@teek B@h!14-Sep-08 15:01 
QuestionRegarding Host Header Pin
pmselvan_20005-Jul-08 8:27
pmselvan_20005-Jul-08 8:27 
GeneralContent Type Pin
tauchert22-May-08 3:18
tauchert22-May-08 3:18 
QuestionAjax support Pin
easyal13-Mar-08 5:46
easyal13-Mar-08 5:46 
Questioncan you develope it if get paid? Pin
nolovelust20-Oct-07 9:50
nolovelust20-Oct-07 9:50 
GeneralStrange results with google.com Pin
neil young13-Oct-07 12:42
neil young13-Oct-07 12:42 
GeneralThe proxy name could not be resolved: 'proxy1' Pin
matchupsports26-Jun-07 9:37
matchupsports26-Jun-07 9:37 
GeneralRe: The proxy name could not be resolved: 'proxy1' Pin
Paramesh Gunasekaran26-Jun-07 12:33
Paramesh Gunasekaran26-Jun-07 12:33 
GeneralRe: The proxy name could not be resolved: 'proxy1' Pin
matchupsports26-Jun-07 13:22
matchupsports26-Jun-07 13:22 
GeneralRe: The proxy name could not be resolved: 'proxy1' Pin
Paramesh Gunasekaran13-Jul-07 5:36
Paramesh Gunasekaran13-Jul-07 5:36 
QuestionWhy IRequireSessionState? Pin
Holger Hansen21-Jun-07 23:40
Holger Hansen21-Jun-07 23:40 
AnswerRe: Why IRequireSessionState? Pin
Paramesh Gunasekaran13-Jul-07 5:38
Paramesh Gunasekaran13-Jul-07 5:38 
QuestionOT, where did you take the images? Pin
Simone Busoli25-Apr-07 1:45
Simone Busoli25-Apr-07 1:45 
AnswerRe: OT, where did you take the images? Pin
Paramesh Gunasekaran29-May-07 0:02
Paramesh Gunasekaran29-May-07 0:02 
QuestionHow to use it Pin
knoami23-Apr-07 19:29
knoami23-Apr-07 19:29 
AnswerRe: How to use it Pin
Paramesh Gunasekaran23-Apr-07 23:04
Paramesh Gunasekaran23-Apr-07 23:04 
Generalwill it handle https Pin
DaveAnand23-Apr-07 15:35
DaveAnand23-Apr-07 15:35 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.