読者です 読者をやめる 読者になる 読者になる

しばやん雑記

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

Windows Azure Web サイトのリバースプロキシでエラーになった件を解決した話

Windows Azure Web サイトで Perl を動かそうとしたらリバースプロキシでエラーになる件 - しばやん雑記 の続きで、問題を解決できたのでメモしておきます。

Web サイトのリバースプロキシである ARR 部分で 502 になってしまっていて、ログも出せずに原因不明で心が折れそうだったのですが、今朝ふと HttpModule で Perl のレスポンスをキャプチャすればわかるんじゃないかと思いました。

ということで、サクッとレスポンスを Trace に吐き出す HttpModule を作りました。

レスポンスボディをキャプチャする方法は c# - How do I retrieve response html from within a HttpModule? - Stack Overflow を参考に実装しました。というかほぼそのまんまです。

public class StreamWatcher : Stream
{
    private Stream _base;
    private MemoryStream _memoryStream = new MemoryStream();

    public StreamWatcher(Stream stream)
    {
        _base = stream;
    }

    public override void Flush()
    {
        _base.Flush();
    }

    public override int Read(byte[] buffer, int offset, int count)
    {
        return _base.Read(buffer, offset, count);
    }

    public override void Write(byte[] buffer, int offset, int count)
    {
        _memoryStream.Write(buffer, offset, count);
        _base.Write(buffer, offset, count);
    }

    public override string ToString()
    {
        return Encoding.UTF8.GetString(_memoryStream.ToArray());
    }

    public override bool CanRead
    {
        get { return _base.CanRead; }
    }

    public override bool CanSeek
    {
        get { return _base.CanSeek; }
    }

    public override bool CanWrite
    {
        get { return _base.CanWrite; }
    }

    public override long Seek(long offset, SeekOrigin origin)
    {
        return _base.Seek(offset, origin);
    }

    public override void SetLength(long value)
    {
        _base.SetLength(value);
    }

    public override long Length
    {
        get { return _base.Length; }
    }

    public override long Position
    {
        get { return _base.Position; }
        set { _base.Position = value; }
    }
}

public class LoggingModule : IHttpModule
{
    public void Dispose()
    {
    }

    private StreamWatcher _watcher;

    public void Init(HttpApplication context)
    {
        context.BeginRequest += (o, e) =>
        {
            _watcher = new StreamWatcher(context.Response.Filter);

            context.Response.Filter = _watcher;
        };

        context.EndRequest += (o, e) =>
        {
            Trace.WriteLine("Header:");

            // Capture Headers
            foreach (var key in context.Response.Headers.AllKeys)
            {
                Trace.WriteLine(key + ": " + context.Response.Headers[key]);
            }

            // Capture Body
            string value = _watcher.ToString();

            Trace.WriteLine("Body: " + value);
        };
    }
}

クラスライブラリとしてビルドしておくと、当然ですが dll が生成されます。あとは CGI の実行パイプラインに追加するだけですが、このあたりは手順は田口さんのブログに詳しく書いてあるので、そちらを参考にした方が良いです。

Azure WebサイトでWordPressのようなPHPなアプリケーションでも基本認証やダイジェスト認証をかける | たんたか

ちなみに HttpAuthModule.dll は人形町の夜王こと、ひろゆき先生作です。

肝となる設定は modules 要素なので、そこだけは引っ張ってきておきます。

<system.webServer>
  <modules runAllManagedModulesForAllRequests="true">
    <add type="ClassLibrary1.LoggingModule" name="LoggingModule" />
  </modules>
</system.webServer>

これで全ての HTTP レスポンスは Trace に吐き出されるので、あとはデプロイを行い Web サイトのストリーミングログ機能を使って監視できます。Kudu からも使えますが、Azure SDK 2.2 をインストール済みであればサーバーエクスプローラーから利用できます。

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

正常にストリーミングログに接続できた場合には、出力ウィンドウが開いて以下のような表示になっているはずです。

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

これで LoggingModule が出力した Trace を全て確認できるようになります。実に便利ですね。

そして結局のところ YukiWiki が正常に動かなかった原因は PluginManager.pm が deprecated な機能を使っていて、それに関する警告が標準出力に書き出されていたみたいです。

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

良い感じにコロンが含まれていて、IIS 的には HTTP ヘッダとして処理がされてしまったのですが、ARR はそれを許さずにエラーにしたというのが結論のようです。

なので、警告メッセージを出力しないようにしたところ、正常に YukiWiki が Azure Web サイトでも動作するようになりました。