web-dev-qa-db-ja.com

リクエストボディストリームの巻き戻し

リクエストロガーをOwinミドルウェアとして再実装し、すべての着信リクエストのリクエストURLと本文をログに記録します。 本体を読み取ることはできますが、実行すると、コントローラーの本体パラメーターがnullになります。

ストリームの位置が最後にあるのでnullだと思うので、本文を逆シリアル化しようとしたときに読み取るものは何もありません。以前のバージョンのWeb APIでも同様の問題がありましたが、Streamの位置を0に戻すことができました。この特定のストリームは_This stream does not support seek operations_例外をスローします。

Web API 2.0の最新バージョンでは、リクエストロガー内でRequest.HttpContent.ReadAsStringAsync()を呼び出すことができましたが、本体はそのままコントローラーに到着していました。

ストリームを読み取った後にストリームを巻き戻すにはどうすればよいですか?

または

リクエスト本文を消費せずに読み取るにはどうすればよいですか?

_public class RequestLoggerMiddleware : OwinMiddleware
{
    public RequestLoggerMiddleware(OwinMiddleware next)
        : base(next)
    {
    }

    public override Task Invoke(IOwinContext context)
    {
        return Task.Run(() => {
            string body = new StreamReader(context.Request.Body).ReadToEnd();
            // log body

            context.Request.Body.Position = 0; // cannot set stream position back to 0
            Console.WriteLine(context.Request.Body.CanSeek); // prints false
            this.Next.Invoke(context);
        });
    }
}
_

_public class SampleController : ApiController 
{
    public void Post(ModelClass body)
    {
        // body is now null if the middleware reads it
    }
}
_
28
Despertar

ただ一つの解決策を見つけました。元のストリームを、データを含む新しいストリームで置き換えます。

    public override Task Invoke(IOwinContext context)
    {
        return Task.Run(() => {
            string body = new StreamReader(context.Request.Body).ReadToEnd();
            // log body

            byte[] requestData = Encoding.UTF8.GetBytes(body);
            context.Request.Body = new MemoryStream(requestData);
            this.Next.Invoke(context);
        });
    }

大量のデータを扱う場合は、FileStreamも代わりに機能すると思います。

38
Despertar

これはDespertarによる最初の回答を少し改善したもので、多くの助けになりましたが、バイナリデータを操作するときに問題が発生しました。中間ステップストリームを文字列に抽出し、Encoding.UTF8.GetBytes(body)を使用してバイト配列に戻す方法は、バイナリコンテンツを混乱させます(コンテンツは、UTF8エンコードされた文字列でない限り変更されます)。これがStream.CopyTo()を使用した私の修正です。

    public override async Task Invoke(IOwinContext context)
    {
        // read out body (wait for all bytes)
        using (var streamCopy = new MemoryStream())
        {
            context.Request.Body.CopyTo(streamCopy);
            streamCopy.Position = 0; // rewind

            string body = new StreamReader(streamCopy).ReadToEnd();
            // log body

            streamCopy.Position = 0; // rewind again
            context.Request.Body = streamCopy; // put back in place for downstream handlers

            await this.Next.Invoke(context);
        }
    }

また、MemoryStreamは、完全なボディをログに記録する前にストリームの長さを確認できるため、いいです(誰かが巨大なファイルをアップロードした場合に、私がしたくないことです)。

11
Efrain