Sheen Space

Web Development Study: Writing IIS native module

leave a comment »


This post comes from my IIS study. It includes basic information about writing own IIS 7 module, and also try to demonstrate difference between global module and HTTP request module, by using the two types of modules in a classic scenario, which is redirecting HTTP requests to HTTPS.

Definitions of request module and global module can be found everywhere. Simply speaking, request module is attached to individual request. In run time, there is a new module instance created everytime a new request comes in. Of course you don’t neccessarily have to create new instance, you can also use singleton, or object pool pattern. But request module’s behaviour is only meaningful to individual request. Request module can only register for request-related events, like BeginRequest, AuthenticateRequest, and EndRequest.

In contrast, global module has its life as long as the application it is enabled for. It can have state information visible to all requests. And it can register for global events, like GlobalFileChange, GlobalConfigurationChange, and PreBeginRequest.

Interface & Base Class

Microsoft provides a selection of interfaces and base classes for developers to write their own modules. A module is typically a DLL file, with one single export function:

HRESULT __stdcall RegisterModule(DWORD aServerVersion, IHttpModuleRegistrationInfo* aRegInfo, IHttpServer* aServer)

To implement global module, you need to derive from CGlobalModule base class, which provides a group of event handler member functions, and Terminate(). You only need to override handler virtual functions of interested events. But remember you have to register for those interested events, and not register for event that you don’t have overrided handler, otherwise IIS will call event handler of base class, and it triggers Debug Break by definition.

To implement Http module, you need to derive from CHttpModule, which also provides a group of event handler member functions and Dispose(). Different from global module implementation, you also need to write your module factory class, derived from IHttpModuleFactory, which declares two pure virtual functions:

HRESULT GetHttpModule(OUT CHttpModule** ppModule, IN IModuleAllocator* pAllocator);
VOID Terminate();

IIS Module Life Management

Both global module and Http module factory have application-level life. They are created in RegisterModule(), and are passed to IIS via aRegInfo->SetGlobalNotifications() and aRegInfo->SetRequestNotifications(). RegisterModule() is called when IIS worker process (w3wp.exe) loads the module DLL. This can happen in two situations:

  • when first request comes in, and first worker process of the application hasn’t loaded the module DLL;
  • when worker process is recycled.

Terminate() is called for both class objects in two situations:

  • when worker process is stopping;
  • when worker process is recycled.

Http module object, in contrast, has request-level life. It is typically created in IHttpModuleFactory::GetHttpModule() function. This function is called whenever new request is ready for processing. Module’s Dispose() function is called when request processing ends.

Now we can write empty modules which do nothing:

Http Module

class TestHttpModuleFactory : public IHttpModuleFactory
{
public:
	HRESULT GetHttpModule(OUT CHttpModule** ppModule, IN IModuleAllocator* pAllocator)
	{
		*ppModule = new TestHttpModule();
		return S_OK;
	}

	VOID Terminate()
	{
		delete this;
	}
};

class TestHttpModule : public CHttpModule
{
public:
    REQUEST_NOTIFICATION_STATUS OnBeginRequest(IN IHttpContext* pHttpContext, IN IHttpEventProvider* pProvider)
	{
		return RQ_NOTIFICATION_CONTINUE;
	}

	REQUEST_NOTIFICATION_STATUS OnEndRequest(IN IHttpContext* pHttpContext, IN IHttpEventProvider* pProvider)
	{
		return RQ_NOTIFICATION_CONTINUE;
	}
};

Global Module

class TestGlobalModule : public CGlobalModule
{
public:
	GLOBAL_NOTIFICATION_STATUS OnGlobalPreBeginRequest(IN IPreBeginRequestProvider* pProvider)
	{
		return GL_NOTIFICATION_CONTINUE;
	}

	VOID Terminate()
	{
		delete this;
	}
};

Module Deployment

There are many good posts about installing and enabling module in IIS, like this one: IIS 7 Modules Overview. You can put your module DLL file anywhere, but the only thing to remember is to grant file access permission to ApplicationPoolIdentity, or you will get vague error.

Redirecting HTTP to HTTPS

Now we want to use module to handle a classic scenario – automatically redirect non-secure client request (http://) to secure url (https://).

I use default web site provided by IIS 7 to demonstrate. In SSL settings of application “Default Web Site”, we enable “Require SSL”. Now when we type “http://localhost/iisstart.htm” in browser, we will get error message:

HTTP Error 403.4 – Forbidden
The page you are trying to access is secured with Secure Sockets Layer (SSL).

Error 403.4

We can try to use http module to tackle the problem. Naturally, we want to register for BeginRequest event, and redirect request url there. After implementation, we re-type “http://localhost/iisstart.htm”, but still get same error message. Redirection doesn’t work. Why is it? First good thing to do is to enable Failed Request Logging for our application, error code 403.4. In generated log file, we can see that BeginRequest event is not triggered for our module, only EndRequest event triggered. It looks like SSL request check is done in IIS Web Core component, and if check fails, no normal events will be triggered for http modules, only error logging and response sending continue.

We notice that global PreBeginRequest event is triggered for those global modules. We can turn to global module for help. In the global module code below, we register for GL_PRE_BEGIN_REQUEST event, and redirect request there.

    GLOBAL_NOTIFICATION_STATUS OnGlobalPreBeginRequest(IN IPreBeginRequestProvider* pProvider)
    {
		IHttpContext* pHttpContext = pProvider->GetHttpContext();
		IHttpRequest* pRequest = pHttpContext->GetRequest();

		PCWSTR url = pRequest->GetRawHttpRequest()->CookedUrl.pFullUrl;

		PCWSTR http = L"http://";
		PCWSTR https = L"https://";
		PHTTP_SSL_INFO ssl = pRequest->GetRawHttpRequest()->pSslInfo;

		if (wcsncmp(url, https, 8) != 0 && ssl == NULL && wcsncmp(url, http, 7) == 0)
		{
			HRESULT hr = pHttpContext->GetResponse()->Redirect("https://localhost/iisstart.htm");
			if (FAILED(hr))
			{
				// Log error
			}
			else
			{
		        return GL_NOTIFICATION_HANDLED;
			}
		}

        return GL_NOTIFICATION_CONTINUE;
    }

This time, redirection works well. It proves that if we want to implement complete HTTP-to-HTTPS redirection functionality, using Http module is not enough. Global module is the only candidate for comprehensive solution.

Advertisements

Written by Ying

21/10/2010 at 13:30

Posted in ASP.Net, Programming, Technology

Tagged with ,

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

%d bloggers like this: