WebSurfer's Home

トップ > Blog 1   |   ログイン
APMLフィルター

サーバー側で応答コンテンツの取得 (CORE)

by WebSurfer 2022年7月7日 16:30

ASP.NET Core Web アプリの応答ボディの文字列を、サーバー側でのログなどの目的で、取得する方法を書きます。先の記事「サーバー側で応答コンテンツの取得」の ASP.NET Core 版です。

応答ボディの文字列

.NET Framework 版 ASP.NET で利用した HttpModule、HttpResponse.Filter プロパティ、HttpResponse.OutputStream プロパティは ASP.NET Core ではサポートされていません。

代わりにカスタム MiddlewareHttpResponse.Body プロパティを使って同様なことを行います。

ただし、HttpResponse.Body プロパティで取得できる Stream は CanRead, CanSeek が false になっており読むことができないという問題があります。

その解決に、先の記事「HttpRequest.Body から読み取る方法 (CORE)」で書いた EnableBuffering メソッドが使えるかと思ったのですが、応答側 HttpResponse ではサポートされていませんでした。

やむを得ず、Middleware で next.Invoke の前に応答ボディ用の Stream を MemoryStream に差し替えて、next.Invoke の後で MemoryStream に書き込まれた応答ボディを取得するという手段を取りました。

そのサンプルコードは以下の通りです。デバッグ実行すると上の画像ように「出力」ウィンドウに応答ボディの文字列が取得できます。

namespace MvcCore6App3.Middleware
{
    public class ResponseContentLogMiddleware
    {
        private readonly RequestDelegate _next;

        public ResponseContentLogMiddleware(RequestDelegate next)
        {
            this._next = next;
        }

        public async Task InvokeAsync(HttpContext context)
        {
            // この例では Home/Privacy のみログを取るという前提
            if (context.Request.Path.ToString().Contains("/Home/Privacy"))
            {
                // 応答ボディの Stream を取得して保持
                Stream responseStream = context.Response.Body;

                // 応答ボディの文字列を取得する
                string bodyContent = "";

                try
                {
                    using (var memoryStream = new MemoryStream())
                    {
                        // 応答ストリームを MemoryStream に差し替えて、それに
                        // 応答ボディを取得。_next.Invoke の前でないとダメ
                        context.Response.Body = memoryStream;

                        await _next.Invoke(context);

                        // この時点ではすでに MemoryStream には応答ボディは
                        // 書き込まれている

                        if (memoryStream.Length > 0L &&
                            memoryStream.CanRead &&
                            memoryStream.CanSeek)
                        {
                            memoryStream.Position = 0L;
                            var reader = new StreamReader(memoryStream);
                            bodyContent = await reader.ReadToEndAsync();

                            // 確認用(デバッグ実行で Visual Studio の「出力」
                            // ウィンドウに表示される)
                            System.Diagnostics.Debug.Write(bodyContent);

                            memoryStream.Position = 0L;

                            // MemoryStream に取得した応答ボディのバイト列を
                            // responseStream にコピー
                            await memoryStream.CopyToAsync(responseStream);
                        }
                    }
                }
                finally
                {
                    // 上のコードで MemoryStream に差し替えた応答ボディの
                    // Stream を元に戻す
                    context.Response.Body = responseStream;
                }
            }
            else
            {
                // 何もしない場合でも以下のコードは必須。これが無いとミドル
                // ウェアのチェーンの途中で止まってしまう
                await _next.Invoke(context);
            }
        }
    }
}

上の Middleware が動くようにするには Program.cs (.NET 5.0 以前では Startup.cs) での設定が必要です。以下のコードで「追記」とコメントした一行を追加します。

// ・・・前略・・・

app.UseAuthentication();
app.UseAuthorization();

// 追記
app.UseMiddleware<ResponseContentLogMiddleware>();

app.MapControllerRoute(
    name: "default",
    pattern: "{controller=Home}/{action=Index}/{id?}");
app.MapRazorPages();

app.Run();

HttpResponse.Body プロパティで取得できる Stream が読めないのは、HttpRequest.Body と同様に、"lightweight and performant as possible" ということを狙ってのことではないかと思います。

なので、上記のようなことをすると性能上の劣化が生じるかもしれません。どうしてもログが必要と言うような場合にとどめておいた方が良さそうな気がします。

Tags: , , ,

CORE

About this blog

2010年5月にこのブログを立ち上げました。主に ASP.NET Web アプリ関係の記事です。

Calendar

<<  2024年4月  >>
31123456
78910111213
14151617181920
21222324252627
2829301234
567891011

View posts in large calendar