Подтвердить что ты не робот

Как я могу безопасно перехватить поток Response в пользовательской промежуточной среде Owin

Я пытаюсь написать простую OWIN промежуточную версию, чтобы перехватить поток ответов. То, что я пытаюсь сделать, это заменить исходный поток пользовательским классом 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.
    }
}

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

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

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

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

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

4b9b3361

Ответ 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);
    }
}

BTW, насколько мне известно, вывод из OwinMiddleware не считается хорошей практикой, потому что OwinMiddleware специфичен для Катаны. Однако это не имеет никакого отношения к вашей проблеме.

Ответ 2

Я последовал за ответом Бадри. Я могу получить полное тело ответа в промежуточном программном обеспечении, но ответ, полученный клиентом, прерывается случайным образом примерно в 60% случаев. Это почти говорит о том, что await buffer.CopyToAsync(stream); иногда не полностью очищает ответ. Кто-нибудь видел эту проблему?