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.