Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

ISAPI Filter with Re-writing rule COM component with ADO implementation with we l manage load balancing concept.

0.00/5 (No votes)
12 Mar 2009 1  
ISAPI Filter with Re-writing rule COM component with ADO implementation with we l manage load balancing concept.

Introduction

This article help you to resolved you all query regard to ISAPI filter. This article cover below point…

  • Develop the ISAPI re-write engine in ISAPI it self. Not no more .INI. Feel to free to re-write rules as to every URL.
  • How to build COM component.
  • Development of this component.
  • Calling Unmanaged code.
  • Unit testing.
  • Deployment on production server and after testing on the development as well QA, Staging Server.

Background

My requirements was developed a the ISAPI filter which is going to perform redirection operation to destination URL. Yes this look simple but what if in requirement more clause get added such as

  • In database we are storing the Source and destination URL
  • Source URL contains re-write rules.
  • How to call COM Component from ISAPI Filter.
  • No INI files used for evaluate re-write rule.
  • Build the re-write engine in ISAPI filter it self.
  • Every URL get first get match in the database and if found then parse the re-write expression using re-write engine and redirect to destination URL with all parameters.
  • ISAPI filter should be highly optimize because N number of user hitting you web server every second.
  • Load banding should be well managed.

Let talk more technical. Lets show you high level diagram, such that we will understand the article.
arch.jpg
Process Flow : process_flow.jpg

Using the Code

Lets go step by step

Step 1

Lest First create the ISAPI filter. I have created a ISAPI project and given name as “GenericRedirectorISAPI” this project contains below file and folder structure
project_explore.jpg

In above project explore I have included three project

  • Re-write rule evaluator engine.
  • ISAPI filter project.
  • Testing to ISAPI filter project.

Now how to create ISAPI filter. Please refer to isapiredirector.aspx
In the above URL you come to know that how to crate and deploy the ISAPI filer on IIS.Now lest code on point.

Step 2

Add the COM component .tlb file reference and rename the name space for you easy convention. It’s Optional. After successfully importing the TLB now using the it’s namespace such that all method and it’s public member visible in the unmanned code.

Note: C++ is managed code which do communication with native code i.e. executing the un-manage code.

Refer the StdAfx.h in this header file

Step 3

In the ISAPI filter project please refer the file(GenericRedirectorISAPI.cpp) I will main function which do the all the job.

In function variable initialization has been done.

Getting server port and confirming that out going protocol shoud be same as incoming protocol

Now getting server name in order to build the proper redirection URL.

No reading URL value form HTTP protocol header in order to fetch exact value from database and pass to re-write engine and get the destination URL.

Building the Destination URL

Calling COM component and passing the current URL fetch from header and getting destination URL. If COM component return NULL value that mean the for that URL no re-write rule has been define in the database so simply handover that URL to IIS web server to handle to that else build the destination URL and modify the source URL by destination URL by "301 Moved Permanently" .

Summary of GenericRedirectorISAPI.cpp : This CPP file contains many ISAPI filer method and using OnPreProcHeader(…) event I have define business logic such as

  • Initialization Of Variable
  • Getting source protocol
  • Getting source server name
  • Reading URL value from Header
  • Calling COM component to process the source URL
  • Managing Unmanage code
  • Creating new header and re-directing to destination if some value return by COM component else handover all things to IIS web server.
// GenericRedirectorISAPI.cpp - Implementation file for ISAPI
//    GenericRedirectorISAPI Filter

#include "stdafx.h"
#include "GenericRedirectorISAPI.h"

// The one and only CWinApp object
// NOTE: You may remove this object if you alter your project to no
// longer use MFC in a DLL.

CWinApp theApp;

// The one and only CGenericRedirectorISAPIFilter object

CGenericRedirectorISAPIFilter theFilter;

// CGenericRedirectorISAPIFilter implementation

CGenericRedirectorISAPIFilter::CGenericRedirectorISAPIFilter()
{
}

CGenericRedirectorISAPIFilter::~CGenericRedirectorISAPIFilter()
{
}

BOOL CGenericRedirectorISAPIFilter::GetFilterVersion(PHTTP_FILTER_VERSION pVer)
{
	// Call default implementation for initialization
	CHttpFilter::GetFilterVersion(pVer);

	// Clear the flags set by base class
	pVer->dwFlags &= ~SF_NOTIFY_ORDER_MASK;

	// Set the flags we are interested in
	pVer->dwFlags |= SF_NOTIFY_SECURE_PORT | SF_NOTIFY_NONSECURE_PORT | SF_NOTIFY_PREPROC_HEADERS;

	// Set Priority
	pVer->dwFlags |= SF_NOTIFY_ORDER_LOW;

	// Load description string
	TCHAR sz[SF_MAX_FILTER_DESC_LEN+1];
	ISAPIVERIFY(::LoadString(AfxGetResourceHandle(),
			IDS_FILTER, sz, SF_MAX_FILTER_DESC_LEN));
	_tcscpy(pVer->lpszFilterDesc, sz);
	return TRUE;
}

DWORD CGenericRedirectorISAPIFilter::OnPreprocHeaders(CHttpFilterContext* pCtxt,
	PHTTP_FILTER_PREPROC_HEADERS pHeaderInfo)
{
	char urlBuffer[2048];
	DWORD urlBuffSize = sizeof(urlBuffer);
	CString urlString(urlBuffer);
	CString tempUrlString = _T("");
	char portBuffer[16];

	DWORD portBuffSize = sizeof(portBuffer);
	BOOL bResult = pCtxt->GetServerVariable("SERVER_PORT", portBuffer, &portBuffSize);
	if( bResult )
	{
		CString portString(portBuffer);
		if( portString == _T("80") )
			tempUrlString += _T("http://");
		else
			tempUrlString += _T("https://");
	}

	char httpHostBuffer[512];
	DWORD httpHostBuffSize = sizeof(httpHostBuffer);
	bResult = pCtxt->GetServerVariable("SERVER_NAME", httpHostBuffer, &httpHostBuffSize);
	if( bResult )
	{
		CString httpHostString(httpHostBuffer);
		tempUrlString +=  httpHostString;
	}
	
	char refererBuffer[2048];
	DWORD refererBuffSize = sizeof(refererBuffer);
	bResult=pHeaderInfo->GetHeader(pCtxt->m_pFC, "url", refererBuffer, &refererBuffSize); 
	if( bResult )
	{
		CString refererString(refererBuffer);
		tempUrlString	+= refererString;
	}
	urlString = _T("");
	urlString = _T("Location: ");
	 
	HRESULT hr = CoInitialize(NULL);
	IGenericRedirectorPtr pICalc(__uuidof(GenericRedirector));
	_bstr_t destination = _T("");
	_bstr_t url = tempUrlString.GetBuffer();
	 
	pICalc->MatchRedirect(url,destination.GetAddress());
	
	if(destination.GetBSTR() != NULL) 
		{
			urlString += destination.GetBSTR();
			urlString += _T("\r\n\r\n");

			pCtxt->ServerSupportFunction (SF_REQ_SEND_RESPONSE_HEADER
									, (LPVOID) "301 Moved Permanently"
									, (LPDWORD) urlString.GetBuffer()
									, (LPDWORD) NULL );
					return SF_STATUS_REQ_FINISHED;
		}
		 
	CoUninitialize(); 
 
	
	return SF_STATUS_REQ_NEXT_NOTIFICATION;
}


// If your extension will not use MFC, you'll need this code to make
// sure the extension objects can find the resource handle for the
// module.  If you convert your extension to not be dependent on MFC,
// remove the comments around the following AfxGetResourceHandle()
// and DllMain() functions, as well as the g_hInstance global.

/****

static HINSTANCE g_hInstance;

HINSTANCE AFXISAPI AfxGetResourceHandle()
{
	return g_hInstance;
}

BOOL WINAPI DllMain(HINSTANCE hInst, ULONG ulReason,
					LPVOID lpReserved)
{
	if (ulReason == DLL_PROCESS_ATTACH)
	{
		g_hInstance = hInst;
	}

	return TRUE;
}

****/

Step 4

Now next step to create re-write parser component

COM Component Creating for parsing re-write rule and database communication

we are creating one component which going take care of the regular expression and database communication. Note : We are creating this COM component in the C#.

COM Component Background

  • It accept the browser URL
  • Call the stored procedure
  • Stored procedure return the matching URL using like query
  • Stored all recordset in StringDictionary object
  • Parse the all source URL againt to passed browser URL and find the exact match
  • If exact match not found the then return NULL else return the destination URL.
  • Now experience that every URL have it’s own re-write rule.
  • Now no INI file.

Define Interface using MarshalAs and UnmanagedType.BStr

Creating interface which is an Abstract definition of implemented class. This interface contains on method which visible in C++ code as unmanaged code. Keep in mind which creating this definition in the Interface we have to se it return type as UnmanagedType.BStr with MarshalAs

import the runtime InteropServices class in order to implement the Marshaling and Un-Marshaling concept

using System;
using System.Runtime.InteropServices;

namespace GenericRedirectorDLL
{
	/// <summary />
	/// IGenericRedirector interface definition.
	/// </summary />
    [ComVisible(true)]
    [InterfaceTypeAttribute(ComInterfaceType.InterfaceIsDual)]
	[Guid("94B7B4DA-9329-4832-B1F9-DA471208D330")]
	public interface IGenericRedirector
	{
		/// <summary />
		/// Matches url to configured set of source for redirection.
		/// </summary />
		/// <param name="url" />Url to match against configured sources</param />
		/// <returns />Destination Url to redirect to when match is found
                  /// against configured source values.  If null, no match is found
                  /// and redirect is not required.</returns />
		[return : MarshalAs(UnmanagedType.BStr)] string MatchRedirect(
                       [MarshalAs(UnmanagedType.BStr)] string url);
	}
}

Now create the definition class i.e. creating implementation class

We are creating the now Method which going to manage the hole operation. Method MatchRedirect (…)

This method going to perform below mention action.

  • Accept the URL as parameter
  • Creating StringDictionary Object calling other method which going to do the communication with database and after matching [ using like caluse] return the recordset and it get stored in the StringDictionary object
  • Now matching the every return Source URL which contains re-write rules
  • Call the other method of of other calls which going to parse the re-write rule and return the passed string.
  • if mathich found the with Browser URL then return the it’s Destination URL.
  • If no match found then return null
public string MatchRedirect(string url)
		{
			StringDictionary urlMaps = RedirectorUrlList.UrlMap;
			if( urlMaps != null )
			{
				string dest;
				foreach(string source in urlMaps.Keys)
				{
					dest = UrlMatchHelper.MatchRegExPattern(url,
                                                source, urlMaps[source]);
 
                    if( dest != null )
						return dest;
				}
			}
			return null;
		}

Before creating of above method first we have to

  • Implement the define call with Interface
  • Set Com visibility true.
  • Set it;s GUID
  • Set it’s ProgID
  • Repeat same step in Interface
[ComVisible(true)]     
[ClassInterface(ClassInterfaceType.AutoDual)]
[Guid("E53AEF65-EA74-419b-A83B-03656A1E0413")]
[ProgId("GenericRedirector")]

Implementation Class

using System;
using System.Collections.Specialized;
using System.Runtime.InteropServices;
using System.Diagnostics;

namespace GenericRedirectorDLL
{
	/// <summary />
	/// GenericRedirector provide a utility whereby requested Url is matched 
	/// against configured source Urls for redirection.
	/// </summary />
    [ComVisible(true)]     
	[ClassInterface(ClassInterfaceType.AutoDual)]
	[Guid("E53AEF65-EA74-419b-A83B-03656A1E0413")]
	[ProgId("GenericRedirector")]
	public class GenericRedirector : IGenericRedirector
	{
		#region "Constructor"
		public GenericRedirector()
		{
		}
		#endregion

		#region IGenericRedirector Members
		/// <summary />
		/// Matches url for configured redirection source.
		/// </summary />
		/// <param name="url" />Url to match against redirect sources.</param />
		/// <returns />Url to redirect to if match is found. 
                  /// If null, no match is found and no redirect is required.</returns />
		public string MatchRedirect(string url)
		{
			StringDictionary urlMaps = RedirectorUrlList.UrlMap;
			if( urlMaps != null )
			{
				string dest;
				foreach(string source in urlMaps.Keys)
				{
					dest = UrlMatchHelper.MatchRegExPattern(url,
                                                  source, urlMaps[source]);
                    /*if (!EventLog.SourceExists("Jaiswar"))
                        EventLog.CreateEventSource("Jaiswar", "Application");
                    EventLog.WriteEntry("Jaiswar", url+"->"+dest ,
                        EventLogEntryType.Information , 11);*/

                    if( dest != null )
						return dest;
				}
			}
			return null;
		}
		#endregion
	}
}

Create Call for reading Source URL from database and stored that URL in the Cache.

using System;
using System.Collections.Specialized;
using System.Data;
using System.Data.SqlClient;
using System.Runtime.InteropServices;
using System.Web;
using System.Web.Caching;

namespace GenericRedirectorDLL
{
	/// <summary />
	/// RedirectorUrlList retrieves redirect mapping list from data source
         /// with caching option.
	/// </summary />
    [ComVisible(true)]
  	internal class RedirectorUrlList
	{
		#region "Member Variables"
		private static string CacheKey = "GenericRedirectMap";
		private static string ConnectionString =
                      @"Server=MDYNYCSYNQA01;Database=mdysid;User ID=sid;Password=sid123";
		private static int CacheTimeInMinutes = 5;
		#endregion

		#region "Constructor"
		private RedirectorUrlList()
		{
		}
		#endregion

		#region "Properties"
		public static StringDictionary UrlMap
		{
			get
			{
				StringDictionary urlMap = null;
				if( HttpContext.Current == null )
					urlMap = UrlMapGet();
				else
				{
					HttpContext current = HttpContext.Current;
					if( current.Cache[CacheKey] != null )
						urlMap =
                                                       current.Cache[CacheKey] as StringDictionary;
					if( urlMap == null )
					{
						CacheUrlMap();
						urlMap = current.Cache[CacheKey] as StringDictionary;
					}
				}
				return urlMap;
			}
		}
		#endregion

		#region "Methods"
		#region "CacheUrlMap"
		/// <summary />
		/// Cache configured Url Maps from Sql Data Source.
		/// </summary />
		private static void CacheUrlMap()
		{
			StringDictionary urlMaps = UrlMapGet();

			#region "Cache Data"
			if( urlMaps.Count > 0 && HttpContext.Current != null )
			{
				HttpContext current = HttpContext.Current;
				current.Cache.Add(CacheKey, urlMaps, null
					, DateTime.Now.Add(new TimeSpan(0, CacheTimeInMinutes, 0))
					, Cache.NoSlidingExpiration,
                                              CacheItemPriority.Normal, 
                                              new CacheItemRemovedCallback(
                                                  UpdateUrlMapCacheOnCacheExpiry) );
			}
			#endregion
		}
		#endregion

		#region "UpdateUrlMapCacheOnCacheExpiry"
		/// <summary />
		/// Refresh Url Map cache on cache expiry.
		/// </summary />
		/// <param name="key" />Expiring cache key</param />
		/// <param name="value" />Expiring cache content</param />
		/// <param name="reason" />Expiry reason</param />
		public static void UpdateUrlMapCacheOnCacheExpiry(string key,
                      object value, CacheItemRemovedReason reason)
		{
			CacheUrlMap();
		}
		#endregion

		#region "UrlMapGet"
		/// <summary />
		/// Retrieves configured Url maps from Sql data source.
		/// </summary />
		/// <returns />StringDictionary containing configured Url maps.</returns />
		private static StringDictionary UrlMapGet()
		{
			StringDictionary urlMaps = new StringDictionary();
			using(SqlConnection conn = new SqlConnection(
                               ConnectionString))
			{
				conn.Open();
                SqlCommand cmd = new SqlCommand("GenericRedirectorsUrl_Get" , conn);
                cmd.CommandType = CommandType.StoredProcedure;
                cmd.Parameters.Add(new SqlParameter("@pOperation", 1));
				
                using(SqlDataReader reader = cmd.ExecuteReader())
				{
					if( reader.HasRows )
						while( reader.Read())
						{
							urlMaps.Add(
                                                                   reader.GetString(reader.GetOrdinal("SourceUrl"))
								, reader.GetString(reader.GetOrdinal("DestinationUrl")));
						}
				}
			}
			return urlMaps;
		}
		#endregion
		#endregion
	}
}

Design he Re-Write Rule Parser Class

This class is heat of the hole ISAPI application. It parse the URL re-write expression and generate the destination.

using System;
using System.Text;
using System.Text.RegularExpressions;
using System.Runtime.InteropServices;

namespace GenericRedirectorDLL
{
	/// <summary />
	/// UrlMatchHelper provide matching and conversion functionality.
	/// </summary />
	[ComVisible(false)]
	public class UrlMatchHelper
	{
		#region "Constructor"
		private UrlMatchHelper()
		{
		}
		#endregion

		#region "Methods"
		public static string MatchRegExPattern(string url, string source, string dest)
		{
			Regex exp = new Regex(source, RegexOptions.IgnoreCase);
			if( exp.IsMatch(url) )
			{
				Match m = exp.Match(url);
				GroupCollection gc = m.Groups;
				if( gc.Count > 1 )		// First group match is top level match.
				{
					int counter = 0;
					foreach(Group g in gc)
					{
						if( counter == 0 )
						{
							counter++;
							continue;
						}
						dest = dest.Replace(
                                                       string.Format("{0}{1:0}{2}", "{",
                                                       counter++, "}"), g.Value);
					}
				}
				return dest;
			}
			return null;
		}
		#endregion
	}
}

Step 5

How to deploy the application and make it happning.

Please follow the below mention step in order

  • Assign Strong name to GenericRedirectorDLL project
    :\.sn –k [stringnameKeyName.snk]
  • Assign the strong name key to GenericRedirectorDLL namespace.
  • Buld the app application at a time to one by one
  • Generate the TBL file of GenericRedirectorDLL.dll
    :\>regasm [GenericRedirectorDLL.dll path] /tlb: [GenericRedirectorDLL.tlb path] / codebase
  • Now Keep the generated TBL file in same location where ISAPI filter DLL (C++) is get created project.
  • Open the Internet Information server
  • Select the Web Serve property . You can select hole or one web server property.
  • Click on he ISAPI Tab
  • Click on ADD button
  • Give then name of ISPAI reference Name such as “Genericredirection”
  • Select the ISPAI Filter DLL path (C++)
  • Reset the server

Database Sturcture

Points of Interest

  • Now No more .INI file
  • Now customize your code as per you re-write rule.
  • Develop the ISAPI re-write engine in ISAPI it self. Not no more .INI. Feel to free to re-write rules as to every URL.
  • How to build COM component.
  • Development of this component.
  • Calling Unmanaged code.
  • Unit testing.
  • Deployment on production server and after testing on the development as well QA, Staging Server.

My requirements was developed a the ISAPI filter which is going to perform redirection operation to destination URL. Yes this look simple but what if in requirement more clause get added such as

  • In database we are storing the Source and destination URL
  • Source URL contains re-write rules.
  • How to call COM Component from ISAPI Filter.
  • No INI files used for evaluate re-write rule.
  • Build the re-write engine in ISAPI filter it self.
  • Every URL get first get match in the database and if found then parse the re-write expression using re-write engine and redirect to destination URL with all parameters.
  • ISAPI filter should be highly optimize because N number of user hitting you web server every second.
  • Load banding should be well managed.

History

If any problem then please contact me any time on +91 9773596947 or +91 9869566849

==========================================
Column  Name	Data type	Max Length
==========================================
Source	          Varchar	 255
Destination	      Varchar	 255
MatchOrder	       Number
                 (Primary Key)	18,0
isActive	        Binary	  1








						

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