しばやん雑記

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

ネイティブ HTTP モジュールを使って IIS のログを書き換える

久し振りに iislua に機能を追加しようと思ったので、IIS のログを書き換える方法を調べて試しました。新しい項目を追加することは出来ないみたいですが、既存の項目であれば書き換え可能みたいです。

CHttpModule には OnLogRequest というメソッドがあるので、ここで処理する必要があるのかと思いましたが、実際には OnSendResponse を実装する必要があります。

CHttpModule::OnSendResponse Method

このメソッドに渡される ISendResponseProvider を使って、IIS のログに書き込まれる前のデータを操作することが出来ます。メソッドは少ないので簡単です。

まずはログの準備が出来ているのか確認するために GetReadyToLogData メソッドを呼び出します。

ISendResponseProvider::GetReadyToLogData Method

ログの準備が出来ていないときには false を返すので、その時は諦めて処理を打ち切ります。

確認後に GetLogData メソッドを呼び出すと、ログのデータが格納された HTTP_LOG_FIELDS_DATA 構造体が取得できます。基本はこの構造体に対して操作を行っていくことになります。

ISendResponseProvider::GetLogData Method
HTTP_LOG_FIELDS_DATA structure (Windows)

HTTP_LOG_FIELDS_DATA への操作が終わったら、SetLogData メソッドを呼び出して反映させます。

ISendResponseProvider::SetLogData Method

ここまでの流れをコードで書くと以下のような感じになります。このサンプルではログの User-Agent を固定値に書き換えるようにしています。

PCHAR maskUserAgent = "Masked";

class CMyHttpModule : public CHttpModule
{
public:
    REQUEST_NOTIFICATION_STATUS OnSendResponse(IN IHttpContext *pHttpContext, IN ISendResponseProvider *pProvider)
    {
        if (!pProvider->GetReadyToLogData())
        {
            return RQ_NOTIFICATION_CONTINUE;
        }

        auto pLogData = reinterpret_cast<PHTTP_LOG_FIELDS_DATA>(pProvider->GetLogData());

        pLogData->UserAgent = maskUserAgent;
        pLogData->UserAgentLength = strlen(maskUserAgent);

        pProvider->SetLogData(reinterpret_cast<PHTTP_LOG_DATA>(pLogData));

        return RQ_NOTIFICATION_CONTINUE;
    }
};

ちなみに SetLogData で NULL をセットすると、ログを書き込まないように出来るみたいです。*1

上のコードではグローバルな領域にある文字列のポインタを渡していたので開放が不要でしたが、std::string の c_str で取得したポインタなどを渡すと開放されるタイミングが怪しいので、AllocateRequestMemory を使ってメモリを確保しましょう。

IHttpContext::AllocateRequestMemory Method

このメソッドで確保されたメモリはリクエストスコープになるみたいなので、安心して使えます。

注意点としては、HTTP モジュールの登録時に RQ_SEND_RESPONSE の優先度を FIRST か HIGH にしておかないと、常に NULL を返すということでしょうか。

Your HTTP module must set the registration priority to PRIORITY_ALIAS_FIRST or PRIORITY_ALIAS_HIGH, or GetLogData will always return NULL. For more information about setting the registration priority, see the IHttpModuleRegistrationInfo::SetPriorityForRequestNotification method.

ISendResponseProvider::GetLogData Method

なので RegisterModule で SetPriorityForRequestNotification を追加で呼び出しておく必要があります。とりあえず PRIORITY_ALIAS_HIGH にしておけば問題ない気がします。

HRESULT APIENTRY RegisterModule(DWORD dwServerVersion, IHttpModuleRegistrationInfo *pModuleInfo, IHttpServer *pHttpServer)
{
    auto hr = pModuleInfo->SetRequestNotifications(new CMyModuleFactory(), RQ_SEND_RESPONSE, 0);

    if (FAILED(hr))
    {
        return hr;
    }

    return pModuleInfo->SetPriorityForRequestNotification(RQ_SEND_RESPONSE, PRIORITY_ALIAS_HIGH);
}

一応 SetRequestNotifications が失敗した時の処理も書いておきます。

これをビルドして IIS に登録してログを確認すると、User-Agent がコードで指定した値に書き換えられていることが確認できます。思っていたよりも簡単でした。

f:id:shiba-yan:20160627132224p:plain

ログに書き出したくない情報をマスクしたり、特定の IP からのアクセスはログに書き出さないなど、利用方法はそれなりにある気がしました。

しかし、これだけのために HTTP モジュールを C++ で作るのはコストに合わない気がするので、iislua に組み込んでもっと手軽に書けるようにしたいですね。