In this chapter, you learn how to create an Internet Server Application Programming Interface (ISAPI). We begin by creating a dynamic-link library (DLL) in Microsoft Visual C++ (MSVC++) 4.x.
We outline the filter's initializations and termination, the entry points, the hypertext transport protocol (HTTP) flow events, and the API function calls. A tutorial shows you how to create a filter that does customized text replacement in an HTML page.
If you need a refresher, see Chapter 13, "Understanding ISAPI Filters," and review the rules for creating a filter.
ISAPI extensions and filters are 32-bit DLLs-or they are called by a 32-bit DLLs, as with OLEISAPI.DLL.
We outline the filter's initialization and terminations, entry points, hypertext transport protocol (HTTP) flow events, and API function calls.
In a tutorial you learn how to create a filter that does customized text replacement in an HTML page.
In this section, we follow the steps to create an ISAPI filter DLL. We use the MSVC++ version 4.x development environment, running on Windows NT server 4.0, with Microsoft's Internet Information Server (IIS) 3.0 to serve the Web requests.
To begin a new project with MSVC++, open Microsoft Developer Studio from the Microsoft Visual C++ programs folder. If you have a subscription to Microsoft Developer Network (MSDN) and it is integrated with the Microsoft Developer Studio, you see a window like the one in Fig. 14.1.
Fig. 14.1Microsoft Developer Studio-ready to go.
![]()
Microsoft Published Resources
Time is money and knowledge is power. Microsoft publishes two CDs and offer both via MSDN and Microsoft TechNet. These are invaluable tools for the busy and the beginning developer.
MSDN is produced quarterly and is the official source from Microsoft for comprehensive programming information, development toolkits, and testing platforms. TechNet, with over 150,000 pages of in-depth technical information on each CD, is produced monthly and is the comprehensive information resource for evaluating, using, and supporting Microsoft business products. Additional ordering and pricing is at Microsoft's Web site: http://www.microsoft.com/msdn and http://www.microsoft.com/technet.
When you have an empty palette to work with, you are ready to create a DLL project workspace. From the File menu, select New. You see a New dialog box like the one in Fig. 14.2.
New dialog box-select an object to create.
Select Project Workspace and click the OK button. You see a New Project Workspace dialog box like the one in Fig. 14.3.
Fig. 14.3New Project Workspace-select a project type to create.
From the Type list box, select Dynamic-Link Library. In the Name text box, enter the name of the DLL to be created. Notice that in the Location text box, the project name is appended to the path where the Developer Studio projects are stored.
Make sure that Win32 is checked in the Platforms check box grouping. When you finish the New Project Workspace dialog box as shown in Fig. 14.4, you are ready to click the Create button. We have a new project workspace and we are ready to begin adding code.
Fig. 14.4New project workspace ready for the sample project.
DllMain is the first function we use when creating a new ISAPI filter. DllMain is normally the entry point for 32-bit DLLs. This single function replaces LibMain and WEP in the Win32 model.
DllMain is optional but highly recommended. The name DllMain is also optional but it is the de facto standard. A DllMain function allows the server to notify your filter when per-process, and per-thread initialization and clean-up, are needed.
DllMain has a specific function prototype that you must adhere to so it works properly. This prototype is as follows:
BOOL WINAPI DllMain (
HINSTANCE hinstDLL,
DWORD fdwReason,
LPVOID lpvReserved
);
DllMain is called when the filter is loaded and unloaded from the server's address space. DllMain can determine why it is being called by checking the value of fdwReason.
The four possible values for fdwReason are DLL_PROCESS_ATTACH, DLL_PROCESS_DETACH, DLL_THREAD_ATTACH, and DLL_THREAD_DETACH. We use DLL_PROCESS_ATTACH to do filter initialization and DLL_PROCESS_DETACH to do filter clean-up.
An example of an initialization process is to read data from persistent storage to initialize the filter's global variables-for example, a Web-site visit counter.
An example of clean-up processing is writing global variables to persistent storage to save the state of the variables between the termination and restarting of the World Wide Web publishing service.
Listing 14.1 is an example of how to retrieve and store state variables in the DllMain function.
Listing 14.1 LST14_1.C-Retrieving and Storing State Variables
/*
Globals
*/
CRITICAL_SECTION gCS; // A critical section handle
// is used to protect global
// state properties
unsigned int giCurrentAd;
/*
This is the entry and exit point for the filter
it is called when the filter is loaded and unloaded
by IIS. This is where state properties need to be
retrieved and store on persistant storage.
*/
BOOL APIENTRY DllMain( HANDLE hModule,
DWORD fdwReason,
LPVOID lpReserved )
{
char szLastAd[4];
switch( fdwReason ) {
case DLL_PROCESS_ATTACH:
{
/*
On process attach we will set the state
variables for the filter. This entails
counting the number of advertisers we're
flipping ads for.
*/
InitializeCriticalSection(&gCS);
EnterCriticalSection(&gCS);
gbHTML=FALSE;
giCurrentAd = GetPrivateProfileInt(TEXT("Info"), TEXT("LastAd"),
1, TEXT("ads.ini"));
LeaveCriticalSection(&gCS);
break;
}
// case DLL_THREAD_ATTACH:
// case DLL_THREAD_DETACH:
case DLL_PROCESS_DETACH:
{
sprintf(szLastAd, "%d", giCurrentAd);
WritePrivateProfileString(TEXT("Info"), TEXT("LastAd"),
szLastAd, TEXT("ads.ini"));
DeleteCriticalSection(&gCS);
break;
}
}
return TRUE;
}
Listing 14.1 above includes an example of using critical-section code to make your filter thread-safe. For a information on making your extension thread-safe, see Chapter 18, "Making Your Extensions Thread-Safe."
![]()
To add a new source file and create a DllMain function for your new filter project in MSVC++, from the File menu, select New. You see the New dialog box shown in Figure 14.2 earlier in this chapter.
Select Text File from the New list box and click the OK button. You see an empty window that is ready for your DllMain function. With the DllMain function added, your development environment should look something like Figure 14.5.
Fig. 14.5Workspace with new DllMain function added.
The last task is to add the new code to the filter project. From the File menu, select Save As and save the file under an appropriate source file name in the same directory as the other project files.
After you save the file to disk, from the Insert menu, select Files into Project. You see the Insert Files into Project dialog box shown in Figure 14.6.
Fig. 14.6The Insert Files into Project dialog box.
With the source file selected, click the Add button. The new source file is added to the new filter project and displayed in the project workspace tree view. The single source file is normally all you need for a basic filter.
Now we are ready to move on to the function calls that enable an ISAPI filter to be recognized and to work as an extension of the Web service.
GetFilterVersion is one of two necessary functions for every ISAPI filter. It is the initial entry point called by IIS. GetFilterVersion receives a pointer to the structure, HTTP_FILTER_VERSION, which must be completed by the function.
Items to be completed in the structure include the filter's version information, the events that the filter is to process, and the priority of these events in terms of other registered filters.
Register the filter only for events that the filter will process. Registering extraneous events can reduce the Web service's performance and scalability.
![]()
The prototype for GetFilterVersion is
BOOL WINAPI GetFilterVersion (PHTTP_FILTER_VERSION pVer)
The pVer parameter points to the HTTP_FILTER_VERSION structure and provides the communication channel between IIS and the filter. The structure holds version information for the server and fields for the client to denote the version, event notifications, filter priority, and a text description of the filter.
The structure is defined as
typedef struct _HTTP_FILTER_VERSION
{
DWORD dwServerFilterVersion;
DWORD dwFilterVersion;
CHAR lpszFilterDesc[SF_MAX_FILTER_DESC_LEN+1];
DWORD dwFlags;
} HTTP_FILTER_VERSION, *PHTTP_FILTER_VERSION;
The structure members are as follows:
dwServerFilterVersion [in]. The version of the specification used by the server. The version of the current header file is HTTP_FILTER_REVISION.
dwFilterVersion [out]. The version of the specification used by the server. The version of the current header file is HTTP_FILTER_REVISION.
lpszFilterDesc [out]. The location in which to store a short string description of the ISAPI filter.
dwFlags [out] The combination of SF_NOTIFY_* flags to specify which events this application is to process. Table 14.1 lists the valid SF_NOTIFY_* flags for IIS version 3.0.
Table 14.1 Valid SF_NOTIFY_* Flags for IIS Version 3.0
Flag | Description |
SF_NOTIFY_SECURE_PORT | Notifies application only for sessions over a secure port. |
SF_NOTIFY_NONSECURE_PORT | Notifies application only for sessions over a nonsecure port. |
SF_NOTIFY_READ_RAW_DATA | Allows the application to see the raw data. The data returned holds both headers and data. |
SF_NOTIFY_PREPROC_HEADERS | Server has reprocessed the headers. |
SF_NOTIFY_AUTHENTICATION | Server is authenticating the client. |
SF_NOTIFY_URL_MAP | Server is mapping a logical URL to a physical path. |
SF_NOTIFY_SEND_RAW_DATA | Server is sending raw data back to the client. |
SF_NOTIFY_LOG | Server is writing information to the server log. |
SF_NOTIFY_END_OF_NET_SESSION | Session with the client is ending. |
SF_NOTIFY_ACCESS_DENIED | Allows a filter to be notified when the server is about to return a 401 Access Denied. This allows the filter to analyze the failure and return a custom message. |
SF_NOTIFY_ORDER_DEFAULT | Loads the filter at the default priority (recommended). |
SF_NOTIFY_ORDER_LOW | Loads the filter at the low priority. |
SF_NOTIFY_ORDER_MEDIUM | Loads the filter at a medium priority. |
SF_NOTIFY_ORDER_HIGH | Loads the filter at a high priority. |
As we said earlier, GetFilterVersion holds key information that must be set before the server begins notifying the filter of events. The first two items to be set in the HTTP_FILTER_VERSION structure are dwFilterVersion and lpszFilterDesc. These items give the Web service version information for the filter and a text description that can be displayed in processing logs.
You should test the HTTP_FILTER_VERSION dwServerFilterVersion member if you are developing the filter for a specific version of IIS. Use the HIWORD and LOWORD macros to extract the major version and minor version numbers of the Web service. Listing 14.2 is an example of how to test the server's version information. When you set the filter's version number, use the MAKELONG macro to set the high and low word that make up the HTTP_FILTER_VERSION structure dwFilterVersion member.
![]()
The most important data element to set in the HTTP_FILTER_VERSION structure is dwFlags. This member tells the Web service which events the filter is to process.
Table 14.1 lists the event notification flags to set for IIS version 3.0. Typically, you can copy this example of a GetFilterVersion function and use it for each new filter. You need to change only the description and events notification flags.
Listing 14.2 LST14_2.C-GetFilterVersion Function
/*
GetFilterVersion - An ISAPI/Win32 API method
This method is required by IIS. It is called
following the process load to ensure that the
filter is compatable with the server.
*/
BOOL WINAPI GetFilterVersion(HTTP_FILTER_VERSION * pVer)
{
WORD wMajorVersion=0;
WORD wMinorVersion=0;
wMajorVersion = HIWORD( pVer->dwServerFilterVersion );
wMinorVersion = LOWORD( pVer->dwServerFilterVersion );
/*
This filter is intended for IIS version 2.0 or greater.
This is an example of how to test the server version.
*/
if (wMajorVersion < 2) return FALSE;
pVer->dwFilterVersion = MAKELONG( 0, 1 ); // Version 1.0
/*
Specify the security level of notifications
(secured port, nonsecured port, or both), the
types of events and order of notification for
this filter (high, medium or low, default=low).
*/
pVer->dwFlags = (
SF_NOTIFY_SECURE_PORT |
SF_NOTIFY_NONSECURE_PORT |
SF_NOTIFY_READ_RAW_DATA |
SF_NOTIFY_PREPROC_HEADERS |
SF_NOTIFY_URL_MAP |
SF_NOTIFY_AUTHENTICATION |
SF_NOTIFY_ACCESS_DENIED |
SF_NOTIFY_SEND_RAW_DATA |
SF_NOTIFY_LOG |
SF_NOTIFY_END_OF_NET_SESSION |
SF_NOTIFY_ORDER_DEFAULT
);
/*
A brief one line description of the filter
*/
strcpy( pVer->lpszFilterDesc, "Sample Filter, v1.0" );
return TRUE;
}
IIS makes assumptions if certain notification flags are not specified. If a port notification flag is not specified, IIS assumes that the filter is to get notifications for both secure (SF_NOTIFY_SECURE_PORT) and nonsecure (SF_NOTIFY_NONSECURE_PORT) port requests. If a priority order is not specified, IIS assumes the default priority (SF_NOTIFY_ORDER_DEFAULT). This is the same as specifying a low priority (SF_NOTIFY_ORDER_LOW).
![]()
HttpFilterProc is the callback function that gets the event notification from the Web service. HttpFilterProc is to IIS what WindowProc is to the Windows operating system: the main event message loop. This is where the event messages are parceled out to the worker functions to do the task at hand.
The HttpFilterProc prototype is
DWORD WINAPI HttpFilterProc(
PHTTP_FILTER_CONTEXT pfc,
DWORD notificationType,
LPVOID pvNotification
);
The parameters for HttpFilterProc include a pointer, pfc, to the HTTP_FILTER_CONTEXT structure that can be used by the filter to associate any context information with the HTTP request. The HttpFilterProc parameters also include pointers to callback functions in the Web service for two-way communications.
The other two parameters, notificationType and pvNotification, have a direct correlation. The data pointed to by the void pointer, pvNotification, can take the shape of several structures, based on the event notification type, notificationType.
Table 14.2 shows the correlation between notificationType and the structures pointed to by pvNotification.
Table 14.2 Notification Types and Structures
Notification Type | Structure Name |
SF_NOTIFY_READ_RAW_DATA | HTTP_FILTER_RAW_DATA |
SF_NOTIFY_SEND_RAW_DATA | HTTP_FILTER_RAW_DATA |
SF_NOTIFY_PREPROC_HEADERS | HTTP_FILTER_PREPROC_HEADERS |
SF_NOTIFY_AUTHENTICATION | HTTP_FILTER_AUTHENT |
SF_NOTIFY_URL_MAP | HTTP_FILTER_URL_MAP |
SF_NOTIFY_LOG | HTTP_FILTER_LOG |
SF_NOTIFY_ACCESS_DENIED | HTTP_FILTER_ACCESS_DENIED |
A filter's HttpFilterProc serves as a traffic director for routing events to the right worker function. Listing 14.3 is an example of a typical HttpFilterProc set up to accept all the possible HTTP communication events.
Listing 14.3 LST14_3.C-HttpFilterProc Example
/*
HttpFilterProc - ISAPI / Win32 API method
This method is a required by IIS. It is called
for each notification event requested. This is
where the filter accomplishes its purpose in life.
*/
DWORD WINAPI HttpFilterProc(HTTP_FILTER_CONTEXT *pfc,
DWORD NotificationType,
VOID * pvData)
{
DWORD dwRet;
/*
Direct the notification to the appropriate
routine for processing.
*/
switch ( NotificationType )
{
case SF_NOTIFY_READ_RAW_DATA:
dwRet = OnReadRawData(pfc, (PHTTP_FILTER_RAW_DATA) pvData );
break;
case SF_NOTIFY_PREPROC_HEADERS:
dwRet = OnPreprocHeaders(pfc, (PHTTP_FILTER_PREPROC_HEADERS) pvData );
break;
case SF_NOTIFY_URL_MAP:
dwRet = OnUrlMap(pfc, (PHTTP_FILTER_URL_MAP) pvData );
break;
case SF_NOTIFY_AUTHENTICATION:
dwRet = OnAuthentication(pfc, (PHTTP_FILTER_AUTHENT) pvData );
break;
case SF_NOTIFY_ACCESS_DENIED:
dwRet = OnAccessDenied(pfc, (PHTTP_FILTER_ACCESS_DENIED) pvData );
break;
case SF_NOTIFY_SEND_RAW_DATA:
dwRet = OnSendRawData(pfc, (PHTTP_FILTER_RAW_DATA) pvData );
break;
case SF_NOTIFY_LOG:
dwRet = OnLog(pfc, (PHTTP_FILTER_LOG) pvData );
break;
case SF_NOTIFY_END_OF_NET_SESSION:
dwRet = OnEndOfNetSession(pfc);
break;
default:
dwRet = SF_STATUS_REQ_NEXT_NOTIFICATION;
break;
}
return dwRet;
}
pfc is a pointer to the HTTP_FILTER_CONTEXT structure for all request notification types. It keeps a consistent state between HTTP communication events and has this layout:
typedef struct _HTTP_FILTER_CONTEXT
{
DWORD cbSize; // structure size
DWORD Revision; // revision level
PVOID ServerContext; // context info for
the server
DWORD ulReserved; // for future use
BOOL fIsSecurePort; // TRUE=request from
secure port
PVOID pFilterContext; // context info
for the filter
//
// Server callbacks
//
BOOL (WINAPI * GetServerVariable) (
struct _HTTP_FILTER_CONTEXT * pfc,
LPSTR lpszVariableName,
LPVOID lpvBuffer,
LPDWORD lpdwSize
);
BOOL (WINAPI * AddResponseHeaders) (
struct _HTTP_FILTER_CONTEXT * pfc,
LPSTR lpszHeaders,
DWORD dwReserved
);
BOOL (WINAPI * WriteClient) (
struct _HTTP_FILTER_CONTEXT * pfc,
LPVOID Buffer,
LPDWORD lpdwBytes,
DWORD dwReserved
);
VOID * (WINAPI * AllocMem) (
struct _HTTP_FILTER_CONTEXT * pfc,
DWORD cbSize,
DWORD dwReserved
);
BOOL (WINAPI * ServerSupportFunction) (
struct _HTTP_FILTER_CONTEXT * pfc,
enum SF_REQ_TYPE sfReq,
PVOID pData,
DWORD ul1,
DWORD ul2
);
} HTTP_FILTER_CONTEXT, *PHTTP_FILTER_CONT
fIsSecurePort
The filter context structure has a member named fIsSecurePort, which is a Boolean flag that indicates whether the HTTP request was made over a secured port using the secure socket layer (SSL) encryption protocol. If the request did originate from client requesting a secure port, this flag is set to TRUE or a nonzero value.
pFilterContext
The pFilterContext member of the filter context structure is a void pointer. It can be replaced with a pointer to data that is relevant in the context of the HTTP request.
Use the filter-context AllocMem callback function or a standard memory-allocation method to allocate memory and set pFilterContext to the allocated memory and data structure.
Since the SF_NOTIFY_END_OF_NET_SESSION is the last event in the HTTP communication flow, you can request it in the GetFilterVersion function. This allows the release of resources requested during the context of a single HTTP request. If memory is allocated using the AllocMem callback function, resources are automatically released at the end of the HTTP communication.
![]()
In the HTTP_FILTER_CONTEXT structure are five callback functions, all for two-way communication between the filter and the Web service. These functions are detailed next.
GetServerVariable Callback Function
The first callback function found in HTTP_FILTER_CONTEXT is GetServerVariable. This function retrieves information about a connection or about the Web service. The function prototype and parameters are as follows:
BOOL WINAPI GetServerVariable (
PHTTP_FILTER_CONTEXT pfc,
LPSTR lpszVariableName,
LPVOID lpvBuffer
LPDWORD lpdwSizeofBuffer
);
The parameters for GetServerVariable include the following:
Table 14.3 Possible Server Variables
Variable Name | Description |
AUTH_TYPE | Holds the type of authentication used. For example, if basic authentication is used, the string is Basic. For NT challenge-response, it is NTLM. Other authentication schemes have other strings. |
CONTENT_LENGTH | Number of bytes the script can expect to receive from the client. |
CONTENT_TYPE | Content type of the information supplied in the body of a POST request. |
PATH_INFO | Additional path information, as given by the client. This consists of the trailing part of the URL after the script name but before the query string, if any. |
PATH_TRANSLATED | Value of PATH_INFO but with any virtual path name expanded into a directory specification. |
QUERY_STRING | Information that follows the "?" in the URL that referenced this script. |
REMOTE_ADDR | IP address of the client or agent of the client (for example, a gateway or firewall) that sent the request. |
REMOTE_HOST | Host name of the client or agent of the client (for example, a gateway or firewall) that sent the request. |
REMOTE_USER | Holds the user name supplied by the client and authenticated by the server. This comes back as an empty string when the user is anonymous (but authenticated). |
UNMAPPED_REMOTE_USER | User name before a filter maps the user making the request to an NT user account (which appears as REMOTE_USER). |
REQUEST_METHOD | HTTP request method. |
SCRIPT_NAME | Name of the script program executed. |
SERVER_NAME | Server's host name or IP address as it should appear in self-referencing URLs. |
SERVER_PORT | TCP/IP port on which the request was received. |
SERVER_PORT_SECURE | String of either 0 or 1. If the request is handled on the secure port, this is 1. Otherwise, it is 0. |
SERVER_PROTOCOL | Name and version of the information- retrieval protocol relating to this request. This is normally HTTP/1.0. |
SERVER_SOFTWARE | Name and version of the server under which the ISAPI filter DLL program is running. |
ALL_HTTP | All HTTP headers not already parsed into one of the previous variables. These variables are of the HTTP_<header field name> form. The headers consist of a null-terminated string with the individual headers separated by line feeds. |
HTTP_ACCEPT | Special-case HTTP header. Values of the Accept: fields are concatenated and separated by a comma (","). For example, if the following lines are part of the HTTP header:
accept: */*; q=0.1 accept: text/html accept: image/jpeg the HTTP_ACCEPT variable has a value of: */*; q=0.1, text/html, image/jpeg |
URL | Gives the base portion of the URL. |
If GetServerVariable returns successfully, the return value is TRUE. If the function fails, you can use the Win32 GetLastError function to determine why the function call failed.
Table 14.4 lists the errors that can be returned by GetLastError, following a failed return from GetServerVariable.
Table 14.4 GetServerVariable: Possible Errors
Value | Description |
ERROR_INVALID_PARAMETER | Bad connection handle. |
ERROR_INVALID_INDEX | Bad or incompatible variable identifier |
ERROR_INSUFFICIENT_BUFFER | Buffer too small. The necessary buffer size is lpdwSize. |
ERROR_MORE_DATA | Buffer too small. Only part of the data is returned. The total size of the data is not known. |
ERROR_NO_DATA | The data requested is not available. |
Listing 14.4 is an example of how to access the Web service variables using GetServerVariables.
Listing 14.4 LST14_4.C-GetServerVariable Example
/*
The following is an example of how to use GetServerVariable to
retrieve information concerning the web server or the communication
in process. In this example pfc is a pointer to the
HTTP_FILTER_CONTEXT structure.
*/
CHAR achUserAgent[4182];
DWORD cbUserAgent= 4182;
// Execute the server callback function GetServerVariable
if ( !pfc->GetServerVariable( pfc,"HTTP_USER_AGENT",achUserAgent,&cbUserAgent ))
{
/*
There was an error in processing the request. This is an example
of checking the error type using the Win32 function GetLastError.
*/
if (ERROR_INVALID_INDEX != GetLastError()) (
return SF_STATUS_REQ_ERROR;
}
}
AddResponseHeaders Callback Function
The AddResponseHeaders callback function allows the server to add response header information to the HTTP communication response to the client. The function prototype for AddResponseHeaders is
BOOL WINAPI AddResponseHeaders) (
PHTTP_FILTER_CONTEXT pfc,
LPSTR lpszHeaders,
DWORD dwReserved
);
The parameters for AddResponseHeaders include the following:
AddResponseHeaders is normally used with the WriteClient callback function, which allows HTTP or binary data to be written directly to the client. Response headers supply information to the client about the data to be transmitted.
You can formulate a complete communication reply by using AddResponseHeader to notify the client of a data type to be sent to a client and WriteClient to write the data to the client.
Listing 14.5 is an example of how to add response headers to an HTTP request using AddResponseHeaders.
Listing 14.5 LST14_5.C-AddResponseHeaders Example
/*
The following is an example of how to use AddResponseHeaders to inform
the client browser of the content length of the data about to be
transmitted.
*/
// Execute the server callback function AddResponseHeaders
if ( !pfc->AddResponseHeaders( pfc, "Content-Length = 100", NULL))
{
/*
There was an error in processing the request so
do error processing here
*/
}
WriteClient Callback Function
The WriteClient callback function sends data to the client from a buffer provided by the filter. WriteClient enables the transfer of binary data because it does not assume that a null-terminated string is passed back to the client.
The function prototype for WriteClient is
BOOL WINAPI WriteClient (
PHTTP_FILTER_CONTEXT pfc,
LPVOID lpvBuffer,
LPDWORD lpdwSizeofBuffer,
DWORD dwReserved
);
The parameters for WriteClient include the following:
WriteClient is often used with AddResponseHeaders to complete an HTTP request. If lpvBuffer points to a null-terminated string and the whole string is to be sent, lpdwSizeofBuffer should be set to the length of the string.
On its return from the call, lpdwSizeofBuffer is equal to the number of bytes transferred. This number is less than the original amount only if the original call returns FALSE.
Listing 14.6 is an example of how to transfer data to the client using WriteClient.
Listing 14.6 LST14_6.C-WriteClient Example
/*
The following is an example of how to use WriteClient to transfer
data to the client browser.
*/
char szResponse[100];
char szTime[20];
DWORD dwRespLen=0;
DWORD dwError=0;
// Create a forced response string
_strtime(szTime);
sprintf(szResponse,
"<HTML><BODY><H1>Forced Response at %s</H1></BODY></HTML>",
szTime);
dwRespLen=strlen(szResponse);
// Execute the server callback function WriteClient
if ( !pfc->WriteClient( pfc, (LPVOID)&szResponse,(LPDWORD)&dwRespLen, NULL))
{
dwError = GetLastError();
}
AllocMem Callback Function
The AllocMem callback function allocates a generic memory buffer that is automatically freed at the end of the HTTP request. The function prototype for AllocMem is
LPVOID AllocMem (
DWORD cbSize,
DWORD dwReserved
);
The parameters for AllocMem include the following:
The return value is a void pointer (LPVOID) to a memory buffer that was allocated by the function call.
You can't manage memory blocks allocated with AllocMem with the normal C runtime or Windows API memory management functions.
![]()
Calls to AllocMem can reduce performance because the burden of memory management is on the Web service. You might increase the filter's performance by allowing the filter to manage its own memory allocations. Allocate memory as needed and then release it at the end of the HTTP communication request. The last event in the HTTP request is SF_NOTIFY_END_OF_NET_SESSION. The filter has to register for this event in the GetFilterVersion function to get a notification of the end-of-net-sessions event.
![]()
Listing 14.7 is an example of how to allocate a block of memory with a call to AllocMem.
Listing 14.7 LST14_7.C-AllocMem Example
/*
The following is an example of how to use AllocMem to
allocate a block of memory.
*/
char* pszBuffer;
pszBuffer=(CHAR*)pfc->AllocMem(pfc, 400, (DWORD)NULL);
memset(pszBuffer,'\0',sizeof(pszBuffer));
ServerSupportFunction Callback Function
The ServerSupportFunction callback function gives the filter general-purpose functions, plus methods specific to the Web service. The function prototype for ServerSupportFunction is
BOOL WINAPI ServerSupportFunction (
PHTTP_FILTER_CONTEXT pfc,
enum SF_REQ_TYPE sfRequest,
PVOID pvData,
LPDWORD lpdwSizeofBuffer,
LPDWORD lpdwDataType
);
The parameters for ServerSupportFunction include:
Value | Description |
SF_REQ_SEND_RESPONSE_HEADER | Sends a complete HTTP server response header, including the status, server version, message time, and Content-type MIME version. Server extensions should append other information at the end, such as and Content-length, etc. followed by an extra '\r\n'. |
SF_REQ_ADD_HEADERS_ON_DENIAL | If the server denies the HTTP request, add the specified headers to the server error response. This allows an authentication filter to advertise its services without filtering every request.
Generally, the headers are WWW-Authenticate headers with custom authentication schemes but no restriction on what headers can be specified. |
SF_REQ_SET_NEXT_READ_SIZE | Used only by raw-data filters that return SF_STATUS_READ_NEXT lpdwSizeofBuffer size in bytes for the next read. |
Listing 14.8 is an example of one of the possible function calls using ServerSupportFunction.
Listing 14.8 LST14_8.C-ServerSupportFunction Example
/*
The following is an example of how to use ServerSupportFunction to read
additional data from the client browser.
*/
char szBuffer[100];
DWORD dwBufferLen=0;
dwszBufferLen=sizeof(szBuffer);
pfc->ServerSupportFunction(pfc,
SF_REQ_SET_NEXT_READ_SIZE,
(PVOID)&szBuffer,
dwBufferLen,
(DWORD)NULL );
When the necessary functions are in your filter and the notification events have been requested, you're ready to do your processing. But when and where does the code get written?
Chapter 12, "Using ISAPI Filters," outlines the sequence of events in the HTTP request. Chapter 12 also gives you ideas for the kinds of filters you can create.
Listing 14.3 earlier in this chapter is the backbone for HTTP request event processing. Worker functions are called from the switch/case statement or processing is done in the case statement.
The tutorial at the end of chapter brings together all the pieces we discussed with an example of how to change the raw data sent back to the browser.
When the filter's processing is done, the right return code has to be passed back to the Web service. The return code tells the Web service how the event was handled and what process is needed next.
Table 14.6 lists the valid return codes and their meanings for the HttpFilterProc function.
Table 14.6 HttpFilterProc Return Codes
Value | Description |
SF_STATUS_REQ_FINISHED | The filter has handled the HTTP request. The server should disconnect the session. |
SF_STATUS_REQ_FINISHED_KEEP_CONN | This is the same as SF_STATUS_REQ_FINISHED, except that the server should keep the TCP session open if the option was negotiated. |
SF_STATUS_REQ_NEXT_NOTIFICATION | The next filter in the notification chain should be called. |
SF_STATUS_REQ_HANDLED_NOTIFICATION | This filter handled the notification. No other handlers should be called for this notification type. |
SF_STATUS_REQ_ERROR | An error occurred. The server should call GetLastError and indicate the error to the client. |
SF_STATUS_REQ_READ_NEXT | The filter is an opaque stream filter and the session parameters are being negotiated. This is valid only for raw- read notification. |
The AdFlipperFilter project is on the companion CD to this book. AdFlipperFilter replaces a text string in an HTML page to create advertisement graphics with hyperlinks to the advertiser's Web site.
The filter uses a DllMain function to initialize filter-wide parameters. When DllMain is called with the flag, DLL_PROCESS_ATTACH, a critical-section handle is created for thread-safe processing.
The contents of a configuration file are loaded into global variables for be used throughout the filter. When DllMain is called with DLL_PROCESS_DETACH, clean-up processing is done by deleting the critical section handle.
AdFlipperFilter's GetFilterVersion function requests notifications for two specific events, SF_NOTIFY_URL_MAP and SF_NOTIFY_SEND_RAW_DATA. The URL map event is requested to analyze the name of the HTML page requested. The send raw data event is requested because that is were the raw data being sent to the client browser can be manipulated.
By default, IIS assumes that a filter will be used for processing on both secure and nonsecure ports. IIS also assumes that the filter will process at a default priority, which is defined as low priority. For our tutorial, the SF_NOTIFY_* flags are requested specifically to ensure proper processing.
AdFlipperFilter's HttpFilterProc function directs the event traffic to the proper worker function. The function's switch/case statement directs notification events of SF_NOTIFY_URL_MAP to the OnUrlMap function and SF_NOTIFY_SEND_RAW_DATA to the OnSendRawData function. All other extraneous events are bypassed.
The OnUrlMap function analyzes the URL resource that is requested. In this example, we want to do processing only for resources that end with the ads.htm string. So URLs like http://www.yoursite.com/ads.htm and http://www.yoursite.com/HOMads.htm are valid for processing.
When a URL passes the test for AdFlipperFilter processing, the filter-context callback function AllocMem is called to create a memory buffer to hold the image and link string. The InsertAd function is called with the memory buffer just created.
The InsertAd function uses the filter's global variables and configuration file to determine which advertisement is next in the rotation. The function then constructs the image-and- link strings to be inserted into the body of HTML page at the special tag placeholder.
The image-and-link string that is created in the InsertAd function also calls an ISAPI ClickThruExtension.dll extension. This extension is on the companion CD in the AdFlipperFilter project directory. The ClickThruExtension.dll extension tallies the number of times a user clicks on an advertisement that passes control to the advertiser's Web site. Chick-thru statistics can be reviewed by placing a request with the ISAPI SiteStatsExtension.dll extension (also on the companion CD in the AdFlipperFilter project directory). A request to the http://www.yoursite.com/scripts/SiteStatsExtension.dll?ClickThrus resource produces an HTML page with the number of click thrus and the first date a click thru occurred, by URL. You should put the supplemental extensions mentioned above in the IIS scripts directory before you try to use them.
![]()
The OnSendRawData function is called twice in the HTTP communication flow. It's first called with the response headers to be passed back to the client browser.
These headers include the Content-Length header, which tells the browser how much data to expect. Since AdFlipperFilter expands the HTML page size, the Content-Length header needs to be increased to include the number of bytes added by the image-and-link string.
The second call to the OnSendRawData function is just before the server passes the HTML page to the browser. This is when the function inserts the image-and-link string into the page.
Listing 14.9 is the source code for the AdFlipperFilter.
Listing 14.9 ADFLIPPERFILTER.C-AdFlipperFilter Source Code
/*
Copyright (c) 1996 ClearMind, Inc.
Module Name:
AdFlipperFilter.c
Abstract:
This filter checks HTML pages accessed at the server for
the special advertisement tags. Then it looks into its
database for the next ad to run on the page. It then
inserts the hyperlink, graphic and alternate text for the
advertiser.
*/
#include <windows.h>
#include <httpfilt.h>
#include <string.h>
#include <stdio.h>
#include <stdarg.h>
#define ADINI "d:\\inetsrv\\scripts\\filters\\Ad.INI"
#define ADLINKS "AdLinks"
#define ADGRAPHICS "AdGraphics"
#define ADHEIGHTS "AdHeights"
#define ADWIDTHS "AdWidths"
#define ADTARGETS "AdTargets"
/*
Private prototypes
*/
DWORD OnUrlMap(HTTP_FILTER_CONTEXT *pfc,
HTTP_FILTER_URL_MAP *pvData);
DWORD OnSendRawData(HTTP_FILTER_CONTEXT *pfc,
HTTP_FILTER_RAW_DATA *pvData);
//void InsertAd(char* pszBuffer, char* pszAdBuffer, int b);
void InsertAd(char* pszAdBuffer);
/*
Globals
*/
CRITICAL_SECTION gCS; // A critical section handle
// is used to protect global
// state properties
char gszAdLinks[2048];
char* gpszAdLink;
char* gpszNewBuffer;
char* gpszAdBuffer;
unsigned int giCurrentAd;
unsigned int giLastAd;
unsigned int giExpandedLen;
/*
This the the entry and exit point for the filter
it is called when the filter is loaded and unloaded
by IIS. This is where state properties need to be
retrieved and store on persistant storage.
*/
BOOL APIENTRY DllMain( HANDLE hModule,
DWORD ul_reason_for_call,
LPVOID lpReserved )
{
unsigned int i = 0;
switch( ul_reason_for_call ) {
case DLL_PROCESS_ATTACH:
{
/*
On process attach we will set the state
variables for the filter. This entails
counting the number of advertisers we're
flipping ads for.
*/
InitializeCriticalSection(&gCS);
EnterCriticalSection(&gCS);
giCurrentAd=1;
giLastAd=1;
GetPrivateProfileString(ADLINKS, NULL, TEXT(" "),
gszAdLinks, sizeof(gszAdLinks),
ADINI);
gpszAdLink = (char*)gszAdLinks;
while (gszAdLinks[i])
{
giLastAd++;
i+=strlen(gpszAdLink)+1;
gpszAdLink+=strlen(gpszAdLink)+1;
}
gpszAdLink = (char*)gszAdLinks;
LeaveCriticalSection(&gCS);
break;
}
// case DLL_THREAD_ATTACH:
// case DLL_THREAD_DETACH:
case DLL_PROCESS_DETACH:
{
DeleteCriticalSection(&gCS);
break;
}
}
return TRUE;
}
/*
GetFilterVersion - An ISAPI/Win32 API method
This method is required by IIS. It is called
following the process load to ensure that the
filter is compatable with the server.
*/
BOOL WINAPI GetFilterVersion(HTTP_FILTER_VERSION * pVer)
{
pVer->dwFilterVersion = MAKELONG( 0, 1 ); // Version 1.0
/*
Specify the security level of notifications
(secured port, nonsecured port, or both), the
types of events and order of notification for
this filter (high, medium or low, default=low).
*/
pVer->dwFlags = (
SF_NOTIFY_SECURE_PORT |
SF_NOTIFY_NONSECURE_PORT |
SF_NOTIFY_URL_MAP |
SF_NOTIFY_SEND_RAW_DATA |
SF_NOTIFY_ORDER_DEFAULT
);
/*
A brief one line description of the filter
*/
strcpy( pVer->lpszFilterDesc, TEXT("Ad Flipper Filter, v1.1"));
return TRUE;
}
/*
HttpFilterProc - ISAPI / Win32 API method
This method is a required by IIS. It is called
for each notification event requested. This is
where the filter accomplishes its purpose in life.
*/
DWORD WINAPI HttpFilterProc(HTTP_FILTER_CONTEXT *pfc,
DWORD NotificationType,
VOID * pvData)
{
DWORD dwRet;
/*
Direct the notification to the appropriate
routine for processing.
*/
switch ( NotificationType )
{
case SF_NOTIFY_URL_MAP:
dwRet = OnUrlMap(pfc, (PHTTP_FILTER_URL_MAP) pvData );
break;
case SF_NOTIFY_SEND_RAW_DATA:
dwRet = OnSendRawData(pfc, (PHTTP_FILTER_RAW_DATA) pvData );
break;
default:
dwRet = SF_STATUS_REQ_NEXT_NOTIFICATION;
break;
}
return dwRet;
}
/*
IIS Filter Event Routines
*/
/*
OnUrlMap -
The data returned within pvData includes the URL
requested (pvData->pszURL) and the full path to
the physical data (pvData->pszPhysicalPath.
*/
DWORD OnUrlMap(HTTP_FILTER_CONTEXT *pfc,
HTTP_FILTER_URL_MAP *pvData)
{
/*
In this routine we will check to see if the page begin
requested ends with "ads.htm". This will signify an
advertisement page. Then we will create the text link
and image line that will create the ad...
*/
if (strstr(pvData->pszURL, TEXT("ads.htm"))) {
gpszAdBuffer=(CHAR*)pfc->AllocMem(pfc, 400, (DWORD)NULL);
memset(gpszAdBuffer,'\0',sizeof(gpszAdBuffer));
InsertAd(gpszAdBuffer);
pfc->pFilterContext = (void*)gpszAdBuffer;
}
return SF_STATUS_REQ_NEXT_NOTIFICATION;
}
/*
OnSendRawData -
This routine is called twice for this event.
The first time it is called is when it sends
the browser a notification of the actual data
it is about to transmit(e.g. text/html, image/gif,
etc.) The second time this routine is called
is when the actual data (e.g. text, gif, etc.)
is being transmitted to the browser.
*/
DWORD OnSendRawData(HTTP_FILTER_CONTEXT *pfc,
HTTP_FILTER_RAW_DATA *pvData)
{
CHAR* pszBuffer;
DWORD cbNewInData=0;
BOOL bAdPage=FALSE;
DWORD i=0;
DWORD j=0;
char* pcBefore;
char* pcBetween;
char* pcAfter;
char cCurrentLen[5];
int c=0;
HTTP_FILTER_RAW_DATA* pRawData = (PHTTP_FILTER_RAW_DATA) pvData;
pszBuffer=pvData->pvInData;
if (pfc->pFilterContext) // Is this an ad?
{
/* This first call to OnSendRawData are the response
headers. Since we are expanding the page size, we
need to alter the "Content-Length" header.
*/
if (strstr((char*) pRawData->pvInData,"HTTP/1.0"))
{
giExpandedLen=strlen((char*) pfc->pFilterContext);
memset(cCurrentLen, '\0', sizeof(cCurrentLen));
pcBefore=(char*)pRawData->pvInData;
pcBetween=strstr(pcBefore,"Content-Length:");
if (pcBetween)
{
pcAfter=pcBetween;
while (*pcAfter>=' ')
{
if (*pcAfter>='\x30' && *pcAfter <='\x39')
cCurrentLen[c++]=*pcAfter;
pcAfter++;
}
c = atoi((char*)cCurrentLen);
giExpandedLen+=c;
*pcBetween='\0';
sprintf((char*) pRawData->pvInData,
"%sContent-Length: %i%s",
pcBefore,giExpandedLen,pcAfter);
pRawData->cbInData=(DWORD) strlen((char*) pRawData->pvInData);
}
}
else
{
/* This is the second call to OnSendRawData, this call
contains the actual html page. This section checks
for the special tag we're using. "<!ADIN>" as a place
holder for the link and image line.
*/
if (strstr((char*)pvData->pvInData,"<!%ADIN%>"))
{
gpszNewBuffer=(CHAR*)pfc->AllocMem(pfc, giExpandedLen+1, (DWORD)NULL);
memset(gpszNewBuffer,'\0',giExpandedLen+1);
pcBefore=(char*)pRawData->pvInData;
pcBetween=strstr(pcBefore,"<!%ADIN%>");
if (pcBetween)
{
pcAfter=pcBetween;
while (*pcAfter!='>') { pcAfter++; }
pcAfter++;
*pcBetween='\0';
sprintf(gpszNewBuffer,"%s%s%s",pcBefore,gpszAdBuffer,pcAfter);
pRawData->pvInData=(void*)gpszNewBuffer;
pRawData->cbInData=giExpandedLen;
}
}
}
}
return SF_STATUS_REQ_NEXT_NOTIFICATION;
}
/*
The following #define define all the possible string
combinations that may inserted into the html page
*/
#define LINK_STRING "<A HREF=\"http:/scripts/ClickThruExtension.dll?http://%s\"><IMG SRC=\"/images/%s\" ALT=\"%s\" BORDER=0></A>"
#define LINK_STRING2 "<A HREF=\"http:/scripts/ClickThruExtension.dll?http://%s\"><IMG SRC=\"/images/%s\" ALT=\"%s\" BORDER=0 height=\"%s\" width=\"%s\"></A>"
#define LINK_STRING3 "<A HREF=\"http:/scripts/ClickThruExtension.dll?http://%s\" target=\"%s\"><IMG SRC=\"/images/%s\" ALT=\"%s\" BORDER=0 height=\"%s\" width=\"%s\"></A>"
/*
InsertAd -
The routine gets the graphic and alternate text from
the database and creates the graphic link HTML line
and inserts it into the HTML page.
*/
void InsertAd(char* pszAdBuffer){
char szGraphic[100];
char szAltText[100];
char szHeight[7];
char szWidth[7];
char szTarget[20];
unsigned int i=0;
GetPrivateProfileString(ADGRAPHICS, gpszAdLink,
TEXT("clearmind.gif"), szGraphic,
sizeof(szGraphic), ADINI);
GetPrivateProfileString(ADLINKS, gpszAdLink,
TEXT("ClearMind Inc."), szAltText,
sizeof(szAltText), ADINI);
GetPrivateProfileString(ADHEIGHTS, gpszAdLink,
TEXT("\0"), szHeight,
sizeof(szHeight), ADINI);
GetPrivateProfileString(ADWIDTHS, gpszAdLink,
TEXT("\0"), szWidth,
sizeof(szWidth), ADINI);
GetPrivateProfileString(ADTARGETS, gpszAdLink,
TEXT("\0"), szTarget,
sizeof(szTarget), ADINI);
if (szHeight[0] == '\0' || szWidth[0] == '\0') {
wsprintf(pszAdBuffer, LINK_STRING,
gpszAdLink, szGraphic, szAltText);
} else {
if (szTarget[0] =='\0') {
wsprintf(pszAdBuffer, LINK_STRING2,
gpszAdLink, szGraphic, szAltText, szHeight, szWidth);
} else {
wsprintf(pszAdBuffer, LINK_STRING3,
gpszAdLink, szTarget, szGraphic, szAltText, szHeight, szWidth);
}
}
giCurrentAd++;
if (giLastAd==giCurrentAd){
giCurrentAd = 1;
gpszAdLink=(char*)gszAdLinks;
}
else {
gpszAdLink+=strlen(gpszAdLink)+1;
}
return;
}
Use these instructions to built the AdFlipperFilter.dll:
nmake /f makefile CFG="AdFlipperFilter - Win32 Release
Use these instructions to install the AdFlipperFilter.dll:
HKEY_LOCAL_MACHINE\System\CurrentControlSet\Services\W3SCVC\Parameters
ISAPI filters enable you to interact with the request event data and Web server data at a low level. The following chapters tell you more about how to construct filters, and how filters interact with the Web service and the HTTP communication flow.
© 1996, QUE Corporation, an imprint of Macmillan Publishing USA, a Simon and Schuster Company.