The Darkside

Shedding light on things and stuff

 
  Home :: Contact :: Syndication  :: Login
  75 Posts :: 0 Stories :: 49 Comments :: 2 Trackbacks

Ads

Archives

Post Categories

Open Source Projects

Other Blogs

This article details a HttpModule that removes white space, certain javascript comments, as well as optimising ASPX post-back javascript. This is useful when trying to save on the bandwidth your blog is using, or just plain and simply trying to decrease load time of your pages. I've also implemented a custom configuration section to allow the consumer to enable only the functionality required - you can look here for information on creating your own custom sections.

Download

PageOptimiser.zip

The Code

The implementation of the HttpModule is quite straight forward - the PageCleaner class implements IHttpModule, and in the init method, I attach an event to the context.PostRequestHandlerExecute

public class PageCleaner : IHttpModule
{
    #region IHttpModule Members
    void IHttpModule.Dispose() { }
 
    void IHttpModule.Init(HttpApplication context)
    {
        context.PostRequestHandlerExecute += new EventHandler(Context_PostRequestHandlerExecute);
    }
    #endregion
 
    private static void Context_PostRequestHandlerExecute(object sender, EventArgs e)
    {
        if (sender is HttpApplication)
        {
            using (var app = sender as HttpApplication)
            {
                if (app.Context.CurrentHandler is Page)
                {
                    app.Response.Filter = new PageCleanFilter(app.Response.Filter);
                }
            }
        }
    }
}

After some validation of the objects, a new filter is assigned to the HttpApplication.Response.Filter object. The filter is of type Stream, and the PageCleanFilter inherits this class.

The crux of the page optimisation happens in the overridden Write method in the PageCleanFilter class, which checks the custom configuration section to see which switches have been enabled, and then calls the helper functions in the class. 

 Expand Code
public override void Write(byte[] buffer, int offset, int count)
       {
           //As noted by Gregory in the comments below this article, the next three lines:
           //  (a) have redundant code, 
           //  (b) have a bug
           //byte[] data = new byte[count];
           //Buffer.BlockCopy(buffer, offset, data, 0, count);
           //string html = Encoding.Default.GetString(buffer);
 
           string html = Encoding.Default.GetString(buffer, offset, count);
 
           if (PageOptimiserConfigSection.Settings.StripJavaScriptComments)
           {
               html = RemoveJScriptComments(html);
           }
 
           if (PageOptimiserConfigSection.Settings.StripWhiteSpace)
           {
               html = RemoveWhiteSpace(html);
               html = RemoveLineBreaks(html);
           }
 
           if (PageOptimiserConfigSection.Settings.SubstituteJavascript)
           {
               //Substitute with the function name
               html = RemoveGetElementId(html);
 
               //And add the function into the page
               string javaScriptString = @"<script type=""text/javascript"">function $(id){return document.getElementById(id);}</script></head>";
               html = html.Replace("</head>", javaScriptString);
           }
 
           if (PageOptimiserConfigSection.Settings.AddStats)
           {
               _SizeBefore += buffer.Length;
               _SizeAfter += html.Length;
               int pos = html.IndexOf("</body>", StringComparison.OrdinalIgnoreCase);
               if (pos != -1)
               {
                   string stats = "<!-- StatDiff=\"" + _SizeBefore.ToString() + "/" + _SizeAfter.ToString() + "\"--></body>";
                   html = html.Replace("</body>", stats);
               }
           }
 
           byte[] outdata = Encoding.Default.GetBytes(html);
           _sink.Write(outdata, 0, outdata.GetLength(0));
       }

Configuring the PageOptimiser for your web application requires a few modifications to your web.config file. Firstly, add the declaration for the custom configuration section. Remember that the configSections elements must be the first element after <configuration> element.
<configSections>
  <section name="PageOptimiserConfigSection" type="Darkside.PageOptimiser.Configuration.PageOptimiserConfigSection, Darkside.PageOptimiser" />
</configSections>

Add in the custom config section after this (not necessary to be directly after, but useful)

<PageOptimiserConfigSection
    stripWhiteSpace="false"
    stripJavaScriptComments="false"
    substituteJavascript="true"
    addStats="true"
      />

And finally, add the HttpModule section in to the config file

<httpModules>
  <add type="Darkside.PageOptimiser.PageCleaner, Darkside.PageOptimiser" name="PageOptimserHttpModule" />
</httpModules>

This must be inside the <system.web> element.

When all the switches in the config section are set to true, you will notice that the average page size will have been reduced significantly. In addition, the final part of the rendered page will have an Html comment inserted along the lines of:

<!-- StatDiff="36779/33991"-->

This is controlled by the addStats switch, and is an indication of the before/after sizes of your rendered page.

posted on Monday, March 03, 2008 9:05 PM

Feedback

# re: Web Page Optmisation using an HttpModule 3/5/2008 5:37 PM Grant M
THANKS!! I've been waiting for this article for a long time :)

In your PageOptimiserFilter class you have a private variable called _javaScriptSubstituted.

This never seems to be used other than when it gets set in the RemoveGetElementId method.

CAn this be removed?

# re: Web Page Optmisation using an HttpModule 3/5/2008 6:05 PM Darksider
Yeah, it can be removed - That was just me being lazy. I've been working on other javascript substitution routines that required a flag to be get/set later on. I removed these to get the article out quicker.

# re: Web Page Optmisation using an HttpModule 3/5/2008 6:25 PM Grant M
lol, forgot what tight deadlines you had :)

So, we should be expecting the update soon then hey?

# re: Web Page Optmisation using an HttpModule 8/27/2008 7:48 PM Gregory
Your code breaks some html it parses. You should change

byte[] data = new byte[count];
Buffer.BlockCopy(buffer, offset, data, 0, count);
string html = Encoding.Default.GetString(buffer);

To

string html = Encoding.Default.GetString(buffer, offset, count);

This avoids allocating redundantly and removes the bug where the last chunk was truncated (since you converted the entire input buffer to a string even though it only contains bytes of data).

# re: Web Page Optmisation using an HttpModule 8/27/2008 7:50 PM Gregory
Typo, the last sentence is meant to be:

This avoids allocating redundantly and removes the bug where the last chunk was truncated (since you converted the entire input buffer to a string even though it only contains count bytes of data).

# re: Web Page Optmisation using an HttpModule 8/31/2008 8:20 AM Darksider
Well spotted (and refactored)! I've made the changes in the code/article

# re: Web Page Optmisation using an HttpModule 9/24/2008 1:59 AM Job Ortiz
Hi,


I would like to know if this Code can be used in MOSS, I tryed to implement it, but it does not work.

Addicionaly when i compiled the code a get and error in line 23 using (var app = sender as HttpApplication)


Error 102 The type or namespace name 'var' could not be found (are you missing a using directive or an assembly reference?) E:\Desarrollo\MOSS\PageOptimiser\PageOptimiser\PageOptimiserModule.cs 23 24 PageOptimiser


Note: in order to compiled, i change "var" for "HttpApplication"


Thanks
Job


# re: Web Page Optmisation using an HttpModule 9/25/2008 10:23 AM Darksider
@Job: var was introduced with the .net framework 3.5 - you sounds like you're using an older version.

Comments have been closed on this topic.