【问题标题】:How can I safely intercept the Response stream in a custom Owin Middleware如何安全地拦截自定义 Owin 中间件中的响应流
【发布时间】:2014-10-06 10:13:17
【问题描述】:

我正在尝试编写一个简单的OWIN 中间件,以拦截响应流。我想要做的是用自定义的基于 Stream 的类替换原始流,在那里我将能够拦截对响应流的写入。

但是,我遇到了一些问题,因为我不知道响应何时被链中的内部中间件组件完全写入。永远不会调用 Stream 的 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.
    }
}

正如我在上面代码中的 cmets 中提到的那样,我可以想到两种策略来检测响应是否完整。

a) 我可以记录写入响应流的字节数并将其与预期的响应长度相关联。但是,对于使用分块传输编码的响应,长度是未知的。

b) 当在响应流上调用Dispose 时,我可以确定响应流已完成。但是,OWIN/Katana 基础架构从不对替换的流调用 Dispose。

我一直在调查Opaque Streaming,以了解操纵底层 HTTP 协议是否是一种可行的方法,但我似乎不知道 Katana 是否支持不透明流。

有没有办法实现我想要的?

【问题讨论】:

    标签: c# stream owin katana owin-middleware


    【解决方案1】:

    我认为您不需要子类流,但是您可以通过以下方式阅读响应。只需确保此中间件是 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 是特定于 Katana 的。但是,这与您的问题无关。

    【讨论】:

    • 感谢您的回复。关于派生自OwinMiddleware 的观点。但是,在您的回复中,您使用的是 OwinContext,这也是 Katana 特有的。我错过了什么吗?
    • 是的,你是对的。但是我在中间件内部使用OwinContext,这意味着我依赖于Katana。但是,通过不在构造函数或 Invoke 方法签名中使用它,我不会强制其他程序集依赖于 Katana。构建 OWIN 管道的人不需要知道任何关于 OwinMiddleware 的信息。同样,当管道中此之前的中间件调用Invoke
    • 我设法找到了 David Fowler 与此相关的 SO 答案的链接。 stackoverflow.com/a/19613529/1709870
    • 奇怪,答案有道理,很简单,但我根本无法让它工作:stackoverflow.com/questions/29884776/…
    • 为什么不使用Using 处理流?我在大多数中间件 Response\Request Posts 中都看到了这一点。是因为它知道中间件的生命周期很短吗?
    猜你喜欢
    • 2015-01-18
    • 2017-03-13
    • 1970-01-01
    • 1970-01-01
    • 2018-07-14
    • 2022-07-02
    • 1970-01-01
    • 2018-09-27
    相关资源
    最近更新 更多