Introduction:

One of the advantages of using HTTP compression on a web server is that it reduces the amount of bandwidth required to serve web pages, compression can dramatically increase speed and quantity of content that your server can serve.

Article explains how you can create very simple HttpModule to compress your dynamic content using compression library (System.IO.Compression) available in .net 2.0.

What are the different options available for compression?

There are three major ways to compress your web pages.

You can use the IIS built-in compression filter especially if you are using IIS6. But there are some cases where you will find HttpModule more useful and efficient for example.

Different type of compression encoding that are registered with IANA:

The Internet Assigned Numbers Authority (IANA) acts as a registry for content-coding value tokens, registry contains following tokens.

We will ignore compress because gzip and deflate will cover majority of browsers.

For gzip compression we will use System.IO.Compression.GZipStream and for deflate we will use System.IO.Compression.GZipStream

How to know if browser accepts compression?

A compression-aware browser tells servers that it would prefer to receive encoded content with a message in the HTTP header like following.

Accept-Encoding: gzip,deflate

Which means that browser can accept gzip and deflate encoding.

We will create CompressionSettings class which will encapsulate settings and preferences that we need for compression, class will have following properties and methods.

Following is CompressionSettings.cs:

public class CompressionSettings

{

private string preferredAlgorithm = "gzip";

private StringCollection contentTypes = new StringCollection();

public StringCollection ContentTypes

{

get { return contentTypes; }

set { contentTypes = value; }

}

public string PreferredAlgorithm

{

get { return preferredAlgorithm; }

set { preferredAlgorithm = value; }

}

public CompressionSettings()

{

ContentTypes.Add("text/html");

ContentTypes.Add("text/plain");

}

public static CompressionSettings GetCompressionSettings()

{

if (HttpContext.Current.Cache["CompressionSettings"] == null)

{

CompressionSettings compressionSettings = new CompressionSettings();

if (ConfigurationManager.GetSection("CompressionSettings") != null)

{

compressionSettings = (CompressionSettings)ConfigurationManager.GetSection

("CompressionSettings");

}

HttpContext.Current.Cache.Insert("CompressionSettings",compressionSettings);

}

return (CompressionSettings)HttpContext.Current.Cache["CompressionSettings"];

}

public bool IsContentTypeCompressed(string contentType)

{

foreach (string s in contentTypes)

{

if (String.Compare(s, contentType, true) == 0)

{

return true;

}

}

return false;

}

public String GetCompressionType(string acceptEncoding)

{

bool foundDeflate = false;

bool foundGZip = false;

string[] schemes = acceptEncoding.Split(',');

for (int i = 0; i < schemes.Length; i++)

{

string acceptEncodingValue = schemes[i].Trim().ToLower();

if (acceptEncodingValue.StartsWith("deflate")&& canAcceptQuality(acceptEncodingValue))

{

foundDeflate = true;

}

else if ((acceptEncodingValue.StartsWith("gzip")|| acceptEncodingValue.StartsWith("x-gzip"))
&& canAcceptQuality(acceptEncodingValue))

{

foundGZip = true;

}

else if (acceptEncodingValue.StartsWith("*") && canAcceptQuality(acceptEncodingValue))

{

foundGZip = true;

foundDeflate = false;

}

}

if (preferredAlgorithm == "gzip" && foundGZip)

{

return "gzip";

}

if (preferredAlgorithm == "deflate" && foundDeflate)

{

return "deflate";

}

if (foundDeflate) return "deflate";

if (foundGZip) return "gzip";

return "none";

}

/***********************************************

* System.IO.Compression does not support compression

* ratio so we will just check header to see if specified

* compression level is above zero.

* ********************************************/

bool canAcceptQuality(string acceptEncodingValue)

{

int qParam = acceptEncodingValue.IndexOf("q=");

float val = 1.0f;

if (qParam >= 0)

{

try

{

val = float.Parse(acceptEncodingValue.Substring(qParam + 2,
acceptEncodingValue.Length - (qParam + 2)));

}

catch (FormatException)

{

            }

}

return (val > 0.0f);

}

}

How does server send information to browser that page is compressed?

HTTP/1.1 uses encoding type values in the Content-Encoding header field. It indicates what decoding mechanism will be required to remove the encoding for example.

Content-Encoding: gzip

Means that server has used gzip to encode content and browser will need to use same to remove encoding.

We will create HttpModule to apply compression filter to http response pipeline. Http Modules provided powerful way to extend your ASP.NET applications by adding pre and post-processing to each HTTP request coming into your application.

We will use ReleaseRequestState event to hook up our compression filter and we will use compressionSettings to determine which compression to apply.

Following is HttpModule.cs:

public class CompressionModule : IHttpModule

{

public void Dispose()

{

// nothing to dispose.

}

public void Init(HttpApplication context)

{

context.ReleaseRequestState += new EventHandler(this.CompressContent);

}

void CompressContent(object sender, EventArgs e)

{

HttpApplication context = ((HttpApplication)sender);

if (!context.Context.Items.Contains("jigarcompkey"))

{

CompressionSettings settings = CompressionSettings.GetCompressionSettings();

string acceptedTypes = context.Request.Headers["Accept-Encoding"];

if (settings.IsContentTypeCompressed(context.Response.ContentType) && acceptedTypes !=null)

{

string preferedcompression = settings.GetCompressionType(acceptedTypes);

if (preferedcompression == "gzip")

{

context.Response.AppendHeader("Content-Encoding", "gzip");

context.Response.AppendHeader("X-Compressed-By", "Jigar-HttpCompress");

context.Response.Filter =new GZipStream(context.Response.Filter, CompressionMode.Compress);

}

if (preferedcompression == "deflate")

{

context.Response.AppendHeader("Content-Encoding", "deflate");

context.Response.AppendHeader("X-Compressed-By", "Jigar-HttpCompress");

context.Response.Filter =new DeflateStream(context.Response.Filter, CompressionMode.Compress);

}

}

context.Context.Items.Add("jigarcompkey", "yes");

}

    }
}
 
Use CompressionModule in your web application:
Following is typical web.config file

<?xml version="1.0"?>

<configuration>

<configSections>

<section name="CompressionSettings"type=
"Jigar.Web.HttpCompression.CompressionConfiguration, Jigar.Web.HttpCompression"/>

</configSections>

<appSettings/>

<system.web>

<httpModules>

<add name="CompressionModule"type=
"Jigar.Web.HttpCompression.CompressionModule, Jigar.Web.HttpCompression"/>

</httpModules>

<compilation debug="true"/>

</system.web>

<CompressionSettings PreferredAlgorithm="gzip">

<add>

<ContentTypes Value="text/html"></ContentTypes>

</add>

</CompressionSettings>

</configuration>

Testing Compression:

There are various addons available for firefox which allows you to change request headers. I personally use "TemparData" and "Live Http Headers"

Figure 1 shows result of compression.

Caution while using output cache:

Http compression is applied before output cache so if you are using output cache along with compression then you can get into trouble, for example if your first request accepts deflate compression then server will cache compressed copy and it will serve that copy to all corresponding request untill it remains in cache.

To avoid this problem you can use varybycustom along with outputcache and then set value of varybycustom in global.asax to encoding that you are using.

Source Code:

Attached source code contains compression project and test web project which uses compression module.