web-dev-qa-db-ja.com

カスタムOwinミドルウェアで応答ストリームを安全にインターセプトするにはどうすればよいですか

応答ストリームをインターセプトするために、単純な [〜#〜] owin [〜#〜] ミドルウェアを記述しようとしています。私がやろうとしていることは、元のストリームをカスタムのストリームベースのクラスに置き換えることです。この場合、応答ストリームへの書き込みをインターセプトできるようになります。

ただし、チェーン内のミドルウェアコンポーネントによって応答が完全に書き込まれるタイミングがわからないため、いくつかの問題に直面しています。ストリームのDisposeオーバーライドは呼び出されません。そのため、応答ストリームの最後に発生するはずの、処理を実行するタイミングがわかりません。

これがサンプルコードです:

public sealed class CustomMiddleware: OwinMiddleware
{
    public CustomMiddleware(OwinMiddleware next)
        : base(next)
    {
    }

    public override async Task Invoke(IOwinContext context)
    {
        var request = context.Request;
        var response = context.Response;

        // capture response stream

        var vr = new MemoryStream();
        var responseStream = new ResponseStream(vr, response.Body);

        response.OnSendingHeaders(state =>
        {
            var resp = (state as IOwinContext).Response;
            var contentLength = resp.Headers.ContentLength;

            // contentLength == null for Chunked responses

        }, context);

        // invoke the next middleware in the pipeline

        await Next.Invoke(context);
    }
}

public sealed class ResponseStream : Stream
{
    private readonly Stream stream_; // MemoryStream
    private readonly Stream output_; // Owin response
    private long writtenBytes_ = 0L;

    public ResponseStream(Stream stream, Stream output)
    {
        stream_ = stream;
        output_ = output;
    }

    ... // System.IO.Stream implementation

    public override void Write(byte[] buffer, int offset, int count)
    {
        // capture writes to the response stream in our local stream
        stream_.Write(buffer, offset, count);

        // write to the real output stream
        output_.Write(buffer, offset, count);

        // update the number of bytes written

        writtenBytes_ += count;

        // how do we know the response is complete ?
        // we could check that the number of bytes written
        // is equal to the content length, but content length
        // is not available for Chunked responses.
    }

    protected override void Dispose(bool disposing)
    {
        // we could perform our processing
        // when the stream is disposed of.
        // however, this method is never called by
        // the OWIN/Katana infrastructure.
    }
}

上記のコードのコメントで触れたように、応答が完了したかどうかを検出するために考えられる戦略が2つあります。

a)応答ストリームに書き込まれたバイト数を記録し、それを予想される応答の長さに関連付けることができます。ただし、チャンク転送エンコーディングを使用する応答の場合、長さはわかりません。

b)Disposeが応答ストリームで呼び出されると、応答ストリームが完了したと判断できます。ただし、OWIN/Katanaインフラストラクチャは、置き換えられたストリームでDisposeを呼び出すことはありません。

基になるHTTPプロトコルの操作が実行可能なアプローチになるかどうかを確認するために Opaque Streaming を調査していますが、KatanaがOpaque Streamingをサポートしているかどうかはわかりません。

私が望むものを達成する方法はありますか?

21
Maxime Labelle

サブクラス化されたストリームが必要になるとは思いませんが、応答を読み取る方法を次に示します。このミドルウェアがOWINパイプラインの最初のミドルウェアであることを確認し、応答を検査する最後のミドルウェアになるようにします。

using AppFunc = Func<IDictionary<string, object>, Task>;

public class CustomMiddleware
{
    private readonly AppFunc next;

    public CustomMiddleware(AppFunc next)
    {
        this.next = next;
    }

    public async Task Invoke(IDictionary<string, object> env)
    {
        IOwinContext context = new OwinContext(env);

        // Buffer the response
        var stream = context.Response.Body;
        var buffer = new MemoryStream();
        context.Response.Body = buffer;

        await this.next(env);

        buffer.Seek(0, SeekOrigin.Begin);
        var reader = new StreamReader(buffer);
        string responseBody = await reader.ReadToEndAsync();

        // Now, you can access response body.
        Debug.WriteLine(responseBody);

        // You need to do this so that the response we buffered
        // is flushed out to the client application.
        buffer.Seek(0, SeekOrigin.Begin);
        await buffer.CopyToAsync(stream);
    }
}

ところで、私が知る限り、OwinMiddlewareはカタナに固有であるため、OwinMiddlewareからの派生は良い方法とは見なされません。ただし、問題とは関係ありません。

37
Badri