Improve Application Performance Using HTTP Handler

Introduction

This article explains how to improve application performance using HTTP Handlers and shows it with an example.

To reduce the page load time of the web pages in our application, we can compress content, cache them and then load them on demand. Each time a CSS or a script file is loaded, it takes time for the browser to download the content. That slows down the application until the loading of the CSS or scripts are over. We can use a HTTP Handler to combine the script and CSS files, cache them and then load the compressed content for faster page load in our application. The first time the files are requested, they will be loaded into the cache and then served; each subsequent requests for those files would then be served from the cache only. This would reduce the load time and provide much better responsiveness since the application wouldn't have to wait for the files to be loaded from the disk onto memory and then to the browser's context. Fetching content in this way would improve the application's performance to a considerable extent. We can use a HttpHandler that, when injected into the response stream, would compress the content, cache it and then fetch it to the web page.

Let us start using the Handler

Using this handler is also simple. All we need is a few simple steps outlined below.

1. Include the handler in the web project as in the following:

  1. #region Namespaces  
  2. using System;  
  3. using System.Net;  
  4. using System.IO;  
  5. using System.IO.Compression;  
  6. using System.Text;  
  7. using System.Web;  
  8. #endregion  
  9.   
  10. namespace PerformanceOptimizationDemo  
  11. {  
  12.     /// <summary>  
  13.     /// CompressFiles handler for compressing scripts and style sheets and storing them in the cache.  
  14.     /// </summary>  
  15.     public class CompressFiles : IHttpHandler  
  16.     {  
  17.         #region Data members  
  18.   
  19.         private const bool COMPRESSCONTENT = true;  
  20.         private const string CACHE_KEY_PREFIX = "HttpCompress.";  
  21.         private readonly static TimeSpan CACHE_DURATION = TimeSpan.FromHours(10);  
  22.  
  23.         #endregion  
  24.   
  25.         /// <summary>  
  26.         ///   
  27.         /// </summary>  
  28.         /// <param name="context"></param>  
  29.         public void ProcessRequest(HttpContext context)  
  30.         {  
  31.             HttpRequest request = context.Request;  
  32.   
  33.             // Read setName, contentType and version. All are required. They are  
  34.             // used as cache key  
  35.             string setName = request["s"] ?? string.Empty;  
  36.             string contentType = request["t"] ?? string.Empty;  
  37.             string version = request["v"] ?? string.Empty;  
  38.   
  39.             // Decide if browser supports compressed response  
  40.             bool isCompressed = COMPRESSCONTENT && this.IsGZipEnabled(context.Request);  
  41.   
  42.             // Response is written as UTF8 encoding. If you are using languages like  
  43.             // Arabic, you should change this to proper encoding   
  44.             UTF8Encoding encoding = new UTF8Encoding(false);  
  45.   
  46.             // If the set has already been cached, write the response directly from  
  47.             // cache. Otherwise generate the response and cache it  
  48.             if (!this.WriteBytesFromCache(context, setName, version, isCompressed, contentType))  
  49.             {  
  50.                 using (MemoryStream memoryStream = new MemoryStream(5000))  
  51.                 {  
  52.                     // Decide regular stream or GZipStream based on whether the response  
  53.                     // can be cached or not  
  54.                     using (Stream writer = isCompressed ?  
  55.                         (Stream)(new GZipStream(memoryStream, CompressionMode.Compress)) :  
  56.                         memoryStream)  
  57.                     {  
  58.   
  59.                         // Load the files defined in <appSettings> and process each file  
  60.                         string setDefinition =  
  61.                             System.Configuration.ConfigurationManager.AppSettings[setName] ?? "";  
  62.                         string[] fileNames = setDefinition.Split(new char[] { ',' },  
  63.                             StringSplitOptions.RemoveEmptyEntries);  
  64.   
  65.                         foreach (string fileName in fileNames)  
  66.                         {  
  67.                             byte[] fileBytes = this.GetFileContent(context, fileName.Trim(), encoding);  
  68.                             writer.Write(fileBytes, 0, fileBytes.Length);  
  69.                         }  
  70.   
  71.                         writer.Close();  
  72.                     }  
  73.   
  74.                     // Cache the combined response so that it can be directly written  
  75.                     // in subsequent calls   
  76.                     byte[] responseBytes = memoryStream.ToArray();  
  77.                     context.Cache.Insert(GetKeyFromCache(setName, version, isCompressed),  
  78.                         responseBytes, null, System.Web.Caching.Cache.NoAbsoluteExpiration,  
  79.                         CACHE_DURATION);  
  80.   
  81.                     // Generate the response  
  82.                     this.WriteBytes(responseBytes, context, isCompressed, contentType);  
  83.                 }  
  84.             }  
  85.         }  
  86.  
  87.         #region Private methods  
  88.   
  89.         /// <summary>  
  90.         ///   
  91.         /// </summary>  
  92.         /// <param name="context"></param>  
  93.         /// <param name="virtualPath"></param>  
  94.         /// <param name="encoding"></param>  
  95.         /// <returns></returns>  
  96.         private byte[] GetFileContent(HttpContext context, string virtualPath, Encoding encoding)  
  97.         {  
  98.             if (virtualPath.StartsWith("http://", StringComparison.InvariantCultureIgnoreCase))  
  99.             {  
  100.                 using (WebClient client = new WebClient())  
  101.                 {  
  102.                     return client.DownloadData(virtualPath);  
  103.                 }  
  104.             }  
  105.             else  
  106.             {  
  107.                 string physicalPath = context.Server.MapPath(virtualPath);  
  108.                 byte[] bytes = File.ReadAllBytes(physicalPath);  
  109.                 return bytes;  
  110.             }  
  111.         }  
  112.   
  113.         /// <summary>  
  114.         ///   
  115.         /// </summary>  
  116.         /// <param name="context"></param>  
  117.         /// <param name="setName"></param>  
  118.         /// <param name="version"></param>  
  119.         /// <param name="isCompressed"></param>  
  120.         /// <param name="contentType"></param>  
  121.         /// <returns></returns>  
  122.         private bool WriteBytesFromCache(HttpContext context, string setName, string version,  
  123.             bool isCompressed, string contentType)  
  124.         {  
  125.             byte[] responseBytes = context.Cache[GetKeyFromCache(setName, version, isCompressed)] as byte[];  
  126.   
  127.             if (null == responseBytes || 0 == responseBytes.Length) return false;  
  128.   
  129.             this.WriteBytes(responseBytes, context, isCompressed, contentType);  
  130.             return true;  
  131.         }  
  132.   
  133.         /// <summary>  
  134.         ///   
  135.         /// </summary>  
  136.         /// <param name="bytes"></param>  
  137.         /// <param name="context"></param>  
  138.         /// <param name="isCompressed"></param>  
  139.         /// <param name="contentType"></param>  
  140.         private void WriteBytes(byte[] bytes, HttpContext context,  
  141.             bool isCompressed, string contentType)  
  142.         {  
  143.             HttpResponse response = context.Response;  
  144.   
  145.             response.AppendHeader("Content-Length", bytes.Length.ToString());  
  146.             response.ContentType = contentType;  
  147.   
  148.             if (isCompressed)  
  149.                 response.AppendHeader("Content-Encoding""gzip");  
  150.   
  151.             context.Response.Cache.SetCacheability(HttpCacheability.Public);  
  152.             context.Response.Cache.SetExpires(DateTime.Now.Add(CACHE_DURATION));  
  153.             context.Response.Cache.SetMaxAge(CACHE_DURATION);  
  154.             context.Response.Cache.AppendCacheExtension("must-revalidate, proxy-revalidate");  
  155.   
  156.             response.OutputStream.Write(bytes, 0, bytes.Length);  
  157.             response.Flush();  
  158.         }  
  159.   
  160.         /// <summary>  
  161.         ///   
  162.         /// </summary>  
  163.         /// <param name="request"></param>  
  164.         /// <returns></returns>  
  165.         private bool IsGZipEnabled(HttpRequest request)  
  166.         {  
  167.             string acceptEncoding = request.Headers["Accept-Encoding"];  
  168.             if (!string.IsNullOrEmpty(acceptEncoding) &&  
  169.                  (acceptEncoding.Contains("gzip") || acceptEncoding.Contains("deflate")))  
  170.                 return true;  
  171.             return false;  
  172.         }  
  173.   
  174.         /// <summary>  
  175.         ///   
  176.         /// </summary>  
  177.         /// <param name="setName"></param>  
  178.         /// <param name="version"></param>  
  179.         /// <param name="isCompressed"></param>  
  180.         /// <returns></returns>  
  181.         private string GetKeyFromCache(string setName, string version, bool isCompressed)  
  182.         {  
  183.             return CACHE_KEY_PREFIX + setName + "." + version + "." + isCompressed;  
  184.         }  
  185.  
  186.         #endregion  
  187.   
  188.         /// <summary>  
  189.         /// IsReusable property  
  190.         /// </summary>  
  191.         public bool IsReusable  
  192.         {  
  193.             get  
  194.             {  
  195.                 return true;  
  196.             }  
  197.         }  
  198.     }  
  199. }  
2. Define the files to be compressed and cached in the <appSettings> section of the web.config of the web project as in the following:
  1. <?xml version="1.0"?>  
  2. <configuration>  
  3.     
  4.   <appSettings>  
  5.     <add key="Css_Files" value="~/Styles/Css1.css,~/Styles/Css2.css,~/Styles/jquery.ribbon.css,~/Styles/liteaccordion.css"/>  
  6.     <add key="Script_Files" value="~/Scripts/Js1.js,~/Scripts/Js2.js,~/Scripts/jquery.js"/>  
  7.   </appSettings>  
  8.     
  9.     <system.web>  
  10.         <compilation debug="true" targetFramework="4.0" />  
  11.     </system.web>  
  12. </configuration>  
3. Invoke the handler in the master page or content page as appropriate, as in the following:

 

  1. <%@ Page Language="C#" AutoEventWireup="true" CodeBehind="Demo.aspx.cs" Inherits="PerformanceOptimizationDemo.Demo" %>  
  2.   
  3. <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">  
  4.   
  5. <html xmlns="http://www.w3.org/1999/xhtml">  
  6. <head runat="server">  
  7.     <title>Demo Application</title>  
  8.     <link type="text/css"   
  9.             rel="Stylesheet"   
  10.             href="CompressFiles.ashx?s=Css_Files&t=text/css&v=1" />  
  11. </head>  
  12. <body>  
  13. <script type="text/javascript"   
  14.         src="CompressFiles.ashx?s=Script_Files&t=type/javascript&v=1" >  
  15. </script>  
  16.   
  17.     <form id="form1" runat="server">  
  18.     <div>  
  19.       
  20.     </div>  
  21.     </form>  
  22. </body>  
  23. </html>  

Summary

In this article we have learned how to optimize application Performance using the HTTP Handler. Please find the attached source code.

 


Similar Articles