Как реализовать фильтр HTTP-ответов для одновременной работы со всем контентом, без разбивки на фрагменты - PullRequest
8 голосов
/ 16 декабря 2010

Как уже упоминалось в нескольких других постах (см. Ссылки ниже), я пытаюсь создать фильтры ответов для изменения содержимого, создаваемого другим веб-приложением.

У меня есть базовая логика преобразования строк, которая работает и инкапсулируетсяв фильтры, которые являются производными от общей базы FilterBase.Однако логика должна работать с полным контентом, а не с фрагментами контента.Поэтому мне нужно кэшировать порции по мере их записи и выполнять фильтр после завершения всех записей.

Как показано ниже, я создал новый ResponseFilter, производный от MemoryStream.При записи содержимое кэшируется в другой MemoryStream.На Flush полное содержимое, теперь находящееся в MemoryStream, преобразуется в строку, и логика фильтра включается. Измененное содержимое затем записывается обратно в исходный поток.

Однако при каждом втором запросе (в основномкогда новый фильтр создается поверх предыдущего), выполняется метод Flush предыдущего фильтра.На этом этапе происходит сбой приложения в методе _outputStream.Write (), поскольку _cachedStream пуст.

Порядок событий следующий:

  1. Первый запрос
  2. Вызывается метод записи
  3. Вызывается метод Flush
  4. Вызывается метод Close
  5. Вызывается метод Close
  6. При этомУкажите точку, приложение вернется, и отобразится правильное содержимое.
  7. Второй запрос
  8. Вызов метода Flush
  9. Сбой приложения в _outputStream.Write.ArgumentOutOfRangeException (offset).
  10. Продолжить через сбой (с помощью Visual Studio)
  11. Метод Close вызывается

У меня есть пара вопросов:

  1. Почему дважды вызывается метод Close?
  2. Почему вызывается Flush после вызова метода Closed?
  3. К приведенному ниже пункту Джея Flush может вызываться до того, как поток полностью прочитангде должна находиться логика фильтра?В тесном?В Flush, но с «if close»?
  4. Какова правильная реализация фильтра ответов, который работает со всем контентом сразу?

Примечание :Я испытываю точно такое же поведение (за исключением событий Close), если я не переопределяю метод Close.

public class ResponseFilter : MemoryStream
{
    private readonly Stream _outputStream;
    private MemoryStream _cachedStream = new MemoryStream(1024);

    private readonly FilterBase _filter;

    public ResponseFilter (Stream outputStream, FilterBase filter)
    {
        _outputStream = outputStream;
        _filter = filter;
    }

    // Flush is called on the second, fourth, and so on, page request (second request) with empty content.
    public override void Flush()
    {
        Encoding encoding = HttpContext.Current.Response.ContentEncoding;

        string cachedContent = encoding.GetString(_cachedStream.ToArray());

        // Filter the cached content
        cachedContent = _filter.Filter(cachedContent);

        byte[] buffer = encoding.GetBytes(cachedContent);
        _cachedStream = new MemoryStream();
        _cachedStream.Write(buffer, 0, buffer.Length);

        // Write new content to stream
        _outputStream.Write(_cachedStream.ToArray(), 0, (int)_cachedStream.Length);
        _cachedStream.SetLength(0);

        _outputStream.Flush();
    }

    // Write is called on the first, third, and so on, page request.
    public override void Write(byte[] buffer, int offset, int count)
    {
        // Cache the content.
        _cachedStream.Write(buffer, 0, count);
    }

    public override void Close()
    {
        _outputStream.Close();
    }
}

// Example usage in a custom HTTP Module on the BeginRequest event.
FilterBase transformFilter = new MapServiceJsonResponseFilter();
response.Filter = new ResponseFilter(response.Filter, transformFilter);

Ссылки:

Ответы [ 2 ]

7 голосов
/ 17 декабря 2010

Благодаря совету от Джея о том, что Flush вызывается для инкрементных записей, я смог заставить фильтр работать так, как нужно, выполняя логику фильтрации, только если фильтр закрывается и еще не закрыт. Это гарантирует, что фильтр очищается только один раз, когда поток закрывается. Я сделал это с помощью нескольких простых полей _isClosing и _isClosed, как показано в окончательном коде ниже.

public class ResponseFilter : MemoryStream
{
    private readonly Stream _outputStream;
    private MemoryStream _cachedStream = new MemoryStream(1024);

    private readonly FilterBase _filter;
    private bool _isClosing;
    private bool _isClosed;

    public ResponseFilter (Stream outputStream, FilterBase filter)
    {
        _outputStream = outputStream;
        _filter = filter;
    }

    public override void Flush()
    {
        if (_isClosing && !_isClosed)
        {
            Encoding encoding = HttpContext.Current.Response.ContentEncoding;

            string cachedContent = encoding.GetString(_cachedStream.ToArray());

            // Filter the cached content
            cachedContent = _filter.Filter(cachedContent);

            byte[] buffer = encoding.GetBytes(cachedContent);
            _cachedStream = new MemoryStream();
            _cachedStream.Write(buffer, 0, buffer.Length);

            // Write new content to stream
            _outputStream.Write(_cachedStream.ToArray(), 0, (int)_cachedStream.Length);
            _cachedStream.SetLength(0);

            _outputStream.Flush();
        }
    }

    public override void Write(byte[] buffer, int offset, int count)
    {
        _cachedStream.Write(buffer, 0, count);
    }

    public override void Close()
    {
        _isClosing = true;

        Flush();

        _isClosed = true;
        _isClosing = false;

        _outputStream.Close();
    }
}

Я еще не нашел ответы на другие мои вопросы выше, поэтому я не буду отмечать этот ответ как исключенный в данный момент.

0 голосов
/ 16 декабря 2010

Flush не вызывается явно. Возможно, он вызывается, когда код понимает, что необходим новый объект, или, возможно, в результате финализатора.

Я думаю, что можно вызывать сброс после любой инкрементальной записи, поэтому я не уверен, что вызов сброса в любом случае является адекватной индикацией полного сообщения.

...