Mega Code Archive

 
Categories / Delphi / Examples
 

Writing a simple ISAPI Filter for IIS

Title: Writing a simple ISAPI Filter for IIS. Question: This article shows you the basics of creating a simple ISAPI filter and how to use one to map sub-domains into specific folders. http://daniel.yourdomain.com will map the same way as http://www.yourdomain.com/members/daniel Answer: Borland has gone a long way helping us to create web applications. As of today, however, there is no real support for writing ISAPI Filters. Note: ISAPI Filters are quite different from ISAPI/NSAPI Extensions! INTRODUCTION ============ I have had no chance, so far, to look into Delphi 6 and figure how it supports ISAPI Filters, a first look revealed no new facts, anyway. ISAPI Filters are not called by referencing them in the URI of the request like http://www.yourserver.com/cgi/isapi.dll?params=54,4568,54.8 rather they are installed with the IIS-Console and invoked on EVERY call made to your web site. Therefore, ISAPI Filters have to be very fast in processing requests. THE LIBRARY BASICS ================== Every ISAPI Dll has to export two (or three) functions. These functions are called by thi IIS to initialize the filter, to process every single request and to destroy the filter. Attn.: These names are case-sensitive! (1) GetFilterVersion is called once, while the filter is running. All preliminary work is to be done here. The filter has to return on what kind of events it is supposed to process data. (2) HttpFilterProc is called for every object requested by the client, regardless of whether it is an image, a html file or an ASP script. (3) TerminateFilter is called when the web server is shuting down. Use this function to free resources reservered, etc. This function must return the value 1 - as off IIS 4 (NT) and IIS 5 (W2K). THE EVENTS PASSED TO HTTPFILTERPROC =================================== The following basic events are defined: (1) SF_NOTIFY_READ_RAW_DATA - The filter wants to process all incoming data (like form data) (2) SF_NOTIFY_PREPROC_HEADERS - The filter wants to process all header information sent by the client (browser type, uri, cookies, ...) (3) SF_NOTIFY_AUTHENTICATION - The filter is used to check the user authentication sent by the client (4) SF_NOTIFY_URL_MAP - The filter is used to map incoming requests to specific folders and files on the hard drive (5) SF_NOTIFY_ACCESS_DENIED - The filter wants to process responses when authentication has failed (for logging, ...) (6) SF_NOTIFY_SEND_RAW_DATA - The filter wants to process all outgoing data (like link checker, cookie munchers, ...) (7) SF_NOTIFY_LOG - The filter is used to change the data sent to the IIS log files (8) SF_NOTIFY_END_OF_NET_SESSION - The filter has to free resources reserved for specific users Note: The samples named are just a short list of possible uses for ISAPI filters. Because a filter is invoked on every request, you have to register the filter for every event you want to handle. Events, the filter does not register for, are not sent to it, thus increasing the speed of the filter. THE SUBDOMAIN URL MAPPER ============================= In this article I show you one simple, but useful application of such an ISAPI filter. This filter does not respect some aspects of good programming, like loading setup information, but rather has them written directly into the code - this one is left for you. I am just showing you the basics of ISAPI Filter programming. The filter to be installed on the "Properties" dialog with your IIS-Console for the specific "Web Site." Attn: You can install the filter on multiple "Web Sites," it is, however, loaded once only for all web sites using it. Therefore, you have to take care of this fact within your code. Further: I have not made this sample aware of the multi-threading part an ISAPI Filter has to take care of. WHAT DOES THIS SAMPLE DO ======================== Incoming request like http://username.yourdomain.com/guestbook.asp are mapped as they were entered like http://www.yourdomain.com/members/username/guestbook.asp "Members" can be defined by you. This filter maps the subdomain only, if the subfolder corresponding with the subdomain exists. MORE ON ISAPI FILTERS ===================== In a future article I will publish a complete unit with all definitions for ISAPI Filters. At present I am still working on them. You can read more on ISAPI filters on the msdn.microsoft.com home page. Here are the ISAPI header conversions: ISAPI Filter Header (D3K) THE SOURCE CODE FOR THE ISAPI FILTER LIBRARY ============================================ library DomainMapper; uses Windows, SysUtils, Classes, FileCtrl; {$R *.RES} // these constants must be changed to match your environment const // as found on the hard drive FAbsBaseFolder = 'C:\INetPub\WWWRoot\Members\'; // as entered in the URL FRelBaseFolder = '/members/'; const SF_MAX_FILTER_DESC_LEN = (256 + 1); SF_NOTIFY_SECURE_PORT = $00000001; SF_NOTIFY_NONSECURE_PORT = $00000002; SF_NOTIFY_READ_RAW_DATA = $00008000; SF_NOTIFY_PREPROC_HEADERS = $00004000; SF_NOTIFY_AUTHENTICATION = $00002000; SF_NOTIFY_URL_MAP = $00001000; SF_NOTIFY_ACCESS_DENIED = $00000800; SF_NOTIFY_SEND_RAW_DATA = $00000400; SF_NOTIFY_LOG = $00000200; SF_NOTIFY_END_OF_NET_SESSION = $00000100; SF_NOTIFY_ORDER_HIGH = $00080000; SF_NOTIFY_ORDER_MEDIUM = $00040000; SF_NOTIFY_ORDER_LOW = $00020000; SF_NOTIFY_ORDER_DEFAULT = SF_NOTIFY_ORDER_LOW; SF_NOTIFY_ORDER_MASK = SF_NOTIFY_ORDER_HIGH or SF_NOTIFY_ORDER_MEDIUM or SF_NOTIFY_ORDER_LOW; SF_STATUS_REQ_FINISHED = $8000000; SF_STATUS_REQ_FINISHED_KEEP_CONN = $8000001; SF_STATUS_REQ_NEXT_NOTIFICATION = $8000002; SF_STATUS_REQ_HANDLED_NOTIFICATION = $8000003; SF_STATUS_REQ_ERROR = $8000004; SF_STATUS_REQ_READ_NEXT = $8000005; type PHTTP_FILTER_VERSION = ^HTTP_FILTER_VERSION; HTTP_FILTER_VERSION = record dwServerFilterVersion: DWORD; dwFilterVersion: DWORD; lpszFilterDesc: array[0..SF_MAX_FILTER_DESC_LEN - 1] of Char; dwFlags: DWORD; end; THTTP_FILTER_VERSION = HTTP_FILTER_VERSION; LPHTTP_FILTER_VERSION = PHTTP_FILTER_VERSION; LPVOID = POINTER; TFilterGetServerVariableProc = function( var pfc{: THTTP_FILTER_CONTEXT}; VariableName: PChar; Buffer: Pointer; var Size: DWORD ): BOOL; stdcall; TFilterAddResponseHeadersProc = function( var pfc{: THTTP_FILTER_CONTEXT}; lpszHeaders: PChar; dwReserved: DWORD ): BOOL; stdcall; TFilterWriteClientProc = function( var pfc{: THTTP_FILTER_CONTEXT}; Buffer: Pointer; var Bytes: DWORD; dwReserved: DWORD ): BOOL; stdcall; TFilterAllocMemProc = function( var pfc{: THTTP_FILTER_CONTEXT}; cbSize: DWORD; dwReserved: DWORD ): Pointer; stdcall; TFilterServerSupportFunctionProc = function( var pfc{: THTTP_FILTER_CONTEXT}; sfReq: DWORD; pData: Pointer; ul1, ul2: DWORD ): BOOL; stdcall; PHTTP_FILTER_CONTEXT = ^THTTP_FILTER_CONTEXT; THTTP_FILTER_CONTEXT = record cbSize: DWORD; Revision: DWORD; ServerContext: Pointer; ulReserved: DWORD; fIsSecurePort: BOOL; pFilterContext: Pointer; GetServerVariable: TFilterGetServerVariableProc; AddResponseHeaders: TFilterAddResponseHeadersProc; WriteClient: TFilterWriteClientProc; AllocMem: TFilterAllocMemProc; ServerSupportFunction: TFilterServerSupportFunctionProc; end; HTTP_FILTER_CONTEXT = THTTP_FILTER_CONTEXT; PCardinal = ^Cardinal; TGetServerVariable = function( var pfc: THTTP_FILTER_CONTEXT; VariableName: PChar; Buffer: LPVOID; BuffSize: PCardinal ): BOOL; StdCall; TGetHeaderProc = function (var pfc: THTTP_FILTER_CONTEXT; lpszName: PChar; var lpvBuffer; var lpdwSize: DWORD): BOOL stdcall; TSetHeaderProc = function (var pfc: THTTP_FILTER_CONTEXT; lpszName, lpszValue: PChar): BOOL stdcall; TAddHeaderProc = function (var pfc: THTTP_FILTER_CONTEXT; lpszName, lpszValue: PChar): BOOL stdcall; PHTTP_FILTER_PREPROC_HEADERS = ^THTTP_FILTER_PREPROC_HEADERS; THTTP_FILTER_PREPROC_HEADERS = record GetHeader: TGetHeaderProc; SetHeader: TSetHeaderProc; AddHeader: TAddHeaderProc; dwReserved: DWORD; end; { * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * Exported Functions * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * } function GetFilterVersion(var pVer: HTTP_FILTER_VERSION): BOOL; stdcall; export; begin with pVer do begin // version dwFilterVersion := MakeLong(0 {minor}, 1 {major}); // description StrPCopy(lpszFilterDesc, 'A Simple Domain Mapper'); // notification flags dwFlags := SF_NOTIFY_ORDER_DEFAULT or SF_NOTIFY_PREPROC_HEADERS; end; Result := True; end; function HttpFilterProc(var FilterContext: HTTP_FILTER_CONTEXT; NotificationType: DWORD; pvNotification: LPVOID): DWORD; stdcall; export; var Buffer: array[0..4096] of Char; Size: Cardinal; P: Integer; DestFolder, Domain, SubDomain, URL: String; Data: THTTP_FILTER_PREPROC_HEADERS; begin try Result := SF_STATUS_REQ_NEXT_NOTIFICATION; if NotificationType = SF_NOTIFY_PREPROC_HEADERS then begin // we can check whether to map the current sub-domain into a directory Data := THTTP_FILTER_PREPROC_HEADERS(pvNotification^); // get the domain requested by the user Size := SizeOf(Buffer); FillChar(Buffer, Size, 0); TGetServerVariable(FilterContext.GetServerVariable) (FilterContext, 'SERVER_NAME', @Buffer[0], @Size); Domain := StrPas(@Buffer[0]); // get the sub-domain requested P := Pos('.', Domain); if P = 0 then Exit; SubDomain := Copy(Domain, 1, P - 1); // create the destination folder DestFolder := FAbsBaseFolder + SubDomain; if DirectoryExists(ExtractFilePath(DestFolder)) then begin // the sub-domain my be routed - a corresponding folder does exist // get the uri of the document requested Size := SizeOf(Buffer); FillChar(Buffer, Size, 0); TGetHeaderProc(Data.GetHeader)(FilterContext, 'url', Buffer[0], Size); URL := StrPas(Buffer); // add the virtual base folder to the uri if URL = '' then URL := FRelBaseFolder + SubDomain else URL := FRelBaseFolder + SubDomain + URL; // send new uri to IIS StrPCopy(@Buffer[0], URL); TSetHeaderProc(Data.SetHeader)(FilterContext, 'url', @Buffer[0]); end; end; except // on error return request to process next ISAPI message Result := SF_STATUS_REQ_NEXT_NOTIFICATION; end; end; function TerminateFilter(Flags: DWORD): DWORD; stdcall; export; begin // must return 1 (as of IIS 4 and IIS 5) Result := 1; end; exports GetFilterVersion, HttpFilterProc, TerminateFilter; begin end.