Однажды посмотрев HTML-документ, генерируемый ASP.NET и содержащий GridView, я заметил, что большое количество символов, содержащихся на странице - пробелы и знаки табуляции. Очевидно, ASP.NET щедро расставлял их, где только можно.
Конечно, избавиться от лишних килобайтов HTML-документа можно, воспользовавшись библиотеками, которые сжимают HTML-документы при помощи различных алгоритмов. Но такой метод заставит сервер сначала сжать генерируемый HTML-документ, а браузеру пользователя придется распаковывать пришедший ему документ.
Я же хочу в своей статье предложить способ как избавиться от различных ненужных символов, которые содержатся на страницах. Этот способ основан на использовании сборки HTTPModule.
Так что же такое HTTPModule? Вот что о нем говорится в библиотеке MSDN:
HttpModule — это сборка, реализующая интерфейс IHttpModule и обрабатывающая события. ASP.NET включает набор сборок HttpModule, которые могут использоваться в приложениях пользователей. Например, SessionStateModule предоставляется ASP.NET для поставки в приложение служб состояния сеанса. Пользовательские обработчики HttpModule могут быть созданы в качестве ответа на событие ASP.NET или событие пользователя.
Общая процедура написания обработчика HttpModule следующая:
* Реализация интерфейса IHttpModule. * Обработка метода Init и регистрация необходимых событий. * Обработка событий. * Реализация (при необходимости) метода Dispose, если требуется выполнить очистку. * Регистрация модуля в файле Web.config.
HTTP Module включается в процесс обработки запроса пользователя после создания объекта HTTP Application и перед созданием HTTP Handle, так что HTTP Module позволяет обработать следующие события объекта HTTP Application:
Подключение обработчиков событий выполняется в методе Init класса HTTPModule.
В своем примере мне необходимо подключить обработчик на 2 события ReleaseRequestState, как раз после того, как была сгенерирована HTML-версия страницы и объект Response готов к отправке пользователю.
/// <summary> /// Подключение обработчиков событий /// </summary> public void Init(HttpApplication context) { //Подключаем обработчик на событие ReleaseRequestState context.ReleaseRequestState += new EventHandler(this.context_Clear); //Подключаем обработчик на событие PreSendRequestHeaders context.PreSendRequestHeaders += new EventHandler(this.context_Clear); //Два обработчика необходимы для совместимости с библиотеками сжатия HTML-документов }
Сам обработчик будет иметь следующий вид:
/// <summary> /// Обработчик события PostRequestHandlerExecute /// </summary> void context_Clear(object sender, EventArgs e) { HttpApplication app = (HttpApplication)sender; //Получение HTTP Application //Получаем имя файла который обрабатывается string realPath = app.Request.Path.Remove(0, app.Request.ApplicationPath.Length + 1); //Проверяем не является ли он ссылкой на ресурс сборки if (realPath == WebResource.axd) return; //Проверяем тип содержимого if (app.Response.ContentType == text/html app.Response.ContentType == text/javascript) //Устанавливаем фильтр-обработчик app.Context.Response.Filter = new HTMLClearer(app.Context.Response.Filter); }
Фильтр-обработчик - это самое главное. Он позволяет изменять содержимое объекта Response. А дополнительные проверки необходимы, чтобы исключить обработку ресурсов сборок и документов, тип которых отличен от text/html и text/javascript (в документах другого типа нет необходимости убирать лишние символы).
Теперь уделим внимание обработчику содержимого Response.
Это класс, являющийся наследником System.IO.Stream. В его реализации нам интересен только один метод - это метод Write:
/// <summary> /// Обрабатываем данные поступающие в Response /// </summary> public override void Write(byte[] buffer, int offset, int count) { //Преобразовываем массив байт в строку string s = System.Text.Encoding.UTF8.GetString(buffer); //Используя регулярные выражения убираем все ненужные символы s = Regex.Replace(s, >(rn){0,10} {0,20}t{0,10}(rn){0,10}t{0,10}(rn){0,10} {0,20}(rn){0,10} {0,20}<, ><, RegexOptions.Compiled); s = Regex.Replace(s, ;(rn){0,10} {0,20}t{0,10}(rn){0,10}t{0,10}, ;, RegexOptions.Compiled); s = Regex.Replace(s, {(rn){0,10} {0,20}t{0,10}(rn){0,10}t{0,10}, {, RegexOptions.Compiled); s = Regex.Replace(s, >(rn){0,10}t{0,10}<, ><, RegexOptions.Compiled); s = Regex.Replace(s, >r{0,10}t{0,10}<, ><, RegexOptions.Compiled); //Получивщуюся строку преобразовываем обратно в byte byte[] outdata = System.Text.Encoding.UTF8.GetBytes(s); //Записываем ее в Response _HTML.Write(outdata, 0, outdata.Length); }
А также конструктор класса:
public HTMLClearer(System.IO.Stream HTML) { _HTML = HTML; }
Для демонстрации примера использования HTTP Module и обработчика содержимого объекта HTTP Response создадим проект Class Library и назовем его - HTMLClearer. В этом проекте следует создать файл HTMLClearer.cs, содержащей следующий текст:
using System; using System.Collections.Generic; using System.Text; using System.Web; using System.Text.RegularExpressions; namespace HTMLClearer { public class HTMLClearer : System.IO.Stream { System.IO.Stream _HTML; public HTMLClearer(System.IO.Stream HTML) { _HTML = HTML; } #region Стандартные методы и свойства public override bool CanRead { get { return false; } } public override bool CanSeek { get { return false; } } public override bool CanWrite { get { return true; } } public override long Length { get { return _HTML.Length; } } public override long Position { get { return _HTML.Position ; } set { _HTML.Position = value; } } public override long Seek(long offset, System.IO.SeekOrigin origin) { return _HTML.Seek(offset, origin); } public override void SetLength(long value) { _HTML.SetLength(value); } public override void Flush() { _HTML.Flush(); } public override int Read(byte[] buffer, int offset, int count) { return _HTML.Read(buffer, offset, count); } #endregion /// <summary> /// Обрабатываем данные поступающие в Response /// </summary> public override void Write(byte[] buffer, int offset, int count) { //Преобразовываем массив байт в строку string s = System.Text.Encoding.UTF8.GetString(buffer); //Используя регулярные выражения убираем все ненужные символы s = Regex.Replace(s, >(rn){0,10} {0,20}t{0,10}(rn){0,10}t{0,10}(rn){0,10} {0,20}(rn){0,10} {0,20}<, ><, RegexOptions.Compiled); s = Regex.Replace(s, ;(rn){0,10} {0,20}t{0,10}(rn){0,10}t{0,10}, ;, RegexOptions.Compiled); s = Regex.Replace(s, {(rn){0,10} {0,20}t{0,10}(rn){0,10}t{0,10}, {, RegexOptions.Compiled); s = Regex.Replace(s, >(rn){0,10}t{0,10}<, ><, RegexOptions.Compiled); s = Regex.Replace(s, >r{0,10}t{0,10}<, ><, RegexOptions.Compiled); //Получивщуюся строку преобразовываем обратно в byte byte[] outdata = System.Text.Encoding.UTF8.GetBytes(s); //Записываем ее в Response _HTML.Write(outdata, 0, outdata.Length); } } public class HTTPModule_Clearer : IHttpModule { #region IHttpModule Members public void Dispose() { } /// <summary> /// Подключение обработчиков событий /// </summary> public void Init(HttpApplication context) { //Подключаем обработчик на событие ReleaseRequestState context.ReleaseRequestState += new EventHandler(this.context_Clear); //Подключаем обработчик на событие PreSendRequestHeaders context.PreSendRequestHeaders += new EventHandler(this.context_Clear); //Два обработчика необходимы для совместимости с библиотеками сжатия HTML-документов } /// <summary> /// Обработчик события PostRequestHandlerExecute /// </summary> void context_Clear(object sender, EventArgs e) { HttpApplication app = (HttpApplication)sender; //Получение HTTP Application string realPath = app.Request.Path.Remove(0, app.Request.ApplicationPath.Length + 1); //Получаем имя файла который обрабатывается if (realPath == WebResource.axd) //Проверяем не является ли он ссылкой на ресурс сборки return; if (app.Response.ContentType == text/html app.Response.ContentType == text/javascript) //Проверяем тип содержимого app.Context.Response.Filter = new HTMLClearer(app.Context.Response.Filter); //Устанавливаем фильтр обработчик } #endregion } }
После всех этих манипуляций компилируем проект, и получившуюся библиотеку через Add Reference подключаем к Веб-сайту.
Теперь нам необходимо подключить HTTP Module к общему потоку обработки запросов. Для этого в файле web.config необходимо сделать некоторые изменения, а именно в раздел system.web добавить ссылку на модуль:
Обратите внимание, что в случае сочетания команда-путь, идентичного уже указанному ранее (например, в файле Web.config родительской папки), второй вызов <add> переопределяет предыдущее значение.
<remove> Удаляет из приложения класс HttpModule.
<clear> Удаляет из приложения все сопоставления HttpModule.
При использовании данного модуля размер HTML-страничек, отправляемых пользователю, уменьшается примерно на 10%, что не может не сказаться на трафике, как пользователя, так и сервера.
P.S.
Использование HTTP Module позволяет также вносить изменения в отправляемый пользователю ответ (например, если есть необходимость добавить к странице Header или Footer).
Также, дописав метод Write, можно сохранять определенные части страницы или страницы целиком в базу данных или в файл.