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;

  &n
bsp;    

    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 …  
 

8 Comments

  1. Anonymous

    These are all great comments and suggestions (and more are welcome). Thanks. I will follow up as soon as I can.

  2. Anonymous

    Regarding the open-source “rules”, have you considered releasing the models under a Creative Commons license?

    • Hey Aaron~I am glad that SSL Diagnostics helped fix your proeblm.The design team, comprised of myself and others at Microsoft, set out to help those who are new or veterans of IIS (and SSL) and this is good to see.If you have questions in the future, let us know .~Chris

  3. Anonymous

    What if I am processing some data in Filter and what pass it up to Module??
    Let’s say some kind of response filter.
    Gathering data in pContext, processing at SF_NOTIFY_END_OF_REQUEST notification and want to pass result up to Module

Leave a Reply

Your email address will not be published. Required fields are marked *