• Microsoft .NET
  • ASP.NET
  • Модификация ответа HTTP (HTTP Response) при помощи фильтров

Модификация ответа HTTP (HTTP Response) при помощи фильтров

ОГЛАВЛЕНИЕ

Когда обозреватель запрашивает ASP.NET-страницу с веб-сервера, движок ASP.NET обрабатывает запрос в несколько шагов, результатом которых  будет разметка, возвращенная запрашиваемому обозревателю для последующего отображения. Этапы данного процесса иногда называют HTTP- конвейером , который может выполнить такие задачи, как идентификация, авторизация, а также заставить запрашиваемую страницу обработать свое содержимое. На одном из последних этапов HTTP-конвейера обработанная разметка передается фильтру ответов (response filter), который (при наличии) имеет возможность исследовать и изменить разметку до того, как она будет возвращена запрашиваемому обозревателю.

Вы можете создать свои собственные response filters при помощи нескольких строк кода и связать их с соответствующим типом страницы (к примеру, ASP.NET-ресурсы, создающие HTML), либо для всех ASP.NET-ресурсов. Если вы используете интегрированный режим IIS 7 , то вы можете заставить ваш фильтр работать с выходными данными любого типа. Данная статья предоставляет обзор фильтров ответа и демонстрирует два фильтра: простой фильтр, который вырезает пробелы для того, чтобы уменьшить размер переданной разметки, и фильтр, который добавляет сообщение об авторских правах в нижней части всех страниц. Вы можете скачать эти два фильтра, а также приложение с примерами в C# и Visual Basic, в конце статьи. Читайте далее, чтобы узнать больше об этом!

Краткий обзор фильтров ответа

Фильтры ответа были добавлены в ASP.NET версии 1.1 и предоставляют разработчикам способ программного исследования и модификации содержимого запрашиваемого ресурса после того, как оно было сгенерировано, но до того, как оно будет отослано к клиенту. Когда запрашивается и обрабатывается страница ASP.NET , выходная информация записывается в экземпляр HttpWriter. Объект HttpWriter получаетзаписываемые данные и периодически записывает содержимое в поток, возвращаемый клиенту. Для улучшения производительности объект HttpWriter производит буферизацию данных и посылает в поток по частям (вместо того, чтобы посылать по одному символу).

Поток - это объект, который  расширяет класс Stream в пространстве имен System.IO. Вкратце, поток функционирует как буфер, где данные могут быть записаны и считаны. Существует множество встроенных в .NET Framework потоков, которые используют различные хранилища в качестве буфера. К примеру, класс MemoryStream является потоком, который хранит свой буфер в памяти, а FileStream - в файле на жестком диске.

Потоки имеют источник входных данных и место выходных данных. Данные получены при помощи метода Read и добавляются к потоку при помощи вызова метода Write. Следующая диаграмма демонстрирует поток, используемый объектом HttpWriter - объект HttpWriter записывает  в поток данные с обрабатываемой страницы , которые потом  считываются и отправляются клиенту.


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


Используя данную технику, вы можете создать специализированный поток (или потоки) , обрабатывающий выходные данные до того, как они будут отосланы финальному потоку и возвращены запрашиваемому клиенту. Такие специализированные потоки называются фильтрами ответов (response filters). Фильтры ответов являются полезным инструментом для пересылки обработанного результата страницы. Они могут быть использованы для компресии ненужных пробелов, добавления стандартной разметки ко всем страницам (например, такой, как оповещение о правах или JavaScript для инструментов, как Google Analytics), переписывания разметки страницы для того, чтобы создавать совместимые с XHTML страницы, а также для того, чтобы применять шифрование (encryption) и компрессию (compression) к данным выходного потока. Далее статья демонстрирует способ создания и использования фильтров ответа и рассматривает два простых примера таких фильтров: один из них удаляет пробелы, а другой добавляет сообщение о правах в нижней части каждой страницы. Полный код данных фильтров ( как в Visual Basic, так и C#) доступен в конце данной статьи.

Построение простого фильтра ответа для удаления ненужных пробелов

Приложение, доступное в конце статьи, включает в себя два примера фильтров ответа. Первый из них , который мы сейчас рассмотрим - фильтр удаления ненужных пробелов (WhitespaceFilter).

Фильтр по удаления пробелов не должен быть использован в реальном приложении

Следующий фильтр был создан для демонстрации способа работы фильтра ответа. Он не должен быть использован в реальных приложениях потому, что он слишком агрессивно удаляет все пробелы. К примеру, код удаляет все символы возврата каретки с обработанных выходных данных страницы. Это может вызвать проблемы если у вас есть JavaScript на странице. Представьте себе следующий код:

<script type="text/javascript">
   // Show a messagebox
   alert('Hello, world!');
</script>

Данный код будет сжат в следующий :

<script type="text/javascript">// Show a messagebox alert('Hello, world!');</script> 

Как вы можете убедиться, комментарий и функция alert теперь находятся в одной строке - в результате, функция alert будет обработана как комментарий. Вы можете спокойно изменить код таким образом, чтобы данная проблема была решена, но будьте осторожны, поскольку существуют другие случаи, когда такие вот недостатки превращают удаление ненужных пробелов в не такое уж и простое задание.

Как уже было упомянуто в данной статье, фильтры ответа это потоки. В качестве результата, при создании фильтра ответа вы должны создать класс, который унаследует класс Stream. Класс Stream является абстрактным классом, который определяет основную функциональность для всех потоков. Если ваш фильтр ответа будет напрямую наследовать данный класс, то вам понадобится реализовать несколько методов. Лучшим решением будет расширение класса MemoryStream , который уже определяет данные стандартные методы. Вот оболочка нашего фильтра WhitespaceFilterVB. (Чтобы увидеть версию данного кода на языке C# , загрузите приложение, предоставленное в конце статьи.)

Imports Microsoft.VisualBasic
Imports System.IO
Imports System.Text
Imports System.Text.RegularExpressions

Public Class WhitespaceFilterVB
   Inherits MemoryStream

   ...
End Class

Как демонстрирует диаграмма, указанная выше, поток в цепи получает входную информацию и затем выводит ее в другой поток. Поскольку фильтр ответа служит в качестве потока цепи, нам необходимо указать фильтру ответа, в какой поток он должен выводить данные. Это можно обработать путем создания частной переменной экземпляра типа Stream (outputStream) и затем, создав конструктор, который принимает Stream в качестве входных данных.

Public Class WhitespaceFilterVB
   Inherits MemoryStream

   Private outputStream As Stream = Nothing

   Public Sub New(ByVal output As Stream)
      outputStream = output
   End Sub

   ...
End Class

Последним шагом будет перегрузка метода Write базового класса. Методу Write передается буфер бита, готовый к отсылке в выходной поток. Из данного метода мы можем исследовать буфер и выполнить любые изменения до отсылки в выходной поток. Имейте в виду, что данный метод Write может быть вызван несколько раз с одной и той же страницы. Вспомните, что объект HttpWriter связывает данные в несколько частей и затем записывает их в выходной поток. Каждый раз, как HttpWriter высылает порцию данных, будет вызван метод Write вашего фильтра ответа

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

 Public Class WhitespaceFilterVB
   Inherits MemoryStream

   ...

   Private tabsRe As New Regex("\t", RegexOptions.Compiled Or RegexOptions.Multiline)
   Private carriageReturnRe As New Regex(">\r\n<", RegexOptions.Compiled Or RegexOptions.Multiline)
   Private carriageReturnSafeRe As New Regex("\r\n", RegexOptions.Compiled Or RegexOptions.Multiline)
   Private multipleSpaces As New Regex(" ", RegexOptions.Compiled Or RegexOptions.Multiline)
   Private spaceBetweenTags As New Regex(">\s<", RegexOptions.Compiled Or RegexOptions.Multiline)

   Public Overrides Sub Write(ByVal buffer As Byte(), ByVal offset As Integer, ByVal count As Integer)
      ' Преобразование содержимого буфера в строку
      Dim contentInBuffer As String = UTF8Encoding.UTF8.GetString(buffer)

      ' Вырезание всех пробелов, удаление всех символов табуляции, замена всех символов возврата каретки пробелом и компрессия множества подряд идущих пробелов

      contentInBuffer = tabsRe.Replace(contentInBuffer, String.Empty)
      contentInBuffer = carriageReturnRe.Replace(contentInBuffer, "><")
      contentInBuffer = carriageReturnSafeRe.Replace(contentInBuffer, " ")

      While (multipleSpaces.IsMatch(contentInBuffer))
         contentInBuffer = multipleSpaces.Replace(contentInBuffer, " ")
      End While

      contentInBuffer = spaceBetweenTags.Replace(contentInBuffer, "><")

      outputStream.Write(UTF8Encoding.UTF8.GetBytes(contentInBuffer), offset, UTF8Encoding.UTF8.GetByteCount(contentInBuffer))
   End Sub
End Class