Previous Page TOC Next Page



Chapter 17


ISAPI Filter Objects


ISAPI filter objects are extensions of the ISS Server. Filters add functionality to the server. In this way they are different from ISAPI server extensions that service requests from clients. ISAPI server filters affect all the web spaces of the server that the filter is on. Filters can be used to do security handling, log additional information, and modify output.

When the IIS is started, all the server filters referenced in the registry are loaded. When a request is made to the Server, filters that are designed to handle that specific request are called by the server. The following are different types of requests:

SF_NOTIFY_LOG is called when the Server is going to log in to the database.

Server filters can be used for advanced logging of server activity. filters can modify the incoming or outgoing data, including adding your own server HTML tags. Server filters can also serve as an advanced means of authenticating the user.

Advanced Logging


You can use Server filter for advanced logging. The example chosen for advanced logging logs the number of bytes being requested from the server. This example is helpful if you are an Internet Service Provider and want to track which customers are using the most bandwidth. The example goes beyond a simple hit counter. Instead of hits, the server filter logs the size of the page and the size of the graphics on that page. The logging is done to SQL server, enabling various reports to be generated on the data.

Before you begin coding the example, you need to build the server filter with Microsoft's Extension wizard and learn how to debug the server filter with Microsoft's Developer Studio.

Creating a Filter Using Microsoft's Extension Wizard


In the Microsoft Developer's Studio there is a project wizard for creating server extensions. This ISAPI Extension Wizard can be used to create a filter extension. Use these steps:

  1. 1. Open Microsoft's Developer Studio.
  2. 2. From the menu bar, click on File|New.
  3. 3. Select Project Workspace from the listbox and click OK.
  4. 4. Go to the bottom of the Type listbox and choose ISAPI Extension Wizard.
  5. 5. In the Location editbox, choose the default location c:\msdev\projects.
Note


With server extensions, you choose the script directory for the location of the server extension. This location enables you to run the DLL without copying it. However, server filters can be located anywhere and still run. A server filter does not need to be within the web space of the Web server.

  1. 7. In the Name edit box, type the name of the project; for this example, enter lst17_01.
  2. 8. Click Create.

Figure 17.1 is the first dialog box of the ISAPI Extension Wizard.

Figure 17.1. The Selection dialog box of the ISAPI Extension Wizard, step 1.

  1. 9. Deselect the Generate a Server Extension object check box.
Note


Leaving MFC as a shared DLL increases the loading speed of your DLL. It also helps shared resources if more than one HTTP server extension or HTTP server filter is being run at the same time. Remember to copy mfc40.dll and msvcrt40.dll to your production server when you copy your release build of HTTP server filter.

  1. 11. Click Next.

The following figure is the second dialog in the ISAPI Extension Wizard.

Figure 17.2. The Selection dialog box of the ISAPI Extension Wizard, step 2.

  1. 12. For this example, leave the filter set to low priority. You also should select both secured and nonsecured ports.
  2. 13. You need to choose the types of notification your filter will process. Because you are going to log the size of the data being sent, choose Outgoing raw data and headers.
Note


You can choose more then one type, if you are going to do more then one type of processing. You also can make several DLLs, one for each type of processing. You benefit from putting all the filtering for your Web site in one DLL. The benefits include one process space, faster loading, and sharing of common procedures. The examples in this chapter, however, are divided—one filter per DLL. This filters them easier to read and understand.

  1. 14. Click Finish and OK.

Here is the code that should have been produced by the ISAPI Extension Wizard:

The Source for lst17_1.cpp:


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

//    lst17_01 Filter

#include <afx.h>

#include <afxwin.h>

#include <afxisapi.h>

#include "resource.h"

#include "lst17_01.h"

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

// The one and only CLst17_01Filter object

CLst17_01Filter theFilter;

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

// CLst17_01Filter implementation

CLst17_01Filter::CLst17_01Filter()

{

}

CLst17_01Filter::~CLst17_01Filter()

{

}

BOOL CLst17_01Filter::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_SEND_RAW_DATA | 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 CLst17_01Filter::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 CLst17_01Filter::OnEndOfNetSession(CHttpFilterContext* pCtxt)

{

// TODO: React to this notification accordingly and

// return the appropriate status code

return SF_STATUS_REQ_NEXT_NOTIFICATION;

}

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

// If your extension will not use MFC, you'll need this code to make

// sure the extension objects can find the resource handle for the

// module.  If you convert your extension to not be dependent on MFC,

// remove the comments arounn the following AfxGetResourceHandle()

// and DllMain() functions, as well as the g_hInstance global.

/****

static HINSTANCE g_hInstance;

HINSTANCE AFXISAPI AfxGetResourceHandle()

{

return g_hInstance;

}

BOOL WINAPI DllMain(HINSTANCE hInst, ULONG ulReason,

LPVOID lpReserved)

{

if (ulReason == DLL_PROCESS_ATTACH)

{

g_hInstance = hInst;

}

return TRUE;

}

****/

The Header for Lst17_1.h:


#include <sqlext.h>

class CLst17_01Filter : public CHttpFilter

{

public:

CLst17_01Filter();

~CLst17_01Filter();

BOOL GetFilterVersion(PHTTP_FILTER_VERSION pVer);

DWORD OnSendRawData(CHttpFilterContext* pCtxt,

PHTTP_FILTER_RAW_DATA pRawData);

DWORD OnEndOfNetSession(CHttpFilterContext* pCtxt);

// TODO: Add your own overrides here

};

After you've created the DLL, compile it. Because the Extension Wizard creates nothing but the shell of the server filter, it will have no functionality.

All examples in this chapter start this way.

Debugging the HTTP Server Filter


The best way to debug the server filter is to run the filter through Microsoft Developer Studio. To do this, you need to be on the same machine as the one on which IIS is currently running. Make sure that this server is being used only by you; no other Web pages should be served from it. It is advisable also to run another copy of the Microsoft Developer Studio to make changes to and recompile your server filter. Use your browser on the same machine to call the Server.

Correct Permissions


Remember that the operating system believes that there are two users in the debugging scenarios. The first user, you, is debugging the IIS with the Microsoft Developer Studio. The second user, also you, is coming in from the network using a browser and calling the ISS Server. The first user has your login name and has to be an administrator. The second user is IUSR_MACHINENAME where MACHINENAME is the name of your machine. The second user gets set up by the IIS when it installs. This name can be changed in the Internet Service Manager; IUSR_MACHINENAME is the default.

In the debugging scenario, the second user permissions are adequate. However, special permissions are needed for the first user to run the IIS under Microsoft Developer Studio. If you are not in the administrator group on this machine, have an administrator make you one. Once you are an administrator, you need to give yourself a few more permissions. Use these steps:

  1. 1. Make sure that you are logged on with administrator permissions.
  2. 2. Choose Administrative Tools|User Manager for Domains.
  3. 3. Make sure that User Manager is administrating the Machine that you are on.
  4. 4. Select Policies|User Rights from the menu bar.
  5. 5. Click on the check box in the bottom-right corner, which reads Show Advance User Rights.
  6. 6. From the Right Select box, choose Act as part of the operating system.
  7. 7. Click Add, and add yourself.
  8. 8. Click OK.
  9. 9. From The Right Select Box, choose act as part of Generate security audits.
  10. 10. Click Add, and add yourself.
  11. 11. Click OK.
  12. 12. Click OK to exit User Rights.
  13. 13. Exit User Manager.
  14. 15. Log off and log back on.

No NT Security is set correctly so that you may run a service, like IIS, in the Debugger of Microsoft Developer Studio.

Disabling the IIS


To run the IIS on Microsoft Developer Studio, you need to stop running the default IIS. You can do this by going into the Internet Service Manager and stopping the server. You might also want to disable the server from restarting if you reboot the machine. Disabling the service allows you to come back to a development environment, instead of having to remember to stop the service every time you reboot. To do this, follow these steps:

  1. 1. Go into Control Panel|Choose Services.
  2. 2. Choose World Wide Web Publishing Service from the Services List.
  3. 3. Click Startup.
  4. 4. In the Startup dialog box, choose Manual, and click OK.
  5. 5. Click Close.
  6. 6. Exit the Control Panel.

Running the IIS in Microsoft Developer Studio


Now that you have adequate permissions, and have disabled the default IIS, you can run the IIS in Microsoft Developer Studio:

  1. 1. Start another copy of Microsoft Developer Studio (besides the one open for compiling the server filter).
  2. 2. From the menu bar, choose File|Open Workspace.
  3. 3. From the Open Workspace dialog box's File of Type list, select Executable files (*.exe).
  4. 4. Open the IIS executable c:\inetsrv\server\inetinfo.exe.
  5. 5. From the menu bar of Microsoft Developer Studio, choose Build|Choose Settings.
  6. 6. Click on the debug tab.
  7. 7. Select General from the Category drop-down box.
  8. 8. In the Program argument's edit box, type -e W3Svc.
  9. 9. Select Additional DLLs from the Category drop-down box.
  10. 10. In the Modules Box, choose the debug HTTP Server lst17_01.dll. It should be at c:\msdev\projects\lst17_01\debug\lst17_01.dll.
  11. 11. Click OK and exit the Settings dialog box.
  12. 12. From the menu bar, choose File|Save All.
  13. 13. When asked Do you want to save project workspace information for C:\inetsrv\server\inetinfo.mdp, choose Yes. Then when you want to run the server filter in the Microsoft Developer Studio, you can just open C:\inetsrv\server\inetinfo.mdp, rather than executing all the previous steps.
  14. 14. From the menu bar, choose Build|Execute inetinfo.exe.
  15. 15. Every time you execute inetinfo.exe, you will be prompted with a Dialog that says the following:
  1. Press Yes.

IIS is now running in the debugger of IIS. You can debug the server extension like any other DLL.

Configuring the IIS for Server Filter


For each server filter created, you need to tell the IIS that there is another filter to use. You do this using the following steps to modifying the register:

  1. 1. Open the registry editor.
  2. 2. Choose HKEY_LOCAL_MACHINE.
  3. 3. Then select the SYSTEM\CurrentControlSet\Services\W3SVC\Parameters registry key.
  4. 4. There is a value within Parameters called Filter DLLs. Add the newly-created DLL to the list of DLLs, making sure to separate each DLL by a comma. The default DLL, c:\inetsrv\Server\sspifilt.dll, should already be in the list. Adding Listing 17.1 should make the list look like this:
Note


Adding the DLL to the registry is separate from adding the DLL in Microsoft's Developer Studio. Adding the DLL to the registry tells the IIS to load the DLL and use it as a filter. Adding the DLL to the Additional DLL list in Microsoft's Developer Studio tells the debugger to load the symbols for debugging.


Filter Priority


More than one filter can be run for any particular notification. Each filter is loaded and runs in priority order. When the IIS starts, it reads the registry and loads the DLLs that are listed in the Filter DLLs key. For each DLL, it calls the GetFilterVersion() method, which returns the priority of the filter. This example uses the default priority, which is low. If there were more then one filter being used for a particular notification, the two DLLs should have different priorities to indicate which should run first and which should run second. If two DLLs have the same priority for the same notification, the Server executes the filters based on the Filter DLLs key in the registry. The first one in the list runs first.

Each filter has the capability of telling the server that it is the last filter that needs to be called or enabling the Server to execute the rest of the filters with that particular notification. If the filter wants to have the server stop executing filters that have its notification, it returns SF_STATUS_REQ_HANDLED_NOTIFICATION. If the filter wants the other filter "below" it (with lesser priority or the same priority but behind it in the list) to be called, the filter returns SF_STATUS_REQ_NEXT_NOTIFICATION.

Tip


If you keep all filtering in a single Server Filter, you do not need to deal with priorities or sequential executing filters.


Code for Advanced Logging


Now that you have created a lst17_01.dll with the ISAPI Extension Wizard, configured Microsoft's Developer Studio for debugging, and added the DLL to the registry so that IIS knows to load the filter, you are ready to program the Server Filter. The goal is to log the number of outgoing bytes for each page request to SQL Server.

Using ODBC


One of the greatest advantages of using a Filter Extension is that once it's loaded, it remains loaded in the cache. You can make a connection to an ODBC data source when the server extension is loaded and break the connection when the DLL is unloaded. This type of connection can be thought of as a constantly open pipe from the server extension to the data source. This offers a huge performance advantage in querying data from a database because the most costly thing in making a small query to an ODBC data source is opening the connection. If the connection is already open when the request for information is made, the data comes back much faster.

Before exploring the code for this example, you need to create a database in SQL Server and a table called TrafficLog. Once you have created a database in SQL Server, run the following code in ISQL_w and the table will be created for you:


CREATE TABLE dbo.TrafficLog (

TrafficLog_Id int IDENTITY NOT NULL PRIMARY KEY,

TrafficLog_Size int NULL DEFAULT 0,

TrafficLog_URL varchar (255) NULL ,

TrafficLog_Date datetime NULL DEFAULT GetDate()

)

ISQL_w is an application that comes with SQL Server.

After you create the table, create a data source entry in ODBC to point to the database; make the data source name TrafficLog so that it matches the example. If you don't know how to create an ODBC data source, refer to the section entitled Creating a Data Source in Chapter 18, "_______________."

Add the methods below to the CLst17_01Filter class, they are used to connecting to ODBC:


void CLst17_01Filter::Connect()

{

RETCODE rc;

rc=SQLAllocEnv(&m_henv);

if ((rc != SQL_SUCCESS) && (rc != SQL_SUCCESS_WITH_INFO))

ODBCError(SQL_NULL_HSTMT);

rc=SQLAllocConnect(m_henv, &m_hdbc);

if ((rc != SQL_SUCCESS) && (rc != SQL_SUCCESS_WITH_INFO))

ODBCError(SQL_NULL_HSTMT);

rc=SQLConnect(m_hdbc, (UCHAR FAR *)(LPCTSTR) m_csDSN,

SQL_NTS, (UCHAR FAR *)(LPCTSTR) m_csUser , m_csUser.GetLength(),

(UCHAR FAR *)(LPCTSTR) m_csPassword, m_csPassword.GetLength());

if ((rc != SQL_SUCCESS) && (rc != SQL_SUCCESS_WITH_INFO))

ODBCError(SQL_NULL_HSTMT);

};

The Connect() method and the Disconnect() method are only called once, when the DLL is loading and unloading. They are used to connect and disconnect from the database. They are called from the destructor and the constructor of the CLst17_01Filter class, shown in Listing 17.1.

Note


With caching turned off for debugging, Connect() and Disconnect() will be called for every thread because the DLL is loading and unloading with each thread. You get the performance advantage only when the DLL is putting in production and the caching is turned on.


void CLst17_01Filter::Disconnect()

{

SQLDisconnect(m_hdbc);

SQLFreeConnect(m_hdbc);

SQLFreeEnv(m_henv);

}

void CLst17_01Filter::ODBCError (HSTMT hstmt)

{

UCHAR FAR szSqlState[5];

UCHAR FAR szErrorMsg[80];

SWORD cbErrorMsgMax=80;

SWORD FAR *pcbErrorMsg= new SWORD;

SDWORD FAR *pfNativeError= new SDWORD;

RETCODE rc;

rc=SQLError(m_henv, m_hdbc, hstmt, szSqlState,

pfNativeError, szErrorMsg, cbErrorMsgMax, pcbErrorMsg);

if (!_tcscmp((char*)szSqlState,"08S01"))

{

// Bad Connection Let's try to recover!

ISAPITRACE("Bad Connection\n");

EnterCriticalSection(m_lpcsFlag);

Disconnect();

Connect();

LeaveCriticalSection(m_lpcsFlag);

}

ISAPITRACE1("SQL Error: %s\n",szErrorMsg);

delete pfNativeError;

delete pcbErrorMsg;

};

HSTMT CLst17_01Filter::GetStatement()

{

// The only reason that we are entering a critical section here

// is because the connection could be bad and we are trying to reconnect

// otherwise this isn't needed since ODBC is thread proof.

EnterCriticalSection(m_lpcsFlag);

HSTMT hstmt;

RETCODE rc;

rc=SQLAllocStmt(m_hdbc, &hstmt);

if ((rc != SQL_SUCCESS) && (rc != SQL_SUCCESS_WITH_INFO))

{

// Bad Connection Let's try to recover!

// The CriticalSection Prevents other threads

// From trying to get a statement with a bad connection

ISAPITRACE("Bad Connection\n");

EnterCriticalSection(m_lpcsFlag);

Disconnect();

Connect();

LeaveCriticalSection(m_lpcsFlag);

//Try Again

rc=SQLAllocStmt(m_hdbc, &hstmt);

if ((rc != SQL_SUCCESS) && (rc != SQL_SUCCESS_WITH_INFO))

{

// Only try to reconnect once

// Otherwise it is beyond our control

return(NULL);

}

}

LeaveCriticalSection(m_lpcsFlag);

return(hstmt);

}

void CLst17_01Filter::FreeStatement(HSTMT hstmt)

{

SQLFreeStmt(hstmt, SQL_DROP);

};

The GetStatement() and FreeStatement() methods get a separate ODBC statement for each thread. All threads use the same connection, enabling the ODBC driver and SQL Server to handle the workload.

Tip


With only one open connection per filter, you need only one SQL Server license, because the licenses are issued by the number of connections.

You also need to modify the CLst17_01Filter class constructor and destructor use the code below:


CLst17_01Filter::CLst17_01Filter()

{

m_lpcsFlag=new CRITICAL_SECTION;

InitializeCriticalSection(m_lpcsFlag);

// Hard Coded Datasource, login, and password

m_csDSN="TrafficLog";

m_csUser="sa";

m_csPassword="";

Connect();

}

CLst17_01Filter::~CLst17_01Filter()

{

Disconnect();

DeleteCriticalSection(m_lpcsFlag);

delete m_lpcsFlag;

}

Make sure you declare the methods in the class definition and include the needed SQL Server variables:


class CLst17_01Filter : public CHttpFilter

{

protected:

HENV m_henv;

HDBC m_hdbc;

LPCRITICAL_SECTION m_lpcsFlag;

Cstring m_csDSN;

Cstring m_csUser;

Cstring m_csPassword;

HSTMT GetStatement();

void FreeStatement(HSTMT hstmt);

void ODBCError (HSTMT hstmt);

void Disconnect();

void Connect();

public:

CLst17_01Filter();

~CLst17_01Filter();

BOOL GetFilterVersion(PHTTP_FILTER_VERSION pVer);

DWORD OnSendRawData(CHttpFilterContext* pCtxt,

PHTTP_FILTER_RAW_DATA pRawData);

DWORD OnEndOfNetSession(CHttpFilterContext* pCtxt);

// TODO: Add your own overrides here

};

To link correctly you will have to add odbc32.lib to the Object|Library Modules edit box under Build|Settings|Link so that the ODBC calls can link.

Logging to SQL Server


To log to SQL Server, you need to modify SendRawData() which created by the ISAPI Extension Wizard:


DWORD CLst17_01Filter::OnSendRawData(CHttpFilterContext* pCtxt,

PHTTP_FILTER_RAW_DATA pRawData)

{

DWORD cbInData=pRawData->cbInData;

LPTSTR lpszURL;

if (lpszURL=GetServerVariable(pCtxt,"URL"))

{

Log(lpszURL,cbInData);

delete lpszURL;

}

// TODO: React to this notification accordingly and

// return the appropriate status code

return SF_STATUS_REQ_NEXT_NOTIFICATION;

}

Notice that the method GetServerVariable() is called on the variable "URL" so that you know which request is responsible for the data. The method Log() is called to do the actual logging of the data. GetServerVariable looks like this:


// The Caller must delete the memory, unless error in which case returns NULL

LPTSTR CLst17_01Filter::GetServerVariable(CHttpFilterContext* pCtxt,

 LPCTSTR pszVariableName)

{

LPVOID lpvBuffer=NULL;

DWORD dwSize=0;

pCtxt->GetServerVariable((LPTSTR)pszVariableName,NULL,(LPDWORD)&dwSize);

// Check to see if variable exists

if(dwSize==0)

return(NULL);

lpvBuffer=(LPVOID)new TCHAR[dwSize+1];

if (!(pCtxt->GetServerVariable((LPTSTR)pszVariableName,

lpvBuffer,(LPDWORD)&dwSize)))

{

delete lpvBuffer;

return(NULL);

}

if(dwSize==0)

{

delete lpvBuffer;

return(NULL);

}

return((LPTSTR)lpvBuffer);

};

The Log() method writes a row to the SQL Server every time it gets called. The Log() method looks like this:


void CLst17_01Filter::Log(LPCTSTR lpszURL, DWORD dwSize)

{

HSTMT hstmt;

RETCODE rc;

CString SQLScript;

if((hstmt=GetStatement())==NULL)

{

return;

}

SQLScript.Format(

"INSERT TrafficLog (TrafficLog_Size,TrafficLog_URL) VALUES (%d,'%s')",

dwSize,lpszURL);

rc=SQLExecDirect(hstmt, (UCHAR FAR *) (LPCTSTR) SQLScript, SQL_NTS);

if ((rc != SQL_SUCCESS) && (rc != SQL_SUCCESS_WITH_INFO))

{

ODBCError(hstmt);

goto End;

}

End:

FreeStatement(hstmt);

return;

}

Reviewing the Log


To review the log, open ISQL_w. Make sure the correct database is selected, and then run the following query:


SELECT SUM (TrafficLog_Size) BYTES, TrafficLog_URL

FROM TrafficLog

GROUP BY TrafficLog_URL

As an exercise, you can create a dynamic Web page that displays TrafficLog. You might want to wait until Chapter 18 to implement the page with the Internet Database Connector.

Using Filters to Change the Returning Data


Create a filter, using the ISAPI Extension Wizard in Microsoft Developer Studio, as you did for the preceding example. Call the new filter lst17_02. You still want to override the OnSendRawData() method. This time you look at the outgoing buffer and modify it. When your code sees the string segment <DATE>, you replace it with today's date. Then you can write regular HTML pages that have today's date in them. To do this, you need to add these two methods to CLst17_02Filter, shown in Listing 17.2.


LPTSTR CLst17_02Filter::Replace(LPTSTR lpszString,

LPTSTR lpszTarget, LPTSTR lpszReplacement)

{

LPTSTR lpszIndex;

LPTSTR lpszSegment;

LPTSTR lpszSegmentIndex;

LPTSTR lpszData;

DWORD dwIndexLength;

DWORD dwDataLength;

dwDataLength=_tcslen(lpszString)+_tcslen(lpszReplacement)+1;

lpszData=new TCHAR[dwDataLength];

_tcscpy(lpszData,lpszString);

while (lpszIndex=_tcsstr(lpszData,lpszTarget))

{

dwIndexLength=_tcslen(lpszIndex)+1;

lpszSegment=new TCHAR[dwIndexLength];

_tcscpy(lpszSegment,lpszIndex);

_tcscpy(lpszIndex,lpszReplacement);

lpszSegmentIndex=lpszSegment+_tcslen(lpszTarget);

_tcscat(lpszIndex,lpszSegmentIndex);

delete lpszSegment;

}

// If Needed Allocate some heap

if (_tcslen(lpszString)<_tcslen(lpszData))

{

delete lpszString;

lpszString = new TCHAR[_tcslen(lpszData)];

}

// Padd the end with spaces so that the

// Content Length stays the same

while (_tcslen(lpszString)>_tcslen(lpszData))

{

_tcscat(lpszData," ");

}

// Copy to Output

_tcscpy(lpszString,lpszData);

delete lpszData;

return lpszString;

}

DWORD CLst17_02Filter::OnSendRawData(CHttpFilterContext* pCtxt,

PHTTP_FILTER_RAW_DATA pRawData)

{

LPTSTR lpszData;

DWORD cbBufferSize=pRawData->cbInBuffer;

DWORD cbSize=pRawData->cbInData;

DWORD cbReplacementSize;

// This part creates the Date String

CTime tCurrent = CTime::GetCurrentTime();

LPTSTR lpszTime;

lpszTime = new TCHAR[6];

sprintf(lpszTime,_T("%d-%d"),tCurrent.GetMonth(),tCurrent.GetDay());

lpszData=new TCHAR[cbSize+1];

memcpy(lpszData,pRawData->pvInData,cbSize);

lpszData[cbSize]='\0';

lpszData=Replace(lpszData,_T("<DATE>"),(LPTSTR) lpszTime);

cbReplacementSize=_tcslen(lpszData);

// If the tag is smaller then the data that

// is replacing it them don't change the

// data.

if (cbReplacementSize <= (int)cbBufferSize)

{

_tcscpy((LPTSTR)pRawData->pvInData,lpszData);

}

delete lpszData;

delete lpszTime;

// TODO: React to this notification accordingly and

// return the appropriate status code

return SF_STATUS_REQ_NEXT_NOTIFICATION;

}

Remember to add the filter to the Filter DLLs key in the registry and to the additional DLLs in Microsoft Developer Studio. You are now ready to debug the code using Microsoft Developer Studio running the IIS.

This simple piece of HTML demonstrates the filter:


<HTML>

<BODY>

Current Date is: <DATE>

</BODY>

</HTML>
Note


The IIS allocates the space needed for the data being sent out. If you want to replace a piece of data with another one, you need to make sure that you have enough room. In other words, you must work within the limits of the buffer allocated by the IIS. The best way to do this is to create tags that are bigger then the data that you want to replace. For instance, the <DATE> tag has more characters in it than the biggest date, '12-31'.


Authentication


Server Filters can also be used for authentication of users who enter your Web space. Besides using the IIS authentication process, you can build your own by using Server Filters. Let's begin by creating a Server Filter using Microsoft Developer Studio's ISAPI Extension Wizard. Create it like the preceding example, but instead of choosing Outgoing raw data and headers, choose Client authentication request. Name the new filter lst17_03.

A Bug in Microsoft Developer Studio


When the ISAPI Extension Wizard creates the method shell for OnAuthentication, it looks like this:


DWORD OnAuthentication(CHttpFilterContext* pCtxt,


PHTTP_FILTER_PREPROC_HEADERS pFiltInfo);

This isn't correct, however, and doesn't work. The method definition should look like this:


virtual DWORD OnAuthentication( CHttpFilterContext* pfc,


PHTTP_FILTER_AUTHENT pAuthent );

You need to change this in both the class declaration and the method header in the example lst17_03.dll. This problem will be fixed in Microsoft Developer's Studio version 4.2.

Debugging Authentication


If you are debugging according to the instruction earlier in this chapter, you will notice that the IIS doesn't call OnAuthentication() every time you refresh the page. It calls OnAuthentication() only the first time in the session that you request a page. Even requesting a different page in the same Web space doesn't call OnAuthentication(). To call OnAuthentication() to test it, close the browser after the first authentication and reopen it to test again. This is true only for a successful request, however; if an error is returned by the OnAuthentication() process, it will be called again if the page is refreshed. Be careful of these circumstances when testing your Server Filter.

Authenticating on an IP Address


Listing 17.3, lst17_03.dll, checks the IP Address of the user making the request. If the IP address starts with 157.56, the user is authenticated; otherwise, the user is not. This type of authentication can be used to filter out people outside of a particular IP range. Add GetServerVariable() as a method. This is the second time that you have seen it. It was used in the first listing to retrieve the Server variables passed in from the client. You use GetServerVariable() in Listing 17.3 to get the IP address of the user.

Listing 17.3.


// The Caller must delete the memory, unless error in which case returns NULL

LPTSTR CLst17_03Filter::GetServerVariable(CHttpFilterContext* pCtxt,

LPCTSTR pszVariableName)

{

LPVOID lpvBuffer=NULL;

DWORD dwSize=0;

pCtxt->GetServerVariable((LPTSTR)pszVariableName,NULL,(LPDWORD)&dwSize);

// Check to see if variable exists

if(dwSize==0)

return(NULL);

lpvBuffer=(LPVOID)new TCHAR[dwSize+1];

if (!(pCtxt->GetServerVariable((LPTSTR)pszVariableName,

lpvBuffer,(LPDWORD)&dwSize)))

{

delete lpvBuffer;

return(NULL);

}

if(dwSize==0)

{

delete lpvBuffer;

return(NULL);

}

return((LPTSTR)lpvBuffer);

};

You also need to modify OnAuthentication(). Notice the different return value and the header returned if a user is not authorized:


DWORD CLst17_03Filter::OnAuthentication(CHttpFilterContext* pCtxt,

PHTTP_FILTER_AUTHENT pHeaderInfo)

{

BOOL bNotAuthorized=TRUE;

LPTSTR lpszUser;

LPTSTR lpszIndex;

if(lpszUser=GetServerVariable(pCtxt,"REMOTE_ADDR"))

{

lpszIndex=_tcsstr(lpszUser,_T("."))+1;

lpszIndex=_tcsstr(lpszIndex,_T("."));

(*lpszIndex)=_T('\0');

bNotAuthorized=_tcscmp(lpszUser,"157.56");

delete lpszUser;

}

if (bNotAuthorized)

{

DWORD dwDataSize=_tcslen(_T("401 Unauthorized"));

pCtxt->ServerSupportFunction(  SF_REQ_SEND_RESPONSE_HEADER,

_T("401 Unauthorized"), NULL, &dwDataSize);

return SF_STATUS_REQ_FINISHED;

}

else

{

return SF_STATUS_REQ_NEXT_NOTIFICATION;

}

}

To test, remember that you need to add lst17_03.dll to the registry in the Filter DLLs key, and you also need to add lst17_03.dll to the additional DLLs list in Microsoft Developer Studio.

Summary


Filters enable advanced functionality to be added to the IIS Server. They enable the administrator of the Server to intercept a server request and reply in a way specific to the server.

Previous Page Page Top TOC Next Page