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.
You'll use the Extension Wizard to create an MFC ISAPI filter that serves as a starting point for exploring the capabilities of MFC ISAPI.
You'll explore MFC ISAPI in detail so you understand the relationship between the code you are writing and what you learn in other chapters.
You'll change your filter to log all events as they occur. This way, you can see what the filter does when the server processes a request from a client browser.
You'll learn about some common sources of trouble for MFC developers new to ISAPI.
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.
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.1Starting 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.2ISAPI 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.3Extension 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.
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.![]()
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.4ISAPI calls are processed by the exported DLL functions, which pass the calls to the MFC ISAPI object.
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 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
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() |
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;
}
If you are coming into ISAPI development from a traditional MFC background, some important differences may not be obvious. We look at these next.
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.
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.
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.
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.
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.
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.
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.
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.