Chapter 15

Building Filters with MFC ISAPI Classes


If you're familiar with Microsoft Foundation Classes (MFC) and if you're relatively new to client-server computing, the MFC Internet Server Application Programming Interface (ISAPI) classes are a great way to get started writing ISAPI filters. You'll be working with a programming paradigm that you're familiar with and you'll be writing code that adds value to your filter.

If you have Microsoft Visual C++ (MSVC) version 4.1+, you'll be using the ISAPI Extension Wizard to get started quickly. This chapter tells you how to do that. Then we focus on how MFC works so you can apply what you learn in other chapters to your MFC ISAPI filter.

Creating a Filter with the Extension Wizard

MSVC version 4.1+ supplies a wizard to generate MFC ISAPI extensions and filters. This wizard generates the skeleton code that is the framework for your ISAPI extension or filter. As in other MFC applications, the wizard enables you to concentrate on the functionality of your program.

Using the Extension Wizard

To start the Extension Wizard, run MSVC and select File, New, Project Workspace. From the New Project Workspace dialog, shown in Fig. 15.1, select the ISAPI Extension Wizard. Name the project EXAMPLE and click Create.

Fig. 15.1

Starting the Extension Wizard.

Step 1: Define the Filter

In step 1, you indicate whether you want your ISAPI project to have an extension, a filter, or both. Select Generate a Filter object and deselect Generate a Server Extension object," as shown in Fig. 15.2. You can also choose whether to use MFC in a dynamic-link library (DLL) or in a static library.

Fig. 15.2

ISAPI Extension Wizard, step 1.

Using MFC in a DLL or in a static library

Linking to MFC via a static library decreases the load time of your program and means you do not need to install other files with your program. But this performance boost does come with a penalty. Since each program would have its own copy of MFC, several ISAPI extensions or filters would need more memory than if you were to link to MFC dynamically.

Step 2: Choose the Notifications

In step 2, shown in Figure 15.3, you select the notifications. Normally, you only select the notifications you need to use your filter. But for this example, choose all notifications.

Fig. 15.3

Extension Wizard, step 2.

The OnLog() method is not automatically generated by the Extension Wizard. To add this method, open Class Wizard. Under the Messages list box, scroll until you see OnLog. Click OnLog and click Add Function (see Listing 15.1).

In object-oriented programming, a procedure providing access to an object's data is called a method. For example, in CExampleFilter, OnLog can also be called a method because it's a public member function (See Listing 15.2).


Listing 15.1 EXAMPLE.H-Add OnLog()Declaration to CExampleFilter

// ClassWizard generated virtual function overrides

// NOTE - the ClassWizard will add and remove member functions here.

// DO NOT EDIT what you see in these blocks of generated code !

//{{AFX_VIRTUAL(CExampleFilter)

public:

...

virtual DWORD OnLog(CHttpFilterContext* pCtxt, PHTTP_FILTER_LOG pLog);

//}}AFX_VIRTUAL

Listing 15.2 EXAMPLE.CPP-Add OnLog() to CExampleFilter

DWORD CExampleFilter::OnLog(CHttpFilterContext*, PHTTP_FILTER_LOG)

{

return SF_STATUS_REQ_NEXT_NOTIFICATION;

}

Chapter 14, "Creating an ISAPI Filter," describes the steps to create an ISAPI filter: create a 32-bit DLL, define GetFilterVersion(), and define HttpFilterProc(). The Extension Wizard does these steps in their most minimal form. Now you have a filter project named EXAMPLE that holds the files listed in Table 15.1.

Table 15.1 Files Generated by the Wizard to Complete the ISAPI Project

Name

Description

EXAMPLE.MAK

Project make file, which makes EXAMPLE.DLL.

EXAMPLE.H

Definition of the CExampleFilter class.

EXAMPLE.CPP

Implementation of the CExampleFilter class.

EXAMPLE.DEF

Exports for EXAMPLE.DLL.

EXAMPLE.RC

Resource file.

EXAMPLE.RC2

Resource file.

STDAFX.H

Common header for the project.

STDAFX.CPP

Precompiling source file.

Look at \MSDEV\MFC\INCLUDE\ISAPI.H and \MSDEV\MFC\SRC\ISAPI.CPP. ISAPI.H holds the definition of the CHttpFilter and CHttpFilterContext classes. ISAPI.CPP holds the implementation of the CHttpFilter class. It also holds the implementation of the GetFilterVersion() and HttpServerContext() functions exported from the DLL.

Building and Testing EXAMPLE.DLL

With EXAMPLE.MAK open as the current project in MSVC, select Project, Build. Before the filter can be used, we'll need to make a change in the Windows NT Registry. Open REGEDT32.EXE, which should be in your WINNT\SYSTEM32 folder.

ISAPI filter DLLs are registered under the HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services\W3SVC\FilterDLLs key. Type the full path and file name of your new filter. Separate multiple filters with commas.

Because changing the registry settings can have detrimental effects on your server, you should only use REGEDT32.EXE if you are familiar with the Windows NT Registry.

An alternative way to register your filters is to use Microsoft's Internet Information Server (IIS) hypertext transport protocol (HTTP) Configuration Utility. This is on the companion CD to this book in the IISCFG.ZIP file.

Start the World Wide Web Publishing Service using Internet Services Manager. Or look at the section on debugging in Chapter 17, "Troubleshooting and Debugging Extensions and Filters," for information on how to run the filter in the debugger.

When building the EXAMPLE filter, you may want to change the linker settings to generate the filter DLL in the directory where it will be used by the Web service. Typically, this is \winnt\system32\inetsrv.


If you do this, you'll have to stop the service before trying to link the filter DLL. Filter DLLs are loaded when the Web service starts and are not unloaded until it stops.

The MFC ISAPI Filter Class

Your filter uses the CExampleFilter class, which is derived from CHttpFilter. As described in Chapter 14, "Creating an ISAPI Filter," this is the first entry point called by your server.

The properties returned by the GetFilterVersion() method determine which notifications are received by the filter. The properties also determine the order in which these notifications are called, according the filter's priority flag.

Each of these notifications is received as a call to the HttpFilterProc() function, which passes the notification on to CExampleFilter::HttpFilterProc(). This method initializes the CHttpFilterContext object, checks the notification type, and calls the appropriate notification handler methods.

The CHttpFilterContext object and the notification-specific data are passed as arguments to the notification handler method.

The CHttpFilterContext class provides the GetServerVariable(), AddResponseHeaders(), WriteClient(), AllocMem(), and ServerSupportFunction() methods, which are wrappers for the API calls of the same name.

This class exists separately from CHttpFitler, allowing multiple threads in your CExampleFilter. It is the reason your ISAPI classes must be thread-safe. See Chapter 14, "Creating an ISAPI Filter," for details.

A wrapper is essentially an alternative interface to a function, group of functions, or class.

Figure 15.4 illustrates the concepts discussed above.

Fig. 15.4

ISAPI calls are processed by the exported DLL functions, which pass the calls to the MFC ISAPI object.

CHttpFilter and CExampleFilter

CHttpFilter is the MFC-defined class that is the base class for your filter. It provides an implementation of HttpFilterProc().

Your class, CExampleFilter, provides an implementation of GetFilterProc() and one or more of the notification handler methods-OnPreprocHeaders(), OnAuthentication(), OnUrlMap(), OnSendRawData(), OnReadRawData(), OnLog(), and OnEndOfNetSession().

A single instance of this class is created when the DLL is loaded. Remember, all registered filter DLLs are loaded when the Web service is started. As your filter receives requests, CHttpFilter creates a CHttpFilterContext object for each request. This permits simultaneous calls from multiple clients (see Listing 15.3).

Listing 15.3 FLTREX1.CPP-EXAMPLE.CPP: Filter Declared as Global Object

CExampleFilter theFilter;

GetFilterVersion()

Your MFC ISAPI filter DLL, like a non-MFC ISAPI filter DLL, has an exported the GetFilterVersion() function. In your MFC ISAPI DLL, the function calls the filter object's GetFilterVersion() method, as shown in Listing 15.4.

Listing 15.3 ISAPI.CPP-Exported GetFilterVersion()Passes Call to Filter Object

extern "C" BOOL WINAPI GetFilterVersion(PHTTP_FILTER_VERSION pVer)

{

#ifdef _AFXDLL

AFX_MANAGE_STATE(AfxGetStaticModuleState());

#endif

BOOL bRet;

ISAPIASSERT(pFilter != NULL);

if (pFilter == NULL)

bRet = FALSE;

else

bRet = pFilter->GetFilterVersion(pVer);

return bRet;

}

The GetFilterVersion() method used by your filter class specifies the priority of the filter, tells whether secure and nonsecure ports are filtered, and determines which notifications your filter handles, as shown in Listing 15.5). See Chapter 14, "Creating an ISAPI Filter," for details.

Listing 15.5 EXAMPLE.CPP-GetFilterVersion()Implementation Handles Notifications.

BOOL CExampleFilter::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_ORDER_LOW | SF_NOTIFY_SECURE_PORT | SF_NOTIFY_NONSECURE_PORT

| SF_NOTIFY_LOG | SF_NOTIFY_AUTHENTICATION | SF_NOTIFY_PREPROC_HEADERS

| SF_NOTIFY_READ_RAW_DATA | SF_NOTIFY_SEND_RAW_DATA | SF_NOTIFY_URL_MAP

| SF_NOTIFY_END_OF_NET_SESSION;

// 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;

}

To learn what the MFC ISAPI classes do, we use all the notification handler methods in this chapter. If you decide to change your sample filter to create a production filter, you'll change the pVer->dwFlags settings so that only the necessary notifications are processed by the filter. You can also remove the declarations and implementations of the unused methods from the filter class.

HttpFilterProc()

Your MFC ISAPI filter DLL has an exported HttpFilterProc() function consistent with the non-MFC ISAPI filter DLLs. In the MFC filter DLL, this function passes the call to the CHttpFilter::HttpFilterProc() method (see Listing 15.6).

This method is where the bulk of your filter's work is done. HttpFilterProc() is what in turn calls the various notification handlers you specified in GetFilerVersion().

The PHTTP_FILTER_CONTEXT structure holds information on the specific request being processed. The second parameter, NotificationType, is the type of event that has occurred. The last parameter, pvNotification, contains a notification-specific structure that gives HttpFilterProc() the notification handler to use.

Listing 15.4 ISAPI.CPP-Exported HttpFilterProc() Passes Call to Filter Object

extern "C" DWORD WINAPI HttpFilterProc(PHTTP_FILTER_CONTEXT pfc,

DWORD dwNotificationType, LPVOID pvNotification)

{

#ifdef _AFXDLL

AFX_MANAGE_STATE(AfxGetStaticModuleState());

#endif

DWORD dwRet;

ISAPIASSERT(pFilter != NULL);

if (pFilter == NULL)

dwRet = SF_STATUS_REQ_NEXT_NOTIFICATION;

else

dwRet = pFilter->HttpFilterProc(pfc,

dwNotificationType, pvNotification);

return dwRet;

}

Most of the notifications get notification data. This data is typecast by the CHttpFilter::HttpFilterProc() method to structures that conform to the data type sent with that notification (see Listing 15.7).

Listing 15.5 ISAPI.CPP-HttpFilterProc()Calls Notification Handler Method

DWORD CHttpFilter::HttpFilterProc(PHTTP_FILTER_CONTEXT pfc,

DWORD dwNotificationType, LPVOID pvNotification)

{

DWORD dwRet = SF_STATUS_REQ_NEXT_NOTIFICATION;

CHttpFilterContext callCtxt(pfc);

switch (dwNotificationType)

{

case SF_NOTIFY_READ_RAW_DATA:

dwRet = OnReadRawData(&callCtxt, (PHTTP_FILTER_RAW_DATA) pvNotification);

break;

case SF_NOTIFY_PREPROC_HEADERS:

dwRet = OnPreprocHeaders(&callCtxt,

(PHTTP_FILTER_PREPROC_HEADERS) pvNotification);

break;

case SF_NOTIFY_AUTHENTICATION:

dwRet = OnAuthentication(&callCtxt,

(PHTTP_FILTER_AUTHENT) pvNotification);

break;

case SF_NOTIFY_URL_MAP:

dwRet = OnUrlMap(&callCtxt, (PHTTP_FILTER_URL_MAP) pvNotification);

break;

case SF_NOTIFY_SEND_RAW_DATA:

dwRet = OnSendRawData(&callCtxt, (PHTTP_FILTER_RAW_DATA) pvNotification);

break;

case SF_NOTIFY_LOG:

dwRet = OnLog(&callCtxt, (PHTTP_FILTER_LOG) pvNotification);

break;

case SF_NOTIFY_END_OF_NET_SESSION:

dwRet = OnEndOfNetSession(&callCtxt);

break;

default:

ISAPITRACE1("Warning: unrecognized HTTP filter notification code %d\n", dwNotificationType);

break;

}

return dwRet;

}

Notification Handler Methods

There are seven notification handler methods. As mentioned earlier, you should normally use only the ones you need. In this example, we use all the methods to see what each does.

Table 15.2 shows how each notification type is mapped to a notification handler method.

Table 15.2 Notification Handler Methods

Notification

Handler Method

SF_NOTIFY_PREPROC_HEADERS

OnPreprocHeaders()

SF_NOTIFY_AUTHENTICATION

OnAuthentication()

SF_NOTIFY_URL_MAP

OnUrlMap()

SF_NOTIFY_SEND_RAW_DATA

OnSendRawData()

SF_NOTIFY_LOG

OnLog()

SF_NOTIFY_READ_RAW_DATA

OnReadRawData()

SF_NOTIFY_END_OF_NET_SESSION

OnEndOfNetSession()

CExampleFilter

CExampleFilter supplies dummy implementations of the notification handler methods. As in other C++ programs, before you can use a class, it must be declared. The class declaration for CExampleFilter is shown in Listing 15.8.

Listing 15.6 EXAMPLE.H-CExampleFilter Class Definition

// EXAMPLE.H - Header file for your Internet Server

// Example Filter

#include "resource.h"

class CExampleFilter : public CHttpFilter

{

public:

CExampleFilter();

~CExampleFilter();

// Overrides

// ClassWizard generated virtual function overrides

// NOTE - the ClassWizard will add and remove member functions here.

// DO NOT EDIT what you see in these blocks of generated code !

//{{AFX_VIRTUAL(CExampleFilter)

public:

virtual BOOL GetFilterVersion(PHTTP_FILTER_VERSION pVer);

virtual DWORD OnPreprocHeaders(CHttpFilterContext* pCtxt, PHTTP_FILTER_PREPROC_HEADERS pHeaderInfo);

virtual DWORD OnAuthentication(CHttpFilterContext* pCtxt, PHTTP_FILTER_AUTHENT pAuthent);

virtual DWORD OnUrlMap(CHttpFilterContext* pCtxt, PHTTP_FILTER_URL_MAP pMapInfo);

virtual DWORD OnSendRawData(CHttpFilterContext* pCtxt, PHTTP_FILTER_RAW_DATA pRawData);

virtual DWORD OnReadRawData(CHttpFilterContext* pCtxt, PHTTP_FILTER_RAW_DATA pRawData);

virtual DWORD OnEndOfNetSession(CHttpFilterContext* pCtxt);

virtual DWORD OnLog(CHttpFilterContext* pCtxt, PHTTP_FILTER_LOG pLog);

//}}AFX_VIRTUAL

//{{AFX_MSG(CExampleFilter)

//}}AFX_MSG

};

Once CExampleFilter is declared, we use it in the EXAMPLE.CPP file (see Listing 15.9). This file is where you add the code to respond to individual events as they occur. You use these functions to do the filter's work.

Listing 15.7 EXAMPLE.CPP-CExampleFilter Implementation

// EXAMPLE.CPP - Implementation file for your Internet Server

// Example Filter

#include "stdafx.h"

#include "Example.h"

///////////////////////////////////////////////////////////////////////

// The one and only CExampleFilter object

CExampleFilter theFilter;

///////////////////////////////////////////////////////////////////////

// CExampleFilter implementation

CExampleFilter::CExampleFilter()

{

}

CExampleFilter::~CExampleFilter()

{

}

BOOL CExampleFilter::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_ORDER_LOW | SF_NOTIFY_SECURE_PORT | SF_NOTIFY_NONSECURE_PORT

| SF_NOTIFY_LOG | SF_NOTIFY_AUTHENTICATION | SF_NOTIFY_PREPROC_HEADERS | SF_NOTIFY_READ_RAW_DATA | SF_NOTIFY_SEND_RAW_DATA | SF_NOTIFY_URL_MAP | SF_NOTIFY_END_OF_NET_SESSION;

// 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 CExampleFilter::OnPreprocHeaders(CHttpFilterContext* pCtxt,

PHTTP_FILTER_PREPROC_HEADERS pHeaderInfo)

{

// TODO: React to this notification accordingly and

// return the appropriate status code

return SF_STATUS_REQ_NEXT_NOTIFICATION;

}

DWORD CExampleFilter::OnAuthentication(CHttpFilterContext* pCtxt,

PHTTP_FILTER_AUTHENT pAuthent)

{

// TODO: React to this notification accordingly and

// return the appropriate status code

return SF_STATUS_REQ_NEXT_NOTIFICATION;

}

DWORD CExampleFilter::OnUrlMap(CHttpFilterContext* pCtxt,

PHTTP_FILTER_URL_MAP pMapInfo)

{

// TODO: React to this notification accordingly and

// return the appropriate status code

return SF_STATUS_REQ_NEXT_NOTIFICATION;

}

DWORD CExampleFilter::OnSendRawData(CHttpFilterContext* pCtxt,

PHTTP_FILTER_RAW_DATA pRawData)

{

// TODO: React to this notification accordingly and

// return the appropriate status code

return SF_STATUS_REQ_NEXT_NOTIFICATION;

}

DWORD CExampleFilter::OnReadRawData(CHttpFilterContext* pCtxt,

PHTTP_FILTER_RAW_DATA pRawData)

{

// TODO: React to this notification accordingly and

// return the appropriate status code

return SF_STATUS_REQ_NEXT_NOTIFICATION;

}

DWORD CExampleFilter::OnLog(CHttpFilterContext* pCtxt,

PHTTP_FILTER_LOG pLog)

{

// TODO: React to this notification accordingly and

// return the appropriate status code

return SF_STATUS_REQ_NEXT_NOTIFICATION;

}

DWORD CExampleFilter::OnEndOfNetSession(CHttpFilterContext* pCtxt)

{

// TODO: React to this notification accordingly and

// return the appropriate status code

return SF_STATUS_REQ_NEXT_NOTIFICATION;

}

// Do not edit the following lines, which are needed by ClassWizard.

#if 0

BEGIN_MESSAGE_MAP(CExampleFilter, CHttpFilter)

//{{AFX_MSG_MAP(CExampleFilter)

//}}AFX_MSG_MAP

END_MESSAGE_MAP()

#endif // 0

CHttpFilterContext

The CHttpFilterContext class wraps the HTTP_FILTER_CONTEXT structure and provides methods that deal with the data in that structure. Table 15.3 shows how the standard ISAPI calls are mapped to your filter's notification handler methods. As mentioned earlier, the MFC wrapper functions are named the same way as the ISAPI functions.

Table 15.3 CHttpFilterContext Methods Are Wrappers for ISAPI Functions

API Function

Wrapper Method

GetServerVariable()

CHttpFilterContext::GetServerVariable()

AddResponseHeaders

()CHttpFilterContext::AddResponseHeaders()

WriteClient()

CHttpFilterContext::WriteClient()

AllocMem()

CHttpFilterContext:AllocMem()

ServerSupportFunction()

CHttpFilterContext::ServerSupportFunction()

Add Logging to EXAMPLE

In this section, you'll add logging to the MFC ISAPI filter that you've already created. You can use this filter to do your own research into what each notification is doing.

In EXAMPLE, we declare private member functions to build the output string for each of the notification handler methods. OnReadRawData() and OnSendRawData() share the same string-building function, as shown in Listing 15.10.

Listing 15.8 EXAMPLE.H-CExampleFilter Adds Private Data and Methods for Logging.

// EXAMPLE.H - Header file for your Internet Server

// Example Filter

#include "resource.h"

class CExampleFilter : public CHttpFilter

{

public:

CExampleFilter();

~CExampleFilter();

// Overrides

// ClassWizard generated virtual function overrides

// NOTE - the ClassWizard will add and remove member functions here.

// DO NOT EDIT what you see in these blocks of generated code !

//{{AFX_VIRTUAL(CExampleFilter)

public:

virtual BOOL GetFilterVersion(PHTTP_FILTER_VERSION pVer);

virtual DWORD OnPreprocHeaders(CHttpFilterContext* pCtxt, PHTTP_FILTER_PREPROC_HEADERS pHeaderInfo);

virtual DWORD OnAuthentication(CHttpFilterContext* pCtxt, PHTTP_FILTER_AUTHENT pAuthent);

virtual DWORD OnUrlMap(CHttpFilterContext* pCtxt, PHTTP_FILTER_URL_MAP pMapInfo);

virtual DWORD OnSendRawData(CHttpFilterContext* pCtxt, PHTTP_FILTER_RAW_DATA pRawData);

virtual DWORD OnReadRawData(CHttpFilterContext* pCtxt, PHTTP_FILTER_RAW_DATA pRawData);

virtual DWORD OnEndOfNetSession(CHttpFilterContext* pCtxt);

virtual DWORD OnLog(CHttpFilterContext* pCtxt, PHTTP_FILTER_LOG pLog);

//}}AFX_VIRTUAL

//{{AFX_MSG(CExampleFilter)

//}}AFX_MSG

private:

CString BuildString(LPCTSTR pszFunctionName, PHTTP_FILTER_PREPROC_HEADERS pHeaderInfo);

CString BuildString(LPCTSTR pszFunctionName, PHTTP_FILTER_AUTHENT pAuthent);

CString BuildString(LPCTSTR pszFunctionName, PHTTP_FILTER_URL_MAP pMapInfo);

CString BuildString(LPCTSTR pszFunctionName, PHTTP_FILTER_RAW_DATA pRawData);

CString BuildString(LPCTSTR pszFunctionName, PHTTP_FILTER_LOG pLog);

CString BuildString(LPCTSTR pszFunctionName);

CCriticalSection m_criticalSection;

};

Each of the notification handler methods is changed to log the data it gets. OnAuthentication() is shown in Listing 15.11 with its string-building function. See the companion CD to this book for the source code for logging events.

Listing 15.9 EXAMPLE.CPP-OnAuthentication()Changed to Log Notification

DWORD CExampleFilter::OnAuthentication(CHttpFilterContext* pCtxt,

PHTTP_FILTER_AUTHENT pAuthent)

{

try

{

CString csOutput = BuildString("OnAuthentication", pAuthent);

CSingleLock lock(&m_criticalSection, TRUE);

CFile cfLog(csLogFilename, FILE_MODES);

cfLog.Seek(0, CFile::end);

cfLog.Write(csOutput, csOutput.GetLength());

cfLog.Close();

lock.Unlock();

}

catch(CException* e)

{

ISAPIASSERT(FALSE);

e->Delete();

}

catch(...) // catch structured (hardware) exceptions

{

ISAPIASSERT(FALSE);

}

return SF_STATUS_REQ_NEXT_NOTIFICATION;

}

CString CExampleFilter::BuildString(LPCTSTR pszFunctionName, PHTTP_FILTER_AUTHENT pAuthent)

{

CString csVal("BEGIN LOG ENTRY for ");

csVal += pszFunctionName;

csVal += "\r\n";

// Add the username

csVal += "Username: ";

csVal += pAuthent->pszUser;

csVal += "\r\n";

// Add the password

csVal += "Password: ";

csVal += pAuthent->pszPassword;

csVal += "\r\n";

csVal += "END LOG ENTRY for ";

csVal += pszFunctionName;

csVal += "\r\n\r\n\r\n";

return csVal;

}

A global CString is used to hold the log file name. This is valid only because the value doesn't change. The CString variable could have been declared as a member of the CExampleFilter class, again only because its value doesn't change.

The CCriticalSection and CSingleLock classes are used to force operations on the log file to wait until any existing operation completes. In this case, the CCriticalSection object is declared as a member variable of the CExampleFilter class.

In each of the methods that work on the log file, a CSingleLock object is declared and used to lock the CCriticalSection object.

The data in an HTTP_FILTER_RAW_DATA structure is not necessarily null-terminated, so the filter has to handle this when logging the raw data, as shown in Listing 15.12.

Listing 15.10 EXAMPLE.CPP-BuildString()Handles HTPP_FILTER_RAW_DATA

CString CExampleFilter::BuildString(LPCTSTR pszFunctionName, PHTTP_FILTER_RAW_DATA pRawData)

{

CString csVal;

char* pszBuffer = NULL;

do

{

// Start the string

csVal = "BEGIN LOG ENTRY for ";

csVal += pszFunctionName;

csVal += "\r\n";

// Add NULL termination to the raw data

if(pRawData->cbInData < pRawData->cbInBuffer)

{

// There's room in the buffer for the NULL terminator

((char*)pRawData->pvInData)[pRawData->cbInData] = 0x0;

pszBuffer = (char*)pRawData->pvInData;

}

else

{

// There's no room in the buffer for the NULL terminator

pszBuffer = (char*)malloc(pRawData->cbInData + 1);

if(! pszBuffer) { ISAPIASSERT(FALSE); break; }

memcpy(pszBuffer, pRawData->pvInData, pRawData->cbInData);

pszBuffer[pRawData->cbInData] = 0x0;

}

// Add the raw data to the string

csVal += "pvInData = ";

csVal += (char*)pszBuffer;

csVal += "\r\n";

// Finish the string

csVal += "END LOG ENTRY for ";

csVal += pszFunctionName;

csVal += "\r\n\r\n\r\n";

} while(0);

if(pszBuffer && (pszBuffer != pRawData->pvInData))

{

free(pszBuffer);

}

return csVal;

}

Avoiding Problems

If you are coming into ISAPI development from a traditional MFC background, some important differences may not be obvious. We look at these next.

Storing State Data in the Class

Your filter class cannot store state data as member data because the class handles multiple notifications concurrently. A good rule of thumb is to use member data only if it is initialized in the constructor and never changed while the service is running.

Using Classes That Are Not Thread-Safe

For the same reason that you can't store member data, you can't use classes that are not thread-safe without gaining access to them yourself. CString and CFile are examples of MFC classes that can be used in a filter because they are thread-safe.

The data access object (DAO) class is not thread-safe. It is not suitable for use in ISAPI unless it is protected from concurrent access through a mechanism such as critical sections of code.

Dealing with an Unknown Client

If you're using a filter for a Web server, you don't know what the client application will be. Over time, your filter will probably act on requests from all the major client browsers.

Each of these browsers may send different information or they may send the same information differently. They are constrained only by the HTTP specification.

The HTTP specification is vague because its version differs from browser to browser. In addition, the major browsers use their own nonstandard extensions.

Spend some time looking at what your filter gets from each of the major browsers to understand how these differences affect your filter. Even different versions of the same browser can change what your filter gets with a notification.

Protecting File I/O

Even though the CFile class is thread-safe, you need to code any file input/output (I/O) not to allow concurrent writes to the same file. One way to do this is by using the CFile::shareExclusive mode.

But this mode by itself causes the second open to fail. If you put your file I/O inside critical sections of code, though, the thread waits until it gains access to the critical section.

If you use the critical section appropriately, it guarantees the success of the open operation because no other opens are pending. This is the method used in EXAMPLE.

Handling Exceptions

An exception is an error that occurs during the execution of a program. C++ provides built-in support for exception handling through its try and catch statements. Generally, sections you want to monitor for exceptions are in a try block:

try

{

// some section of code

}

If an exception takes place, it is caught in the try block's corresponding catch handler:

catch(Cexception* pEX)

{

// do something

}

This is an orderly and efficient way to deal with errors. Although a detailed discussion of exception handling is beyond the scope of this chapter, let's take a moment for this important topic.

MSVC has supported C++ exceptions since version 2.0. C++ handling is now the preferred method for exceptions in both standard C++ and MFC programs. The short example we discussed earlier was a C++ exception handler. For a more thorough investigation, see Microsoft Visual C++ Books Online.

In Win32, another method for exceptions is structured exception handling (SEH). SEH enables developers to catch exceptions generated by the operating system. SEH has two types of handlers, Exception Handlers and Termination Handlers. The names of these handlers represent what they and when you should use them.

Since MFC 1.0, Microsoft has provided macros to help developers deal with exceptions. These macros are still included with MSVC 4.2 but primarily for backward compatibility. Although leaving the MFC macros in legacy code should not cause any harm, for new MFC programs Microsoft recommends using C++ exception handling.

ASSERTing

MFC programmers should be familiar with the ASSERT and TRACE macros. These macros help developers find bugs in their programs. For ISAPI developers, Microsoft introduced ISAPIASSERT and ISAPITRACE.

ISAPIASSERT and ISAPITRACE call ASSERT and TRACE if MFC is being used. But you can also use these two new macros in non-MFC ISAPI applications. In a program's debug build, you can use ASSERT or ISAPIASSERT to evaluate an expression for a particular situation.

For example, in the following expression, ASSERT ensures that "i" is not equal to "0" before the program tries division:

ASSERT(i != 0);

z = y/i;

When you're testing the debug build, if "i" equals "0" right before the program tries division, the program stops executing and a message appears with the line number of the ASSERT macro.

TRACE and ISAPITRACE are a way to send messages to a dump device. You can use this to track the values of variables as the program executes. Each of these macros only functions in debug builds. For more information, see Microsoft Visual C++ Books Online.

Handling Multiple Notifications

What you might expect to be a single notification often results in multiple notifications. For example, mapping a URL may trigger more than one SF_NOTIFY_URL_MAP notification as the URL is deciphered.

Notifications are detailed in Chapter 14, "Creating an ISAPI Filter," and Chapter 15, "Extending Your Web Server with Filters." The EXAMPLE filter with logging should help you understand when notifications occur.

From Here...

In this chapter, we discuss the MFC ISAPI filter classes and how they do the work of an ISAPI filter. With this understanding, you can use what you learn in other chapters for your MFC ISAPI filter.


© 1996, QUE Corporation, an imprint of Macmillan Publishing USA, a Simon and Schuster Company.