Очищаем HTML от лишних знаков

Однажды посмотрев 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:

* BeginRequest
* AuthenticateRequest
* PostAuthenticateRequest
* AuthorizeRequest
* PostAuthorizeRequest
* ResolveRequestCache
* PostResolveRequestCache
* PostMapRequestHandler
* AcquireRequestState
* PostAcquireRequestState
* PreRequestHandlerExecute
* PostRequestHandlerExecute
* ReleaseRequestState
* PostReleaseRequestState
* UpdateRequestCache
* PostUpdateRequestCache
* EndRequest

Подключение обработчиков событий выполняется в методе 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 добавить ссылку на модуль:

<httpModules>
              <
add name="HTTPModule_Clearer"  type="HTMLClearer.HTTPModule_Clearer, HTMLClearer"/>
         </
httpModules>

Общий вид тега <httpModules> взятый из MSDN выглядит так:

<httpModules>
       <
add type="classname,assemblyname" name="modulename"/>
       <
remove name="modulename"/>
       <
clear/>
    </
httpModules>


<add>
Добавляет в приложение класс HttpModule.

Обратите внимание, что в случае сочетания команда-путь, идентичного уже указанному ранее (например, в файле Web.config родительской папки), второй вызов <add> переопределяет предыдущее значение.

<remove>
Удаляет из приложения класс HttpModule.

<clear>
Удаляет из приложения все сопоставления HttpModule.

При использовании данного модуля размер HTML-страничек, отправляемых пользователю, уменьшается примерно на 10%, что не может не сказаться на трафике, как пользователя, так и сервера.

P.S.

Использование HTTP Module позволяет также вносить изменения в отправляемый пользователю ответ (например, если есть необходимость добавить к странице Header или Footer).

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

Автор: Никита Егоров