Mike Volodarsky's blog

Formerly the core server PM for IIS 7.0 and ASP.NET, now I run LeanSentry.
UPDATES: New troubleshooting guide released! Fix IIS website hangs →

IIS7 modules vs. IIS6 ISAPI: Managing request state in your module

For post #4 in the IIS7 Modules vs. IIS6 ISAPI series, let's take a look at another common pattern in IIS module development - storing request-specific state inside your module.

When developing a module that participates in request processing, it is often necessary to store request-specific state that the module computes in one stage of the pipeline, and then uses again in a later stage.  For example, if you are writing a module that calcuates the duration of the request, you will need to capture the initial timestamp at the beginning of the pipeline, and then compare it with the timestamp at the end of request processing in order to determine how long the request took.

At a high level, in order to do this, the module needs to allocate some memory to store the state, and associate it with the particular request.  In subsequent request pipeline stages, it needs to look it up, and eventually, deallocate the memory used to store it when the request is finished.

If you just want to see how this is done with IIS7 module APIs, jump straight to the IIS7 part.

Let's first see what is involved when using IIS6 ISAPI filters.

In the ISAPI filter world, each request notification is delivered to the ISAPI DLL via the static HttpFilterProc entrypoint function, passing along the notification id and the filter context structure: 

DWORD WINAPI HttpFilterProc(
  PHTTP_FILTER_CONTEXT pfc,
  DWORD NotificationType,
  LPVOID pvNotification
);

Then, depending on the notification, your HttpFilterProc implementation will provide the required request processing.  In our case, what we are interested in is to store some information, such as our timestamp, during an early notification such as SF_NOTIFY_PREPROC_HEADERS, and then access it later in a late notification such as SF_NOTIFY_END_OF_REQUEST

First, some basics - because your ISAPI filter will be notified separately for the different notifications, you cannot store the state in a local variable inside a function.  Also, since the ISAPI filter will be responsible for processing many requests concurrently, you cannot simply store the state in a static variable inside your DLL.  Instead, you must either:

  1. Create your own table indexed by the request being processed, and store/lookup state in that table, providing the proper locking to insure thread safety. (DONT DO THIS [:)])
  2. Use the server-provided mechanism to associate your own state "context" object with the request, and use it to keep your state between notifications.  This context is automatically associated with the current request, and provided to your filter during each notification inside the HTTP_FILTER_CONTEXT structure.

Our code would look something like this:

class CMyContext

{

public:

    CMyContext(clock_t time)

        : startTime( time )

    {

    }

       

    clock_t GetStartTime()

    {

        return startTime;

    }

       

private:

    clock_t startTime;

};

 

DWORD

WINAPI

HttpFilterProc(

   PHTTP_FILTER_CONTEXT pfc,

   DWORD notificationType,

   LPVOID pvNotification

)

{

    CMyContext*             pContext = NULL;

    clock_t                 time;

       

    switch(notificationType)

    {

    case SF_NOTIFY_PREPROC_HEADERS:

        time = clock();

       

        //

        //  Create and store our per-request context instance

        //

        pfc->pFilterContext = new CMyContext(time);

        break;

    case SF_NOTIFY_END_OF_REQUEST:

       

        //

        //  Get the context from the filter

        //

        pContext = (CMyContext *)pfc->pFilterContext;

        time = clock() - pContext->GetStartTime();

       

        //

        //  Clean up the context at the end of the request

        //

        delete pContext;

        pfc->pFilterContext = NULL;

       

        break;

    default:

        break;

    }

       

    return SF_STATUS_REQ_NEXT_NOTIFICATION;

}

 

Some notes about this code:

  1. I had to create my own context class, CMyContext, to store the request state data I need.
  2. I have to allocate the context for each request, and then make sure to clean it up at the end of the request to avoid memory leaks (alternatively, I could have used the the request memory pool that doesnt need deallocation by allocating the memory with the AllocMem filter support function).

Now, lets take a look at how IIS7 does it

In IIS7, the server will create a new instance of your module class for each request (see this post for more explanation of the IIS7 module class).  Because of this, storing request state is as simple as declaring member variables inside your module class.  You can set the values of these variables as you compute the state during one or more notifications, and use them directly from other notifications.

With this in mind, your IIS7 code looks like this:

class CMyModule : public CHttpModule

{

public:

    CMyModule()

        : startTime ( 0 )

    {

    }

       

    REQUEST_NOTIFICATION_STATUS

    OnBeginRequest(

        IN IHttpContext *                       pHttpContext,

        IN OUT IHttpEventProvider *             pProvider

    )

    {

        startTime = clock();

       

        return RQ_NOTIFICATION_CONTINUE;

    }

       

    REQUEST_NOTIFICATION_STATUS

    OnEndRequest(

        IN IHttpContext *                       pHttpContext,

        IN OUT IHttpEventProvider *             pProvider

    )

    {

        clock_t elapsed = clock() - startTime;

       

        return RQ_NOTIFICATION_CONTINUE;

    }

       

private:

    clock_t     startTime;

};


This eliminates the need to create a separate state class, and associate it with he request via an external mechanism.  You can declare and use all of the request state as type-safe member variables directly inside your module.

Because there is only one thread processing a single request at any given time, and there is a separate instance of your module for each request, you don't need to worry about thread safety (as long as you are using instance variables).

When the request is finished, your CHttpModule instance is automaticaly cleaned up by the server, so you dont need to worry about managing the lifetime of the state.  Just make sure to release whatever resources you are using inside your module's destructor like you normally would for any C++ class.

What about non-request state?

Sometimes, you will need to store state that is not associated with a particular request.  This is necessary whenever the lifetime of the state extends beyond the request, or is scoped to a particular server object like connection, application, or an arbitrary url - for example, in order to keep track of the number of requests to a particular site.  IIS7 provides a mechanism to do this by associating your own context (sorry, extra class required) with the object of choice via the IHttpModuleContextContainer inteface avaialble from many server objects. 

More on this in a future post ...  
 

IIS7 modules vs. IIS6 ISAPI: Memory Management

For post #3 in the IIS7 Modules vs. IIS6 ISAPI series, let’s look at one of the biggest trouble areas that bite ISAPI developers - managing memory.

If you develop software that uses Win32 APIs, you should be familiar with the feeling you get when you look at the MSDN documentation of a fairly simple function, and see that it returns a string or a set of bytes.

The problem, of course, is that you as a caller are responsible for allocating a buffer of a fixed size, and providing this buffer to the function so it can receive the data.

Sounds simple, right? Well, not really. Don’t forget that you also have the handle the case where the buffer is too small to receive the data, AND, you are responsible for cleaning up the buffer when you done.

Let’s take the ISAPI extension GetServerVariable function as an example - this function is frequently called by ISAPI extensions to obtain various request information, such as the normalized URL of the request:

BOOL WINAPI GetServerVariable( 
       HCONN hConn, 
       LPSTR lpszVariableName, 
       LPVOID lpvBuffer, 
       LPDWORD lpdwSizeofBuffer
);

Here is a simplified (!!!) code sample of a utility functions that uses GetServerVariable to obtain server variable values:

#define DEFAULT_BUFFER_SIZE   100

#define MAXIMUM_BUFFER_SIZE   512

 

HRESULT

GetServerVariableIIS6(

    EXTENSION_CONTROL_BLOCK *  pEcb,

    PSTR                       pszName,

    PSTR*                      ppszValue

)

{

    //

    //  Allocate a buffer of default size

    // 

    HRESULT hr        = S_OK;

    BOOL    fRet      = FALSE;

    DWORD   dwLength  = DEFAULT_BUFFER_SIZE;

    PSTR    pszBuffer = new CHAR[dwLength];

 

    if ( pszBuffer == NULL )

    {

        hr = HRESULT_FROM_WIN32( ERROR_NOT_ENOUGH_MEMORY );

        goto Finished;

    }

 

    //

    //  Try to get the server variable

    //

    fRet = pEcb->GetServerVariable( pEcb->ConnID,

                                    pszName,

                                    pszBuffer,

                                    &dwLength );

    //

    //  Check for insufficient size, but only allow up to

    //  a bounded size for the value

    //

    if ( !fRet &&

           GetLastError() == ERROR_INSUFFICIENT_BUFFER &&

           dwLength < MAXIMUM_BUFFER_SIZE )

    {

        //

        //  Re-allocate the buffer to the required size

        //

        delete [] pszBuffer;

        pszBuffer = new CHAR[dwLength];

        if ( pszBuffer == NULL )

        {

            hr = HRESULT_FROM_WIN32( ERROR_NOT_ENOUGH_MEMORY );

            goto Finished;

        }              

 

        //

        //  Try again

        //

        fRet = pEcb->GetServerVariable( pEcb->ConnID,

                                        pszName,

                                        pszBuffer,

                                        &dwLength );

    }

 

    //

    //  Check if we succeeded

    //

    if ( !fRet )

    {

        hr = HRESULT_FROM_WIN32( GetLastError() );

        goto Finished;

    }

 

    //

    //  Return the buffer

    //

    *ppszValue = pszBuffer;

    pszBuffer = NULL;

 

Finished:

 

    //

    //  Clean up the buffer if an error occurred

    //

    if ( pszBuffer != NULL )

    {

        delete [] pszBuffer;

        pszBuffer = NULL;

    }

 

    return hr;

}

So, let’s quickly recap what I had to do here:

  1. Allocate a buffer
  2. Call GetServerVariable to fill the buffer
  3. Check whether the value was too large, and if so, clean up the first buffer, and re-allocate the buffer to be the right size, but only if the size is not too big (never trust the client)
  4. Call ServerVariable again with the larger buffer
  5. If we failed to return the buffer to the client, clean up the buffer

Now, things are about to get a lot worse. You notice that I return the pointer to the allocated buffer from this function to the caller, making deallocating this buffer the responsibility of the caller. This is why I also don’t just allocate the initial buffer on the stack, like David does in his old sample for this code available here: http://blogs.msdn.com/david.wang/archive/2005/08/18/ISAPI-GetServerVariable.aspx.

This means, that the caller has to clean up the buffer when they are done with it. Imagine, that your code has to get a lot of server variables (as is the case a lot of the time).

You will pretty much also have to:

  1. Maintain a list of buffers you used to get server variables
  2. At the end of the request, go through that list and delete all the buffers

And you thought all you were going to do is get a few little server variables. Welcome to ISAPI hell.  

It turns out that managing buffers to operate ISAPI APIs a lot of the time becomes the main theme of your development. Sure, you’ll say, you’ve written this code so many times, you have a solid pattern and maybe even a library you’ve written to do it. And you probably spent quite a few hours debugging memory leaks and access violations doing it.

Enter IIS7.

In IIS7, we made one simple improvement - we took over the management of memory for server APIs that return buffers. Instead of you keeping track of buffers with server variable values, the IIS server core does it (hey, it has to do it anyway).

Many IIS7 APIs return pointers to already allocated core server memory that contains the values you are retrieving.

With IIS7, you don’t need a utility function, or a single new or delete statement to get a server variable. You simply to this:

IHttpContext*    pContext;

PCSTR        pszValue = NULL;

DWORD       dwLength = 0;

//

//  Get the server variable

//

hr = pContext->GetServerVariable( "AUTH_USER",

                                  &pszValue,

                                  &dwLength );

if ( FAILED( hr ) )

{

    goto Finished;

}

NOTE: The dwLength parameter is not an input parameter - it receives the length of the server variable value as a convenience to you, so that you don’t need to call strlen().

After successful completion, The pszValue points to a buffer that can be used for the duration of the request. After the request is finished, the server will automatically reclaim the memory so you never need to worry about cleaning it up.

BONUS - Unicode Support: The server provides an overload of this function that returns Unicode server variable values. This replaces the really painful way to get Unicode values with ISAPI - call the GetServerVariable with “UNICODE_variablename”, and treat the returned bytes as WCHAR even though the buffer is passed in as a CHAR buffer.

Overall, the IIS7 memory management model basically means that much of the time, you don’t need to:

  1. Allocate your own memory
  2. Check for insufficient buffer sizes
  3. Manage and clean up your own memory

This post got a little long, but really I cannot overstate how much grief this saves you when writing a module … letting you concentrate on the reason why you are actually writing it.

Until next time …