Writing a Native Module for IIS 8 using AppInit APIs

The new Application Initialization Feature in IIS 8 became possible through the introduction of some new APIs. These APIs combined with server notifications made a module like Application Initialization possible and they can be helpful in other applications too.

I will walk through developing a native C++ module for IIS 8 that listens to these notifications and uses the new APIs to add a new functionality to IIS Core.

This walkthrough will go over:

1)      Introducing some of the IIS pipeline notifications and APIs

2)      Developing a native C++ module.

3)      Deploying that module and testing it in action.

 

To be able to compile this module you will need to install the latest Windows Server 2012 SDKs.

Also, if you are not familiar with developing native modules for IIS, I would recommend reading "Develop a Native C\C++ Module for IIS 7.0" first.

 

The Native Module:

 

Any IIS native module must implements at least 2 classes and a RegisterModule functions

The first class is inheriting from CHttpModule, and this will be the module class itself. The second class is inheriting from IHttpModuleFactory and this will be the factory class for the module.

However, we will also implement a class that inherits from IGlobalModule as well. We will do that to demonstrate the use of a Global Module APIs (OnGlobalApplicationStart and OnGlobalApplicationPreload). Having a class that inherits from IGlobalModule allows us to add new functionality to the server itself instead of just adding another module in the request pipeline.

First we are going to implement the RegisterModule function.

HRESULT

RegisterModule(

    DWORD                           dwServerVersion,

    IHttpModuleRegistrationInfo *   pModuleInfo,

    IHttpServer *                   pGlobalInfo

)

{

    HRESULT                         hr            = S_OK;

    CMyAppInitModuleFactory *       pFactory      = NULL;

    CMyGlobalAppInitModule  *       pModule       = NULL;   

 

    //The passed IHttpServer object is important for other functions

    //So we will save it's pointer in our own global pointer

    _MyGlobalServerInfo = pGlobalInfo;

   

    //Now will create the module factory object

    pFactory = new CMyAppInitModuleFactory();

    if (pFactory == NULL)

    {

         hr = HRESULT_FROM_WIN32( ERROR_NOT_ENOUGH_MEMORY );

         goto Failed;

    }

   

    //Now we will register our module for the RQ_BEGIN_REQUEST notifications

    /*

    These notifications are made when serving the request starts. At this stage the

    request has entered the pipeline, and the worker process is the one processing it

    */

   

    hr = pModuleInfo->SetRequestNotifications( pFactory,

                                               RQ_BEGIN_REQUEST,

                                               0 );

    if ( FAILED( hr ) )

    {

        goto Failed;

    }

   

    //Now will create the Global module object

    pModule = new CGlobalAppInitModule;

    if ( pModule == NULL )

    {

        hr = HRESULT_FROM_WIN32(ERROR_NOT_ENOUGH_MEMORY);

        goto Failed;

    }

   

    //Now we will register our GlobalModule to listen to these Global Notifications

    /*

    These are the notifications that were introduced in IIS 7.5. However, what changes is when the IIS Core is actually firing these events.

    In IIS 7.5, IIS Core used to send these notifications after the worker process notified WAS which means after the processing of Global.asax begins.

    In most applications that is where most of the initialization time is. In IIS 8 these notifications are sent before notifying WAS

    We will listen to GL_APPLICATION_PRELOAD events

    */

   

    hr = pModuleInfo->SetGlobalNotifications( pModule,

                                              GL_APPLICATION_PRELOAD );

    if ( FALED( hr ) )

    {

        goto Failed;

    }

   

    return S_OK;

   

Failed:

    //We need to properly dispose of the created objects

    if ( pModule != NULL )

    {

        pModule->Terminate();

        pModule = NULL;

    }

   

    if ( pFactory != NULL )

    {

        pFactory->Terminate();

        pFactory = NULL;

    }

   

    return hr;

}

As you can see in the RegisterModule function we registered for 2 different types of notifications. The first is on request level (RQ_BEGIN_REQUEST) which means that our module will be called at the beginning of every request, and then there are the (GL_APPLICATION_START and GL_APPLICATION_PRELOAD) notifications. For these notifications the Global Module Class will be called once when the application starts or preloads.

 

Now let’s implement the Global Module class.

//we will inherit from CGlobalModule

class CMyGlobalAppInitModule : public CGlobalModule

{

  public:

 

    //we will implement these 2 new APIs

   

      //First

      virtual

      GLOBAL_NOTIFICATION_STATUS

      OnGlobalApplicationPreload(

        IN IGlobalApplicationPreloadProvider *  pProvider

        )

        {

            /*

            This function is called every time the application’s worker process loads. In here, for example we can check if this start is due to a process recycle or not.

            The worker process can either be starting for the first time or the result of a recycle, either way this function will be called.

            */

           

            //Lets check if this was the result of process recycle

           

            IHttpContext3 *                         pHttpContext3      = NULL;

            HRESULT                                 hr                 = S_OK;

            IGlobalApplicationPreloadProvider2 *    g_pPreloadProvider = NULL;

       

            IHttpContext *pHttpContext = NULL;

            hr = pProvider->CreateContext(&pHttpContext);

 

            if (FAILED(hr))

            {

                goto Finished;

            }

           

            // now we will get the new IGlobalApplicationPreloadProvider2

            // we will need the global Server Info we saved in the RegisterModule function

            hr = HttpGetExtendedInterface( _MyGlobalServerInfo,

                                           pProvider,

                                           &g_pPreloadProvider );

            if (FAILED(hr))

            {

                goto Finished;

            }

           

            //Now we can use the IGlobalApplicationPreloadProvider to check if the process was the result of a recycle

            if (g_pPreloadProvider->IsProcessRecycled())

            {

                //TODO: something specific to a recycled process

            }

            else

            {

                //TODO: something generic to an app pool process starting

            }

        Finished:

            if (pHttpContext != NULL)

            {

                pHttpContext->ReleaseClonedContext();

                pHttpContext = NULL;

            }

   

            return GL_NOTIFICATION_CONTINUE;

           

        }

       

        //Second

      virtual

      GLOBAL_NOTIFICATION_STATUS

      OnGlobalApplicationStart(

        IN IHttpApplicationStartProvider *    pProvider

        )

        {

            /*

            This is called once when the application starts, and not on recycle.

            */

            return GL_NOTIFICATION_CONTINUE;

        }

};

This was the CGlobalModule class. You should also implement a Terminate function to delete the class instances.

 

Finally the CHttpModule class which will handle the requests. In this class you can do anything you want with the individual requests. You can check for the Warmup server variable for example to know if this request is the fake request generated by the Warmup module, or you can do something totally different with the request. I will go through a very simple example of how to check for the Warmup server variable and inspect the child and parent requests based of that.

Then I will go over the IHttpApplication2 APIs to put the application in Warmup state and to exit from it.

 

class CMyAppInitModule : public CHttpModule

{

  public:

     

      CMyAppInitModule()

      {

      }

     

      virtual

      REQUEST_NOTIFICATION_STATUS

      OnBeginRequest(

        IN IHttpContext *              pHttpContext,

        IN IHttpEventProvider *        pProvider

        )

        {

            //  This will hold the HttpContext for the child request

            IHttpContext3 *                pHttpContext3  = NULL;

            IHttpApplication2 *            pApplication   = NULL;

            HRESULT                        hr             = S_OK;

           

            //Now we will get the request's "WARMUP_REQUEST" server variable

            DWORD valueLength = 200;

            PCWSTR warmupRequest[200];

            *warmupRequest = NULL;

            hr = pHttpContext->GetServerVariable( "WARMUP_REQUEST",

                                                  warmupRequest,

                                                  &valueLength );

                                                 

            // "WARMUP_REQUEST" server variable should be set for warming up requests.

            // For non-warming up requests, Error_Invalid_Index error should be returned.

            if ( hr == HRESULT_FROM_WIN32( ERROR_INVALID_INDEX ) )

            {

                //this is not a warmup request

                //just a normal request

            }

            else if  ( FAILED ( hr ) )

            {

                goto Finished;

            }

            else

            {

                //this is a warmup request

            }

           

           

            //Now we will use the pApplication to get an IHttpApplication2 object

            hr = HttpGetExtendedInterface( _MyGlobalServerInfo,

                                           pHttpContext->GetApplication(),

                                           &pApplication );

            if ( FAILED ( hr ) )

            {

                goto Finished;

            }

            /*

            It is easy to change the application state based on the requested URL or any other local server variable.

            This can really be helpful if you know that a certain page in the application is slow and want to get back

            to the user while the application works in the background

            */

           

            // Calling BeginApplicationWarmup() should put the current application in a Warmup state

            pApplication->BeginApplicationWarmup();

            if ( !pApplication->QueryIsWarmingUp() )

            {

                hr = S_FALSE;

                goto Finished;

            }

           

            // Similarly calling EndApplicationWarmup() should end that warmup state the application was in

            pApplication->EndApplicationWarmup();

            if ( pApplication->QueryIsWarmingUp() )

            {

                hr = S_FALSE;

                goto Finished;

            }

        Finished:

            //if we faced an error, we will set the return status to 500 to let the user know that there was a server error

            if ( FAILED( hr ) )

            {

                pHttpContext->GetResponse()->SetStatus( 500,

                                                        "Server Error",

                                                        0,

                                                        hr );

                 return RQ_NOTIFICATION_FINISH_REQUEST;

            }

 

            return RQ_NOTIFICATION_CONTINUE;

        }

 };

 

The only missing part is the Factory class, which should be very straight forward with nothing new in it.

 

 Deploying the module:

 After compiling the module, you will end up with a dll that you can add to the server as a native module by appcmd.exe or any other way.

 %systemroot%\system32\inetsrv\appcmd.exe install module /name:MyModule /image:[FULL_PATH_TO_DLL]

 Should do it.

 You can attach a debugger to the worker process, and set a break point at CMyAppInitModule::OnBeginRequest or at CMyGlobalAppInitModule::OnGlobalApplicationPreload and follow the execution of the pipeline events.

175 Comments

Add a Comment

As it will appear on the website

Not displayed

Your website