C# と VB.NET の質問掲示板

ASP.NET、C++/CLI、Java 何でもどうぞ

C# と VB.NET の入門サイト

ASPもしくはIISでhttpレスポンスボディを確認する方法

[トピック内 11 記事 (1 - 11 表示)]  << 0 >>

■87867 / inTopicNo.1)  ASPもしくはIISでhttpレスポンスボディを確認する方法
  
□投稿者/ abc (1回)-(2018/07/09(Mon) 08:54:45)

分類:[ASP.NET (C#)] 

環境
IIS 8
ASP.NET 3.5 C#

自サイトがユーザに対して返すhttpレスポンスボディをログとして保存したく、ASP側で取得したいのですが方法が分かりません。
今試しているのは、IHttpModuleを実装したwebmoduleを作り、Application_EndRequest()辺りで取得できないかやっていますが、なかなか上手くいきません。

掲示板の主旨とは違いますが、もしIIS側でログ化できるならそれが一番いいのですが……
ご存知の方いらっしゃいましたらご教授頂きたくお願い致します。
引用返信 編集キー/
■87868 / inTopicNo.2)  Re[1]: ASPもしくはIISでhttpレスポンスボディを確認する方法
□投稿者/ WebSurfer (1545回)-(2018/07/09(Mon) 10:21:29)
No87867 (abc さん) に返信

ググって調べただけですが(なので未検証・未確認ですが)以下の記事が参考になりませんか?

Logging raw HTTP request/response in ASP.NET MVC & IIS7
https://stackoverflow.com/questions/1038466/logging-raw-http-request-response-in-asp-net-mvc-iis7#
引用返信 編集キー/
■87869 / inTopicNo.3)  Re[1]: ASPもしくはIISでhttpレスポンスボディを確認する方法
□投稿者/ WebSurfer (1546回)-(2018/07/09(Mon) 12:35:26)
No87867 (abc さん) に返信

上のレスで「未検証・未確認ですが」と書きましたが、紹介した記事にあるように HttpResponse.Filter
プロパティを利用して応答のコンテンツを取得できることは確認できました。お試しください。
引用返信 編集キー/
■87870 / inTopicNo.4)  Re[2]: ASPもしくはIISでhttpレスポンスボディを確認する方法
□投稿者/ abc (2回)-(2018/07/09(Mon) 13:45:53)
No87869 (WebSurfer さん) に返信
ご確認までして頂きありがとうございます。
現状報告としまして、レスポンスボデイが取得できることは確認できましたが実装で引っ掛かっている状態です。

・ウェブページは正常に表示されるが、ログ上ではobject movedとなってしまい、以降記録されない
・今の方法だとアプリケーションプールが統合モードでしか動かないが、クラシックモードで実現したい(できれば)

■ログ
[Request]2018/07/09 13:26:10:
http://localhost:8888/hoge.aspx
[Response]2018/07/09 13:26:13:
(意図する正常なhtml)
[Request]2018/07/09 13:26:17:
http://localhost:8888/hoge.aspx
[Response]2018/07/09 13:26:17:
<h tml><h ead><t itle>Object moved</t itle></h ead><b ody>
<h 2>Object moved to <a h ref="hoge.aspx">here</ a>.</h 2>
</b ody></html>
[Request]2018/07/09 13:26:17:
http://localhost:8888/hoge.aspx
[Response]2018/07/09 13:26:17:
(以降記録されず……)

■ソース
※OutputFilterStreamはご紹介頂いた下記URLのものをコピペしています。
https://stackoverflow.com/questions/1038466/logging-raw-http-request-response-in-asp-net-mvc-iis7#

public class Logger : IHttpModule
    {
     static readonly string responseLogPath = @"D:\test.txt";
     OutputFilterStream filter;

     public void Init(HttpApplication application)
     {
         application.BeginRequest += (new EventHandler(this.Application_BeginRequest));
         application.EndRequest += (new EventHandler(this.Application_EndRequest));
     }

     public void Dispose()
     {
     }

     private void Application_BeginRequest(Object sender, EventArgs e)
     {
         HttpResponse response = HttpContext.Current.Response;
         WriteLog("[Request]" + DateTime.Now + ":" + Environment.NewLine + HttpContext.Current.Request.Url.ToString());

         filter = new OutputFilterStream(response.Filter);
         response.Filter = filter;
     }

     private void Application_EndRequest(object sender, EventArgs e)
     {
         WriteLog("[Response]" + DateTime.Now + ":" + Environment.NewLine + filter.ReadStream());
     }

     private void WriteLog(string log)
     {
         using (var sw = new StreamWriter(responseLogPath, true))
         {
             sw.WriteLine(log);
         }
     }
}


引用返信 編集キー/
■87871 / inTopicNo.5)  Re[3]: ASPもしくはIISでhttpレスポンスボディを確認する方法
□投稿者/ WebSurfer (1547回)-(2018/07/09(Mon) 14:41:12)
No87870 (abc さん) に返信

> ・ウェブページは正常に表示されるが、ログ上ではobject movedとなってしまい、以降記録されない

紹介した記事には、

"Store filter where you can get to it in the EndRequest handler. I suggest in
HttpContext.Items. There can then get the full response data in
filter.ReadStream()."

と HttpContext.Items を使うように書いてありましたが、質問者さんのコードではそう
なっていないようです。

質問者さんのケースでそこが原因かどうかは分かりませんが、少なくとも自分の環境では
紹介した記事の通りにしてログは取れます。


> ・今の方法だとアプリケーションプールが統合モードでしか動かないが、クラシックモードで実現したい(できれば)

自分的には IIS のクラシックモードはすでに忘却の彼方にあって、クラシックモードの話に
立ち入る元気はないです。

お役に立てずすみませんが、他の方の回答をお待ちください。

引用返信 編集キー/
■87872 / inTopicNo.6)  Re[4]: ASPもしくはIISでhttpレスポンスボディを確認する方法
□投稿者/ abc (3回)-(2018/07/09(Mon) 17:55:44)
No87871 (WebSurfer さん) に返信
ご返信ありがとうございます。
引き続き挑戦してみます。
引用返信 編集キー/
■87874 / inTopicNo.7)  Re[5]: ASPもしくはIISでhttpレスポンスボディを確認する方法
□投稿者/ abc (4回)-(2018/07/09(Mon) 18:16:24)
取り敢えず、クラシックモードで一部のページのみ、ログ出力を実現できました。

現状問題点としては下記の通りで、無知を晒すようで恥ずかしいですが、ひとまず整理してみます。
環境的にデバッグが出来ないため、手探りでやっています……。

1.マネージメントパイプラインモードがクラシックモードだと下記の例外が起きる
[PlatformNotSupportedException: この操作を実行するには、IIS 統合パイプライン モードが必要です。]
どの処理でこけるのか分かりませんが、動くページ、動かないページがあります。

2.統合モードだとwebmoduleが動かない
まだ調査出来ていません。クラシックモードと構成設定が違う?
1の事情から、統合モードでの運用は必須かと思います。

3.ページの一部が表示されない
ログファイルでresponseBodyを確認すると、一部が文字化けしているので、文字コードの問題かと推測します。
ただこの問題に限らず、今回のログ出力処理が実際のレスポンス内容に影響を与えることは避けなければいけません。

もし何かヒントがありましたらコメント頂ければ幸いです。
よろしくお願い致します。
引用返信 編集キー/
■87875 / inTopicNo.8)  Re[6]: ASPもしくはIISでhttpレスポンスボディを確認する方法
□投稿者/ WebSurfer (1548回)-(2018/07/09(Mon) 18:46:06)
No87874 (abc さん) に返信

> もし何かヒントがありましたらコメント頂ければ幸いです。

紹介した記事のように HttpContext.Items を使うという話はどうなったのですか?

IIS のクラシックモードを使うのは諦めて統合パイプラインモードでやることにしたのですか?

そのあたりをクリアにしてもらえないと、この先私がお役に立てるかどうか分からないのですが・・・

とりあえず、検証に使った HTTP モジュールのコードをアップしておきます。環境は Windows 10
Pro 64-bit, ローカル IIS10, Visual Studio 2015 Cimmunity, .NET 4.6.1 です。

HTTP モジュールが動かないとか文字化けするという問題はありません。

using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.IO;

public class ResponseContentLogHttpModule : IHttpModule
{
    public ResponseContentLogHttpModule()
    {
    }

    public String ModuleName
    {
        get { return "ResponseContentLogHttpModule"; }
    }

    // In the Init function, register for HttpApplication 
    // events by adding your handlers.
    public void Init(HttpApplication application)
    {
        application.BeginRequest += new EventHandler(this.Application_BeginRequest);
        application.EndRequest += new EventHandler(this.Application_EndRequest);
    }

    private void Application_BeginRequest(Object source, EventArgs e)
    {
        // Create HttpApplication and HttpContext objects to access
        // request and response properties.
        HttpApplication application = (HttpApplication)source;
        HttpContext context = application.Context;
        string filePath = context.Request.FilePath;
        string fileExtension = VirtualPathUtility.GetExtension(filePath);
        if (fileExtension.Equals(".aspx"))
        {
            HttpResponse response = context.Response;
            var filter = new OutputFilterStream(response.Filter);
            response.Filter = filter;
            context.Items["OutputFilterStream"] = filter;
        }
    }

    private void Application_EndRequest(Object source, EventArgs e)
    {
        HttpApplication application = (HttpApplication)source;
        HttpContext context = application.Context;
        string filePath = context.Request.FilePath;
        string fileExtension = VirtualPathUtility.GetExtension(filePath);
        if (fileExtension.Equals(".aspx"))
        {
            var filter = (OutputFilterStream)context.Items["OutputFilterStream"];
            string responseContent = filter.ReadStream();
            WriteLog(responseContent);
        }
    }

    private void WriteLog(string log)
    {
        string responseLogPath = HttpContext.Current.Server.MapPath("~/App_Data/log.txt");
        using (var sw = new StreamWriter(responseLogPath, true))
        {
            sw.WriteLine(log);
        }
    }

    public void Dispose() { }
}

public class OutputFilterStream : Stream
{
    private readonly Stream InnerStream;
    private readonly MemoryStream CopyStream;

    public OutputFilterStream(Stream inner)
    {
        this.InnerStream = inner;
        this.CopyStream = new MemoryStream();
    }

    public string ReadStream()
    {
        lock (this.InnerStream)
        {
            if (this.CopyStream.Length <= 0L ||
                !this.CopyStream.CanRead ||
                !this.CopyStream.CanSeek)
            {
                return String.Empty;
            }

            long pos = this.CopyStream.Position;
            this.CopyStream.Position = 0L;
            try
            {
                return new StreamReader(this.CopyStream).ReadToEnd();
            }
            finally
            {
                try
                {
                    this.CopyStream.Position = pos;
                }
                catch { }
            }
        }
    }


    public override bool CanRead
    {
        get { return this.InnerStream.CanRead; }
    }

    public override bool CanSeek
    {
        get { return this.InnerStream.CanSeek; }
    }

    public override bool CanWrite
    {
        get { return this.InnerStream.CanWrite; }
    }

    public override void Flush()
    {
        this.InnerStream.Flush();
    }

    public override long Length
    {
        get { return this.InnerStream.Length; }
    }

    public override long Position
    {
        get { return this.InnerStream.Position; }
        set { this.CopyStream.Position = this.InnerStream.Position = value; }
    }

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

    public override long Seek(long offset, SeekOrigin origin)
    {
        this.CopyStream.Seek(offset, origin);
        return this.InnerStream.Seek(offset, origin);
    }

    public override void SetLength(long value)
    {
        this.CopyStream.SetLength(value);
        this.InnerStream.SetLength(value);
    }

    public override void Write(byte[] buffer, int offset, int count)
    {
        this.CopyStream.Write(buffer, offset, count);
        this.InnerStream.Write(buffer, offset, count);
    }
}

引用返信 編集キー/
■87877 / inTopicNo.9)  Re[7]: ASPもしくはIISでhttpレスポンスボディを確認する方法
□投稿者/ PANG2 (218回)-(2018/07/10(Tue) 11:00:08)
(1)HTTPモジュールよりGlobal.asaxの方が容易。

[ASP.NET]アプリケーション共通のロギングを行うには?(HTTPモジュール編)
http://www.atmarkit.co.jp/fdotnet/dotnettips/130asploghttp/asploghttp.html

[ASP.NET]アプリケーション共通のロギングを行うには?(Global.asax編)
http://www.atmarkit.co.jp/fdotnet/dotnettips/127asplogasx/asplogasx.html


(2)共通のPage発生クラスのRenderでキャプチャする。

[ASP.NET]アプリケーション共通の処理をPage派生クラスで実装するには?
http://www.atmarkit.co.jp/fdotnet/dotnettips/295pagevalidate2/pagevalidate2.html

ASP.NET 出力レスポンスをキャプチャする方法
http://csharpvbcomparer.blogspot.com/2015/05/aspnet-capturing-output-response.html


(3) trace.axd でコントロールツリーをロギングする(最大1万件でよければ)

[ASP.NET]ページのトレース情報を出力するには?
http://www.atmarkit.co.jp/fdotnet/dotnettips/040trace/trace.html

引用返信 編集キー/
■87878 / inTopicNo.10)  Re[8]: ASPもしくはIISでhttpレスポンスボディを確認する方法
□投稿者/ abc (5回)-(2018/07/10(Tue) 17:49:07)
No87875 (WebSurfer さん) に返信
コードの提示ありがとうございます。勉強になります。
HttpContext.Itemsを使うという意味がよく分からず、使わないやり方でも一応は動作していたためそのまま使っていましたが、ご提示頂いたコードでクラシックモードでも動作しました。ですので現状No87875のコードでクラシックモードです。
HttpContext.Itemsで保持するのと、フィールド変数で保持するのとで動作が変わるのは何故なのでしょうか?

文字化けは現在も発生していますが、文字化けが起こっていたのはAjaxToolKitに関する下記リクエストに対するレスポンスボディ部のみでした。OutputStreamを読むStreamReader部とログファイル出力のStreamWriter部で文字コードを指定してみましたがダメでした。対応方法はありますでしょうか。
GET http://localhost:8080/hoge.aspx?_TSM_HiddenField_=ToolkitScriptManager1_HiddenField&_hogehoge
Connection: Keep-Alive
Accept: application/javascript, */*;q=0.8
Accept-Encoding: gzip, deflate
Accept-Language: ja-JP

No87877 (PANG2 さん) に返信
ご紹介頂きありがとうございます。
現行の方法でほぼ期待する動作ができたため今はこのまま進めますが、Page派生クラスのPage.Renderを使う方法は分かりやすく次回検討させて頂きます。



引用返信 編集キー/
■87880 / inTopicNo.11)  Re[9]: ASPもしくはIISでhttpレスポンスボディを確認する方法
□投稿者/ WebSurfer (1549回)-(2018/07/10(Tue) 19:46:51)
No87878 (abc さん) に返信

> HttpContext.Itemsを使うという意味がよく分からず、使わないやり方でも一応は動作していたためそのまま使っていましたが、

分からなかったからと言って無視する・スルーするのは止めましょうよ。
少なくとも分からないと言ってください。
そうでないと話が噛み合わなくなりますし、せっかく答えたのにその甲斐がないです。

> HttpContext.Itemsで保持するのと、フィールド変数で保持するのとで動作が変わるのは何故なのでしょうか?

HttpContext.Itemsで保持すると、少なくとも HttpContext が存在する限り filter が指すオブジェクトは保持される
(GC の対象にはならない)が、クラスのフィールド変数はその限りではないということだと思います。

ただ、その具体的なメカニズムは自分は分かりませんが。

>
> 文字化けは現在も発生していますが、文字化けが起こっていたのはAjaxToolKitに関する下記リクエストに対するレスポンスボディ部のみでした。
> 対応方法はありますでしょうか。

そんなこと言われても、具体的な話が何もないので分かりません。コピペすれば問題を再現できるごくごく簡単な
サンプルを書いてアップしてください。
引用返信 編集キー/

このトピックをツリーで一括表示


トピック内ページ移動 / << 0 >>

このトピックに書きこむ