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.