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.