5,699,431 members and growing! (24,327 online)
Email Password   helpLost your password?
General Programming » Internet / Network » Proxy / Firewall     Intermediate

Reverse Proxy in C# .NET v2.0

By Paramesh Gunasekaran

Implementing Reverse Proxy Server in C# .NET v2.0 through HTTP Handler in IIS
C# 2.0, C#, Windows, .NET, .NET 2.0, Dev

Posted: 20 Apr 2007
Updated: 20 Apr 2007
Views: 31,542
Bookmarked: 45 times
Announcements
Loading...



Search    
Advanced Search
Sitemap
9 votes for this Article.
Popularity: 4.11 Rating: 4.30 out of 5
0 votes, 0.0%
1
0 votes, 0.0%
2
0 votes, 0.0%
3
6 votes, 66.7%
4
3 votes, 33.3%
5

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)

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here

About the Author

Paramesh Gunasekaran


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 4 years.
Occupation: Software Developer (Senior)
Company: HCL Technologies
Location: India India

Other popular Internet / Network articles:

Article Top
Sign Up to vote for this article
You must Sign In to use this message board.
FAQ FAQ Noise ToleranceSearch Search Messages 
 Layout  Per page   
 Msgs 1 to 20 of 20 (Total in Forum: 20) (Refresh)FirstPrevNext
Generalproblem with appSettingsmemberPrateek Bahl16:01 14 Sep '08  
QuestionRegarding Host Headermemberpmselvan_20009:27 5 Jul '08  
GeneralContent Typemembertauchert4:18 22 May '08  
QuestionAjax supportmembereasyal6:46 13 Mar '08  
Generalcan you develope it if get paid?membernolovelust10:50 20 Oct '07  
GeneralStrange results with google.commemberneil young13:42 13 Oct '07  
GeneralThe proxy name could not be resolved: 'proxy1'membermatchupsports10:37 26 Jun '07  
GeneralRe: The proxy name could not be resolved: 'proxy1'memberGunasekaran Paramesh13:33 26 Jun '07  
GeneralRe: The proxy name could not be resolved: 'proxy1'membermatchupsports14:22 26 Jun '07  
GeneralRe: The proxy name could not be resolved: 'proxy1'memberGunasekaran Paramesh6:36 13 Jul '07  
QuestionWhy IRequireSessionState?memberHolger Hansen0:40 22 Jun '07  
AnswerRe: Why IRequireSessionState?memberGunasekaran Paramesh6:38 13 Jul '07  
QuestionOT, where did you take the images?memberSimone Busoli2:45 25 Apr '07  
AnswerRe: OT, where did you take the images?memberGunasekaran Paramesh1:02 29 May '07  
GeneralHow to use itmemberKhaled Al-Noami20:29 23 Apr '07  
GeneralRe: How to use itmemberGunasekaran Paramesh0:04 24 Apr '07  
Generalwill it handle httpsmemberDaveAnand16:35 23 Apr '07  
GeneralRe: will it handle httpsmemberSeanKinsey22:18 23 Apr '07  
GeneralRe: will it handle httpsmemberGunasekaran Paramesh0:01 24 Apr '07  
GeneralRe: will it handle httpsmembertripletdad996:44 31 Oct '07  

General General    News News    Question Question    Answer Answer    Joke Joke    Rant Rant    Admin Admin   

PermaLink | Privacy | Terms of Use
Last Updated: 20 Apr 2007
Editor: Sean Ewington
Copyright 2007 by Paramesh Gunasekaran
Everything else Copyright © CodeProject, 1999-2008
Web18 | Advertise on the Code Project