Developing IIS Native module 2 (Replacing response body with WriteEntityChunkByReference)

Summary:

In the last blog, I tried to explain the big picture for IIS native API. So, next thing is to make a real native module. Because IIS native api is huge, I will introduce one by one with some interesting topics and explain why I was interested in for the specific topic.

In this blog, I decided to make a small program with which I can replace the response body with a specific data so that I can learn how to use the IIS native API for manipulating the response data. As usual, I had to solve some issues and hope to share what I learned while solving the problem.

What I learned:

If you look at the attached code below, you will notice the following two lines, which was my first issue to solve.

//char szBuffer[BUFFERLENGTH];
//char szBuffer2[BUFFERLENGTH];  

Because I did not know what memory should be used to set the FromMemory.pBuffer pointer, I started with a stack memory which is used by local variable and I realize it does not work. I got bunch of 0 values or some garbage data.
And then I realize that IIS API requires the memory should be kept in the life cycle of the request and that's why the stack memory did not work. Also, I realize httpContext has AllocateRequestMemory() method and we can allocate a certain size of memory with using it and the memory is kept during the life cycle of the request. So, I got the answer how to fix the first issue.

And then I wanted to display some non-English string and I realize the non-English string, for example Unicode string, should be encoded in UTF8 so that IE can display the data correctly.

I used WriteEntityChunkByReference() API function to set response body with my own data because we can directly assign my data using the API.

One thing interesting regarding the function is we can set -1 to append the data to the existing response body data.

I used OnGlobalPreBeginRequest event handler here. Please refer to MSDN what the event handler means.  

I also added the line of "pHttpResponse->Clear();" but it does not meaningful in the OnGlobalPreBeginRequest event handler because the response body is actually already empty because the response body is not made yet in that stage.

 

#include "stdafx.h"
#define _WINSOCKAPI_
#include <httpserv.h>

class MyGlobalModule : public CGlobalModule
{
public:

    GLOBAL_NOTIFICATION_STATUS
    OnGlobalPreBeginRequest(IN IPreBeginRequestProvider * pProvider)
    {
        HRESULT hr = S_OK;
        IHttpContext* pHttpContext = pProvider->GetHttpContext();
        IHttpResponse * pHttpResponse = pHttpContext->GetResponse();

        if (pHttpResponse != NULL)
        {
            HTTP_DATA_CHUNK dataChunk1;
            HTTP_DATA_CHUNK dataChunk2;
            
            pHttpResponse->Clear();

            int BUFFERLENGTH = 256;

            //char szBuffer[BUFFERLENGTH];
            //char szBuffer2[BUFFERLENGTH];   

            char* szBuffer = (char *) pHttpContext->AllocateRequestMemory(BUFFERLENGTH);
            char* szBuffer2 = (char *) pHttpContext->AllocateRequestMemory(BUFFERLENGTH);

            dataChunk1.DataChunkType = HttpDataChunkFromMemory;
            strcpy_s(szBuffer, 255, "Hello world!!!\r\n");

            dataChunk1.FromMemory.pBuffer = (PVOID) szBuffer;
            dataChunk1.FromMemory.BufferLength = (ULONG) strlen(szBuffer);
            hr = pHttpResponse->WriteEntityChunkByReference( &dataChunk1, -1);

            if (FAILED(hr))
            {
                pProvider->SetErrorStatus( hr );                
                return GL_NOTIFICATION_HANDLED;
            }

            dataChunk2.DataChunkType = HttpDataChunkFromMemory;
            wchar_t wstrTest1[] = L"안녕하세요"; 
            int encodedStrLen = WideCharToMultiByte(CP_UTF8, 0, (LPCWSTR) wstrTest1, -1, szBuffer2, BUFFERLENGTH, NULL, NULL);

            dataChunk2.FromMemory.pBuffer = (PVOID) szBuffer2;
            dataChunk2.FromMemory.BufferLength = encodedStrLen;
            hr = pHttpResponse->WriteEntityChunkByReference( &dataChunk2, -1);

            if (FAILED(hr))
            {
                pProvider->SetErrorStatus( hr );                
                return GL_NOTIFICATION_HANDLED;
            }
            return GL_NOTIFICATION_HANDLED;
        }

        return GL_NOTIFICATION_CONTINUE;
    }

    VOID Terminate()
    {
        delete this;
    }

    MyGlobalModule()
    {
    }

    ~MyGlobalModule()
    {
    }
};

HRESULT
__stdcall
RegisterModule(
    DWORD dwServerVersion,
    IHttpModuleRegistrationInfo * pModuleInfo,
    IHttpServer * pGlobalInfo
)
{
    UNREFERENCED_PARAMETER( dwServerVersion );
    UNREFERENCED_PARAMETER( pGlobalInfo );

    MyGlobalModule * pGlobalModule = new MyGlobalModule;

    if (NULL == pGlobalModule)
    {
        return HRESULT_FROM_WIN32(ERROR_NOT_ENOUGH_MEMORY);
    }

    return pModuleInfo->SetGlobalNotifications(
        pGlobalModule, GL_PRE_BEGIN_REQUEST );
}

How to test the source code:

1. Logon as local administrator's account onto a machine where IIS is installed

2. Create a Visual C++ project of "Class Library" to make a DLL.

3. Copy and paste the above code

4. Right-mouse click the "Source Files" folder and click "Add" context menu and then select "New Item..."

5. Choose "Module-Definition File (.def)" and create a new .def file and paste the following 3 lines (NOTE: the "mod_HelloWorld" was used here because my project is to create the mod_HelloWorld.dll file; you should use the name of the dll file which is created by your project instead of the "mod_HelloWorld"):

LIBRARY "mod_HelloWorld"
EXPORTS
RegisterModule

6. Create a local directory such as c:privates

7. Compile the project to make the dll file, mod_HelloWorld.dll for example, and copy the dll file onto the newly created local directory

8. Open %windir%system32inetsrvconfigapplicationhost.config file and add two lines of <add>, one in the globalModules section and the other in the modules section, as the following:
        ...
            <add name="mod_HelloWorld" image="C:privatesmod_HelloWorld.dll" />
        </globalModules>

        ... 

            <add name="mod_HelloWorld" />
        </modules>
9. Launch IE and send http://localhost request and now you will see the response body is replaced with "Hello world!!! 안녕하세요". (FYI, if the result is displayed with broken characters, the current encoding is not UTF8, which can be fixed by right-mouse-clicking the IE's main page, selecting "Encoding" context menu and choosing the "Unicode (UTF-8)" encoding.

Conclusion:

Now, I hope you feel confident about how to use the WriteEntityChunkByReference API for manipulating the response body and how to handle non English string to make response body with using this simple module.

If you are not clear yet, please try to read the code carefully and try to update the code as you want. I realize the best way to learn programing is to do it by myself without other's help. IIS native API, however, is too huge to catch them easily but you will feel it is actually not that huge eventually. 

I will try to introduce more interesting topic in the next time. 

No Comments