しばやん雑記

Azure とメイドさんが大好きなフリーランスのプログラマーのブログ

IIS で使えるネイティブ HTTP モジュールの作り方

最近は IIS の拡張機能について調べていたので、今回は C++ を使って HTTP モジュールを作ってみました。IIS のネイティブ HTTP モジュールはちゃんと実装手順が決まっています。

まずは最初に流れを簡単に紹介しておきます。

  1. CHttpModule を継承したクラスを作成
  2. CHttpModule のファクトリクラスを実装 (IHttpModuleFactory インターフェース)
  3. ファクトリクラスを登録するための関数をエクスポート

珍しいことに IIS の HTTP モジュールは C++ ベースの実装になってます。公式サイトにも実装方法が載っているので、軽く目を通しておくことをお勧めします。

Develop a Native C\\C++ Module for IIS 7.0 | Microsoft Learn

他にも中の人のブログで、ネイティブ HTTP モジュールを開発する上で便利な情報が公開されていたので一緒に紹介しておきます。リファレンスより実装例の方が今回は欲しい感じです。

Jeong's Blog - Developing IIS Native module
Jeong's Blog - Developing IIS Native module 2 (Replacing response body with WriteEntityChunkByReference)

何か面白いモジュールを作れないかなと考えたところ、Lua を組み込んで何かできるモジュールが浮かんだので、最低限の呼び出す部分だけ実装してみました。

CHttpModule を継承したクラスを用意

CHttpModule クラスには仮想関数が用意されているので、必要な分だけオーバーライドする形になります。今回の例では OnBeginRequest だけを実装してみます。

class CMyHttpModule : public CHttpModule
{
private:
    lua_State *pLuaState;

public:
    CMyHttpModule()
    {
        pLuaState = luaL_newstate();

        luaL_openlibs(pLuaState);
        if (luaL_dofile(pLuaState, "C:\\inetpub\\script.lua"))
        {
            auto text = lua_tostring(pLuaState, -1);

            OutputDebugStringA(text);
        }
    }

    ~CMyHttpModule()
    {
        lua_close(pLuaState);
    }

    REQUEST_NOTIFICATION_STATUS OnBeginRequest(
        IN IHttpContext *pHttpContext,
        IN OUT IHttpEventProvider *pProvider
        )
    {
        lua_getglobal(pLuaState, "onBeginRequest");

        if (lua_pcall(pLuaState, 0, 1, 0) != 0)
        {
            auto text = lua_tostring(pLuaState, -1);

            OutputDebugStringA(text);
        }

        return RQ_NOTIFICATION_CONTINUE;
    }
};

このあたりはマネージ HTTP モジュールとあまり変わりがないですね。IHttpContext が取れるので、それを使えば何でも出来るようになってます。

メソッドの戻り値として RQ_NOTIFICATION_CONTINUE を返すとパイプライン内の次のモジュールへ処理を渡し、RQ_NOTIFICATION_FINISH_REQUEST を返すと処理を打ち切ります。

ファクトリクラスを実装する

HTTP モジュールの実体となるクラスを実装したので、次は IHttpModuleFactory を実装したファクトリクラスを作成します。中身は GetHttpModule と Terminate メソッドぐらいです。

class CMyHttpModuleFactory : public IHttpModuleFactory
{
public:
    virtual HRESULT GetHttpModule(OUT CHttpModule **ppModule, IN IModuleAllocator *pAllocator)
    {
        *ppModule = new CMyHttpModule();

        return S_OK;
    }

    virtual void Terminate()
    {
        delete this;
    }
};

GetHttpModule メソッドが呼び出された時に、先ほど作成した CHttpModule を継承したクラスのインスタンスを ppModule 経由で渡します。とてもシンプルですね。

残った Terminate メソッドでは自分自身を delete しておけば良いみたいです。

RegisterModule 関数でファクトリクラスを公開

ファクトリクラスまで作成したので、最後に RegisterModule 関数を用意して、その中でファクトリクラスのインスタンスを設定するだけです。

extern "C" __declspec(dllexport) HRESULT WINAPI RegisterModule(
    DWORD dwServerVersion,
    IHttpModuleRegistrationInfo *pModuleInfo,
    IHttpServer *pHttpServer
    )
{
    return pModuleInfo->SetRequestNotifications(new CMyHttpModuleFactory(), RQ_BEGIN_REQUEST, 0);
}

SetRequestNotifications メソッドにファクトリクラスと RQ_BEGIN_REQUEST を渡しています。

RQ_BEGIN_REQUEST 以外にも種類があり、CHttpModule でオーバーライドしたメソッドに対応する値を指定しないと、実際にメソッドの呼び出しが行われません。

IIS に登録して動作を確認してみる

これでネイティブ HTTP モジュールが完成したので、ビルドした dll を IIS Manager から登録します。この時、IIS から読み込み可能なディレクトリに置いておかないとエラーになります。

動作確認のために用意した Lua スクリプトは、メッセージをファイルに書き出すだけのものです。

function onBeginRequest()
    f = io.open("C:/inetpub/lua/log.txt", "a")
    f:write("iis lua test\n")
    f:close()
end

onBeginRequest という関数は CMyHttpModule クラスの中で定義してあります。

作成したモジュールを IIS に登録後、とりあえず再起動してからブラウザでアクセスすると、ログファイルが書き出されることを確認できました。

本当なら IHttpContext の情報を Lua 側に渡して、ある程度の操作が可能なまで作りこめば Apache や nginx の Lua モジュールのような機能を実現できるかと思います。

実装するのはかなり大変そうですけどね。