简体   繁体   English

如何安全地拦截自定义Owin中间件中的响应流

[英]How can I safely intercept the Response stream in a custom Owin Middleware

I'm trying to write a simple OWIN Middleware, in order to intercept the response stream. 我正在尝试编写一个简单的OWIN中间件,以拦截响应流。 What I'm trying to do is replace the original stream with custom Stream-based class, where I will be able to intercept writes to the response stream. 我要做的是用自定义的基于Stream的类替换原始流,在那里我将能够拦截对响应流的写入。

However, I'm facing some issues because I cannot know when the response has been completely written to by inner middleware components in the chain. 但是,我遇到了一些问题,因为我无法知道响应是否已被链中的内部中间件组件完全写入。 The Dispose override of the Stream is never called. 永远不会调用Stream的Dispose重写。 So I don't know when it's time to perform my processing, which should happen at the end of the response Stream. 所以我不知道什么时候进行我的处理,这应该发生在响应流的末尾。

Here is a sample code: 这是一个示例代码:

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.
    }
}

As I've alluded to in the comments from the code above, there are two strategies that I can think of in order to detect whether the response is complete. 正如我在上面代码的评论中提到的那样,我可以想到两种策略来检测响应是否完整。

a) I can record the number of bytes written to the response stream and correlate that to the expected response length. a)我可以记录写入响应流的字节数,并将其与预期的响应长度相关联。 However, in the case of responses which use the Chunked Transfer Encoding, the length is not known. 但是,在使用Chunked Transfer Encoding的响应的情况下,长度是未知的。

b) I can decide that the response stream is complete when Dispose is called on the response stream. b)在响应流上调用Dispose时,我可以决定响应流是否完整。 However, the OWIN/Katana infrastructure never calls Dispose on the replaced stream. 但是,OWIN / Katana基础结构从不在已替换的流上调用Dispose。

I have been investigating Opaque Streaming in order to see whether manipulating the underlying HTTP protocol would be a feasible approach, but I don't seem to find whether Katana supports Opaque Streaming or not. 我一直在研究Opaque Streaming ,以便了解操纵底层HTTP协议是否是一种可行的方法,但我似乎没有发现Katana是否支持Opaque Streaming。

Is there a way to achieve what I want ? 有没有办法实现我想要的?

I do not think you will need a sub-classed stream but then here is how you can read the response. 我不认为你需要一个子类流,但接下来就是你如何阅读响应。 Just ensure this middleware is the first one in the OWIN pipeline so that it will be the last one to inspect the response. 确保这个中间件是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);
    }
}

BTW, as far as I know, deriving from OwinMiddleware is not considered a good practice because OwinMiddleware is specific to Katana. 据我所知,BTW源自OwinMiddleware并不是一个好习惯,因为OwinMiddleware是专门针对Katana的。 It is however nothing to do with your problem though. 但是,它与你的问题无关。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM