Compress Web Pages using .NET 2.0 Compression Library

This article explains how to create very simple HttpModule to compress your dynamic content using compression library (System.IO.Compression) available in .NET 2.0?


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.

  • built-in compression filter available in IIS
  • Third party ISAPI filters
  • HttpModule

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.

  • Configuration options available in IIS5 are not flexible and besides that performance is not up to mark.
  • If you want to use output cache along with compression you can save some CPU time by not compressing cached page repeatedly.
  • You site is hosted in shared hosting environment and you are not allowed to install ISAPI filters.

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.

  • gzip which is encoding format produced by the file compression program "gzip", it was designed to be a replacement for compress. Its main advantages over compress are much better compression and freedom from patented algorithms.
  • compress is a UNIX compression program based on the LZC compression method, which is an LZW implementation using variable size pointers as in LZ78.
  • deflate is a lossless data compression algorithm that uses a combination of the LZ77 algorithm and Huffman coding. It was originally defined by Phil Katz for version 2 of his PKZIP archiving tool, and was later specified in RFC 1951.

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.

  • ContentType Property: collection of content types that can be compressed
  • PreferredAlgorithm Property: specifies which algorithm to use if browser supports both gzip and deflate.
  • GetCompressionSettings Method: which returns instance of CompressionSettings class based on configuration settings.
  • CanContentTypeCompressed Method: which takes contentType and returns true is that content type is configured to compress.
  • GetCompressionType Method: which takes value from Accept-Encoding header and returns encoding type based on header value and PreferredAlgorithm.

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:
  • Drop Jigar.Web.HttpCompression.dll in bin directory of you web application.
  • Add entry in your web.config file to register module.
  • Add entry in your web.config file for configuration section handler and then add section for for Compression settings.
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.