问题描述
我正在尝试编写一个简单的 OWIN 中间件,以便拦截响应流.我想要做的是用自定义的基于 Stream 的类替换原始流,在那里我将能够拦截对响应流的写入.
I'm trying to write a simple OWIN Middleware, in order to intercept the response stream. 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 的 Dispose
覆盖.所以我不知道什么时候执行我的处理,这应该发生在响应流的末尾.
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. So I don't know when it's time to perform my processing, which should happen at the end of the response Stream.
这是一个示例代码:
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) 我可以记录写入响应流的字节数,并将其与预期的响应长度相关联.但是,对于使用分块传输编码的响应,长度未知.
a) I can record the number of bytes written to the response stream and correlate that to the expected response length. However, in the case of responses which use the Chunked Transfer Encoding, the length is not known.
b) 当在响应流上调用 Dispose
时,我可以决定响应流是完整的.然而,OWIN/Katana 基础设施从不调用被替换的流上的 Dispose.
b) I can decide that the response stream is complete when Dispose
is called on the response stream. However, the OWIN/Katana infrastructure never calls Dispose on the replaced stream.
我一直在研究 Opaque Streaming 以查看是否操纵底层的 HTTP 协议将是一个可行的方法,但我似乎没有发现 Katana 是否支持 Opaque Streaming.
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.
有没有办法实现我想要的?
Is there a way to achieve what I want ?
推荐答案
我认为您不需要子类流,但这里是您阅读响应的方法.只需确保此中间件是 OWIN 管道中的第一个中间件,以便它是检查响应的最后一个.
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.
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.但是,这与您的问题无关.
BTW, as far as I know, deriving from OwinMiddleware
is not considered a good practice because OwinMiddleware
is specific to Katana. It is however nothing to do with your problem though.
这篇关于如何安全地拦截自定义 Owin 中间件中的响应流的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持!