Как можно безопасно перехватить поток ответов в пользовательском промежуточном программном обеспечении 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.
    }
}

Как я упоминал в комментариях из приведенного выше кода, я могу придумать две стратегии, чтобы определить, завершен ли ответ.

а) Я могу записать количество байтов, записанных в поток ответов, и сопоставить их с ожидаемой длиной ответа. Однако в случае ответов, в которых используется кодирование передачи по частям, длина не известна.

б) я могу решить, что поток ответа завершен, когда Dispose вызывается в потоке ответа. Однако инфраструктура OWIN/Katana никогда не вызывает Dispose в замененном потоке.

Я исследовал Opaque Streaming, чтобы увидеть, будет ли возможным использование манипулирования базовым протоколом HTTP, но я не вижу, поддерживает ли Katana Opaque Streaming или нет.

Есть ли способ добиться того, чего я хочу?

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 специфично для катаны. Однако это не имеет ничего общего с вашей проблемой.

Другие вопросы по тегам