Chapter 13

Understanding ISAPI Filters


In this chapter:

ISAPI filters are a powerful way to extend your Web server. Because of the trend toward more complex Web sites, developers are asked to invent uses for the Web far beyond its original design.

You can use ISAPI filters to change or enhance the default processing of a hypertext transport protocol (HTTP) request. Unlike ISAPI extensions, which you must specify in the requesting URL, ISAPI filters are executed by IIS in response to any request from a client.

Let's take a look at what filters are and how they can help you develop Web solutions.

How ISAPI Filters Work

This section introduces the basics of what makes an ISAPI filter work. First, we'll explore the necessary entries for an ISAPI filter. Then look at some of the things you can do with filters.

DLL Gets Control on Every Request

The first thing to understand about an ISAPI filter is that it is a 32-bit DLL loaded by IIS at server startup. IIS calls filter DLLs at predefined times during the processing of an HTTP request.

At each one of these points, the filter can either add to or change the default processing done by IIS. This is what gives ISAPI filters their power. Imagine that you are on-site at Microsoft and can extend the Web server in any way you want.

Let's begin by breaking up the processing of a filter into two phases: first registration and then event processing. The phases are shown in Table 13.1.

Table 13.1 Phases in Filter Processing

Phase

What happens

Registration

IIS loads the filter and calls the GetFilterVersion() entry.

Event processing

IIS calls the filter's HttpFilterProc() entry as it processes HTTP requests.

Each ISAPI filter has to have two entries defined: GetFilterVersion()and HttpFilterProc(). Let's take a look at these entries.

The GetFilterVersion() entry

When the server starts up, it checks a special registry entry to find out which DLLs it should load as filters. As each DLL is loaded, the special-purpose GetFilterVersion() entry is called. GetFilterVersion() does the registration phase of the filter. It serves the following purposes.

Table 13.2 The Four Filter Priorities

Priority

What it means

SF_NOTIFY_ORDER_HIGH

Will load the filter at a HIGH priority.

SF_NOTIFY_ORDER_MEDIUM

Will load the filter at a MEDIUM priority.

SF_NOTIFY_ORDER_DEFAULT

Will load the filter at the DEFAULT priority. This is recommended.

SF_NOTIFY_ORDER_LOW

Will load the filter at a LOW priority.

If two filters register to process the same event at the same priority level, IIS calls them in the order that they are listed in the registry. We'll explain more about registry entries later.


Only specify a higher priority when you know that your filter will process a majority of the events it is notified about. An example is a filter that logs all requests of a particular server. A high-priority filter that ignores most events can add significant overhead.

Table 13.3 Notification Events and What They Mean

Notification

What it means

SF_NOTIFY_SECURE_PORT

The server calls the filter about sessions over a secure port only.

SF_NOTIFY_NONSECURE_PORT

The server calls the filter about sessions over a nonsecure port only.

SF_NOTIFY_READ_RAW_DATA

The server calls the filter before it process the incoming raw data.

SF_NOTIFY_PREPROC_HEADERS

The server calls the filter before it has preprocessed the headers coming from the client.

SF_NOTIFY_AUTHENTICATION

The server calls the filter when it is about to authenticate the client.

SF_NOTIFY_URL_MAP

The server calls the filter when it is about to map a logical URL to a physical path.

SF_NOTIFY_SEND_RAW_DATA

The server calls the filter when it is about to send raw data back to the client.

SF_NOTIFY_LOG

The server calls the filter when it is about to write data to the server log.

SF_NOTIFY_END_OF_NET_SESSION

The server calls the filter when it is about to end the session with the client. This gives the filter a chance to clean up any data specific to this client session.

SF_NOTIFY_ACCESS_DENIED

The server calls the filter any time it is about to return a 401 Access Denied response. With this notification, a filter can return a custom message.

Even though the filter registers to get notifications for a particular event, it doesn't have to process every occurrence of that event. As we'll see shortly, you can limit the processing of the occurrences of an event in the filter's HttpFilterProc().


GetFilterVersion() is declared as follows. You must define it exactly as it appears here so that IIS can call it.

BOOL WINAPI GetFilterVersion( PHTTP_FILTER_VERSION pVer );

IIS passes only one parameter to GetFilterVersion(), a pointer to an HTTP_FILTER_VERSION structure. The structure is defined in Listing 13.1.

Listing 13.1 HTTPFILT.H-Definition of HTTP_FILTER_VERSION

typedef struct _HTTP_FILTER_VERSION {

DWORD dwServerFilterVersion;

DWORD dwFilterVersion;

CHAR lpszFilterDesc[SF_MAX_FILTER_DESC_LEN+1];

DWORD dwFlags;

} HTTP_FILTER_VERSION;

When the call to GetFilterVersion() returns, IIS uses the information that the filter put in the HTTP_FILTER_VERSION structure to determine when to call the filter. Table 13.4 shows which members of this structure are meaningful to the filter and how to populate them:

Table 13.4 Meaningful HTTP_FILTER_VERSION Members

Member

What it means

dwFilterVersion

Gives the version of the filter.

lpszFilterDesc

Supplies a readable text description of the filter. Set this to something meaningful, as it may be used in administrative tools.

dwFlags

Sets the value of all of the notification events that your filter is to handle OR'ed together.

Listing 13.2 shows a simple use of GetFilterVersion() that completes the steps to register a filter DLL with IIS.

Listing 13.2 Steps to Register GetFilterVersion

BOOL WINAPI GetFilterVersion( PHTTP_FILTER_VERSION pVer )

{

// Set the version of your filter.

pVer->dwFilterVersion = HTTP_FILTER_REVISION;

// Set the description of your filter.

lstrcpy( pVer->lpszFilterDesc, "Your Filter Description Here!" );

// Set the notifications to handle. This filter will handle only authentication events

// over the standard HTTP nonsecure port.

pVer->dwFlags = SF_NOTIFY_AUTHENTICATION | SF_NOTIFY_NONSECURE_PORT;

return TRUE;

}

The HttpFilterProc() Entry

Once the filter is loaded and registered, it is ready to get notifications. This is done when the server calls the HttpFilterProc() entry in the filter. Once again, you must declare this entry exactly as IIS expects it. This is shown in Listing 13.3.

Listing 13.3 Declaring HttpFilterProc

DWORD WINAPI HttpFilterProc( PHTTP_FILTER_CONTEXT pfc, DWORD

notificationType, LPVOID pvNotification );

The first parameter, pfc, is a pointer to a HTTP_FILTER_CONTEXT structure. This structure holds information about the HTTP request itself. See Appendix A, "ISAPI Reference," for the definition of this structure.

The second parameter, notificationType, is a DWORD that represents one of the notification events the filter registered with the server. Table 13.3 shows the possible values for this parameter.

The third parameter, pvNotification, is a void pointer to a server-supplied data area. This data area is the vehicle by which IIS shares the data to be processed with the filter. It is different for each notification type.

Table 13.5 summarizes the data type of this parameter for each notification type.

Table 13.5 pvNotification: Events and Data Types

When the notification type is

pvNotification points to a structure of this type

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

The declaration of HttpFilterProc() specifies a return type of DWORD. As you may have guessed, HttpFilterProc() is allowed to return a predefined set of values. IIS uses these values to determine how to continue processing the event.

Table 13.6 shows these possible values and when they should be used.

Table 13.6 Return Codes from the HttpFilterProc

Return code

When to use it

SF_STATUS_REQ_FINISHED

Use this return code if the filter has handled the HTTP request. This tells the server to disconnect the session.

SF_STATUS_REQ_FINISHED_KEEP_CONN

Use this return code if the filter has handled the HTTP request and you want the server to keep the TCP session open if the option was negotiated.

SF_STATUS_REQ_NEXT_NOTIFICATION

Use this return code if you want the server to call the next filter in the notification chain.

SF_STATUS_REQ_HANDLED_NOTIFICATION

Use this return code if the filter has handled the notification and no other filters should be called for this notification.

SF_STATUS_REQ_ERROR

Use this return code to tell the server that an error occurred. The server will call GetLastError() and indicate the error to the client.

SF_STATUS_REQ_READ_NEXT

Use this return code for raw-read notification only. It is used when the filter is an opaque stream filter and the session parameters are being negotiated.

Filter Operations

Now that you have a basic understanding of ISAPI filters, you probably can't wait to use one! Well, before you do that, let's take a look at some of the useful things that filters can do.

Monitor What Happens

An excellent use of ISAPI filters is advanced logging. IIS gives you some powerful logging options. But two of the things that IIS doesn't log are the user agent (HTTP_USER_AGENT) and the referring page (HTTP_REFERRER).

Does that mean that if you need this information you're out of luck? Absolutely not. A simple filter like the one described in the next section does the trick.

Let's start by defining the GetFilterVersion() entry. Listing 13.4 shows a sample use of this entry.

Listing 13.4 Sample Use of GetFilterVersion()

BOOL WINAPI GetFilterVersion( PHTTP_FILTER_VERSION pVer )

{

// Set this filter's version.

pVer->dwFilterVersion = HSE_FILTER_VERSION;

// Set this filter's description.

strcpy( pVer->lpszFilterDesc, "Advanced logging filter. " );

// Set the notifications to handle. This filter will handle

only log events

// over the standard HTTP nonsecure port.

pVer->dwFlags = SF_NOTIFY_ORDER_HIGH | SF_NOTIFY_LOG;

return TRUE;

}

The sample code in this section is for illustration only. For a complete use of advanced logging and other filters, see Chapter 16, "Extending Your Web Server with Filters."


That was pretty easy, wasn't it? Now you just need to define HttpFilterProc(). This allows the filter to process notifications. Listing 13.5 shows a sample use of HttpFilterProc().

Listing 13.5 LST13_6.CPP-Sample Use of HttpFilterProc

DWORD WINAPI HttpFilterProc( PHTTP_FILTER_CONTEXT pfc, DWORD

notificationType, LPVOID pvNotification )

{

switch(notificationType) {

case SF_NOTIFY_LOG:

// Cast the void pointer to the proper datatype for

this notification.

PHTTP_FILTER_LOG logData =

(PHTTP_FILTER_LOG)pvNotification;

// Perform specific processing on the log data

here...

//

// Even though we handled the notification, we

want to tell IIS

to

// allow normal processing of this notification.

return SF_STATUS_REQ_NEXT_NOTIFICATION;

break;

};

return SF_STATUS_REQ_NEXT_NOTIFICATION;

}

Remember that the pvNotification parameter is passed as an LPVOID because it varies based on the type of the current notification. This allows the same function to be called for all notifications. And it's why we must cast the pointer to the specific data type for a given event, as shown in the following line of code:

PHTTP_FILTER_LOG logData = (PHTTP_FILTER_LOG)pvNotification;

HttpFilterProc() is the entry IIS calls for every notification specified by the filter. Therefore, you must use a switch statement or some form of conditional logic to determine the current notification event.

The previous example shows a filter that monitors some server function. Next, we'll look at a filter that changes the server's interaction with the client.

Customize Authentication

A good example of the power of ISAPI filters is a custom authentication filter. Imagine that your Web site needs a secure area to protect sensitive documents. You must have a user name and password combination to access this area.

Since IIS security is integrated with Windows NT, one option in granting select users access to this area is to set up a Windows NT user account for each user. This is a tedious process, especially if the potential user base is large.

Another option is to build an ISAPI filter to intercept the SF_NOTIFY_AUTHENTICATION event. When the filter is called, it can easily determine the URL requested. If it is a protected URL, the filter can access some external data source (such as a database or flat file) to validate the user name and password.

See Chapter 16, "Extending Your Web Server with Filters," for a sample use of a custom authentication filter.

Balance Requests Among Multiple Web Servers

Yet another use of ISAPI filters is balancing requests among multiple Web servers. Imagine a setup where you have one server acting as the "load balancer" and any number of machines serving the pages.

You make the URL of the load balancer available to the public. The load balancer has an ISAPI filter that monitors the load on the other three servers. When it gets a request, it uses a round-robin to redirect the requests evenly among the other three servers.

If you want to get really sophisticated, you could put an ISAPI filter on each server to communicate with the load balancer via an interprocess communication (IPC), giving it an exact count of how many connections it is processing. This would allow the load balancer to redirect requests to the servers with the least amount of traffic.

These examples should give you a feel for what you can accomplish with ISAPI filters. Let's look at how HTTP requests flow through IIS and how ISAPI filters fit into this flow.

Loaded at Startup, Kept in Memory Till Shutdown

ISAPI filters are loaded by IIS when it starts up and stay in memory until IIS is shut down. This makes calling them efficient. But if the code in HttpFilterProc() is not efficient, the performance of the server is affected.

See "Filter Rules" later in this chapter for more information about how IIS loads ISAPI filters.

Filters must also be thread-safe. IIS maintains a pool of threads that it gives requests to process. The filter can be processing many requests on many different threads at any given time. Thread safety is discussed later in this chapter under "Filter Rules."

Flow of a Filter

This section explains what happens when you add filters to your Web server. To understand the flow of a filter, let's first take a look at the steps that IIS takes when it gets a client request.

Information Flow

A server processes information from a client in seven steps:

  1. Processes the incoming HTTP headers.
  2. Processes the incoming HTTP data.
  3. Maps the URL to a physical file.
  4. Authenticates the user if needed.
  5. Builds the HTTP headers and data to be sent back to client.
  6. Writes log data to the log file.
  7. Ends the connection.

For a graphical representation of this flow, see Figure 13.1.


Fig. 13.1

Flow of a request through IIS.

You can see that the IIS scenario works fine as long as you don't want to change the default processing of these HTTP requests through IIS. An ISAPI filter, however, allows us to change the default processing of an HTTP request.

Figure 13.2 is a graphical representation of the IIS flow with filters added.

Fig. 13.2

Flow of a request with filters.

As you can see, IIS makes many calls to the registered filters as it is processing each HTTP request. This allows any ISAPI filter to change the default processing of a request.

Each call is different in that it gives the filter a chance to process a different event during the processing of the request. Each filter can choose to process the event or to ignore it. If the filter ignores it, IIS processes the event.

Sample Registration and Information Flow

What if you need advanced logging or custom authentication? You might first try to do this with either an ISAPI extension or a CGI application. But for either choice to be effective, each request for a page from your site would have to come through either your extension or your CGI application-not feasible.

ISAPI filters are a much cleaner solution. Take a look at a sample interaction between IIS and a couple of ISAPI filters that do these functions.

Interaction During Filter Registration

As we have already seen, the first phase in the processing of a filter is registration. The following list shows the interaction between IIS and two filters during the registration phase.

  1. IIS loads the DLL for filter 1 and calls GetFilterVersion().
  2. Filter 1 sets its version, indicates that it wants to be notified when IIS is about to authenticate a client (SF_NOTIFY_AUTHENTICATION) and that it has a default priority (SF_NOTIFY_ORDER_DEFAULT).
  3. IIS loads the DLL for filter 2 and calls GetFilterVersion().
  4. Filter 2 sets its version, indicates that it wants to be notified when IIS is about to write data to the server log (SF_NOTIFY_LOG) and when it is about to authenticate a client (SF_NOTIFY_AUTHENTICATION). It also specifies that it has default priority (SF_NOTIFY_ORDER_DEFAULT).

Registration is now complete.

Interaction During Event Processing

Now imagine that a client requests a document from a secure area of our Web site. This happens to be the area that filter 2 is protecting.

Using the seven steps under "Information Flow" earlier in this chapter, let's see what the interaction between IIS and these two filters would look like. Remember, the filters are registered for authentication and logging, steps 4 and 6 of the flow.

  1. IIS gets the request. It checks if any filters are to handle the incoming raw HTTP data and headers (SF_NOTIFY_READ_RAW_DATA). In this scenario, no filters are to handle the data and headers. IIS handles them.
  2. Next, IIS checks to see if any filters are to do a task before it processes the headers (SF_NOTIFY_PREPROC_HEADERS). In this scenario, no filters are to do a task. IIS processes the headers.
  3. Now IIS must map the URL to a physical path (SF_NOTIFY_MAP_URL). Before it does this, it checks if any filters are to map the path. In this scenario, no filters are to map the path. IIS maps the path.

Since neither of the filters registered for the first three steps of the information flow, the server handled them alone. Now we get to the first event that the filters registered for, step 4 under "Information Flow" earlier in this chapter.

  1. The server sees that the document is in a secure area. It checks if any filters are to authenticate the client (SF_NOTIFY_AUTHENTICATION). It sees that both filter 1 and filter 2 registered to do this.
  2. IIS calls HttpFilterProc() in filter 1.
  3. Filter 1 looks at the path of the document and determines that it is not protecting this document. It tells IIS to pass the notification on to other filters.
  4. IIS calls HttpFilterProc() in filter 2.
  5. Filter 2 looks at the path of the document and determines that it is protecting this document. It does the processing to check if the user name and password are valid.
  6. In this scenario, the user name and password are valid. The filter tells IIS that it handled the request (SF_STATUS_REQ_HANDLED_NOTIFICATION).
  7. IIS sees that it doesn't have to pass this notification down to any other filters because filter 2 handled it. So it checks if the authentication passed.

In this scenario, the user id and password combination pass the test supplied by filter 2 in the authentication, step 4 under "Information Flow" earlier in this chapter. IIS is ready to process the raw data.

  1. IIS prepares to send the data back to the client. Before it does this, it checks if any filters are to handle this (SF_NOTIFY_SEND_RAW_DATA).
  2. In this scenario, no filters are to handle this. IIS sends the data back to the client.

The server is at step 6 under "Information flow" earlier in this chapter, the logging event. Filter 2 registered for this event.

  1. IIS prepares the data that will be written to the server log. Before it does this, it checks if any filters are to handle this (SF_NOTIFY_LOG).
  2. IIS sees that filter 2 is to handle this. IIS calls the filter's HttpFilterProc() with the relevant data.
  3. Filter 2 takes some data, logs it for itself, and returns to IIS, indicating that IIS should keep passing this notification down (SF_STATUS_REQ_NEXT_NOTIFICATION). Although the filter processed the event, it doesn't stop the default processing. Instead, it tells IIS to continue processing the event.
  4. IIS sees that it should continue passing the notification to other filters. But in this scenario, there are no more filters. IIS writes the default log information.

IIS must end the connection with the client, the final step under "Information Flow" earlier in this chapter.

  1. IIS checks if any filters are to be notified of the end of the network session with the client (SF_NOTIFY_END_OF_NET_SESSION).
  2. In this scenario, no filters are to be notified. IIS ends the session.

This sample should give you a pretty clear picture of the interactions between IIS and ISAPI filters.

Information flow Inside a Filter

Now let's take a quick look at what happens inside a filter's HttpFilterProc() when a request comes in. The steps for the HttpFilterProc() in most filters are listed below.

  1. Determine the notification type (see Table 13.5).
  2. Decide whether to process this occurrence.
  3. If yes, process it.
  4. Decide whether IIS should pass it on or not.
  5. Return the proper status to IIS.

ISAPI Filter Rules

The previous sections describe what ISAPI filters are and how they work. Now we look into a few rules that each ISAPI filter must follow to work properly.

Must Be Registered Before IIS Starts

Because IIS loads all ISAPI filters at startup, you must know which DLLs to load. You can do this via the Windows NT registry. Unfortunately, at present there is no easier (or safer) way to do this. These are the steps to follow:

  1. Run the Windows Registry Editor: REGEDT32.EXE
  2. Edit the following key:

    HKEY_LOCAL_MACHINE\System\CurrentControlSet\Services\W3SVC

    \Parameters\Filter DLLs

  3. Add your filter's fully qualified path name to this key. Make sure you separate it from other filters with a comma.

Figure 13.3 shows what the registry screen should look like.


Fig. 13.3

A snapshot of the Windows NT registry.

Be sure to separate filter DLLs by commas, but only by commas. If you use any other character, such as semicolons, IIS ignores the entry and won't load your filter DLL. Also, be careful not to remove other entries that may already be there.

You should make editing the registry part of your filter's setup program, if it has one. If the filter doesn't have a setup program, you must do it manually.

Once you have changed the registry, you must stop IIS (if it is already running) and restart it. This allows the server to load the filter.

To see if IIS has a problem loading your filter or any other filter, look in the Event Viewer. IIS writes an event to the event log when it encounters a failure loading a filter DLL.

Must Be a 32-Bit DLL

Each ISAPI filter must be a 32-bit DLL. This is because IIS itself is a 32-bit program and it must call entry points in your DLL.

Must Expose Defined Entry Points

As we have already discussed in this chapter, each filter DLL must have the GetFilterVersion() and HttpFilterProc() entry points defined. Remember, this is the only way IIS can communicate with your DLL. If these entry points are not defined, IIS won't be able to load and use your filter DLL.

Must Be Thread-Safe

IIS makes extensive use of Windows NT's multithreading capabilities. It can respond to a many requests simultaneously. This means that all ISAPI filters must be thread-safe.

An in-depth discussion of threads is beyond the scope of this chapter. But Figure 13.4 shows how IIS processes more than one request simultaneously. The shaded area indicates periods of time that IIS is processing multiple requests simultaneously.


Fig. 13.4

Multiple requests processed simultaneously.

For more information about thread safety, see Chapter 18, "Making Your Extensions Thread-Safe."


Summary

In this chapter, we learn how ISAPI filters work and what you need to make them work smoothly. We review the priority levels and notifications used by filters.

We also look at some practical uses for ISAPI filters. See the following chapters for more on filter operations and notifications.

From Here...

The following chapters give you good examples for building and using ISAPI filters:


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