Posted: June 1, 2012
Speed Up The Web Part III
Boy oh boy, it’s about time we go another one of these “Speed Up The Web” articles out here.
As with our last article we showed you how you can develop your ASP.Net website and automatically concatenate your scripts and stylesheets. This article will show you a nice and easy caching mechanism for all your static content. This of course, assumes you do not have use of a CDN (Content Delivery Network).
Now here we have built ourselves a CDN, and for the purposes of this article, we are going to show you how we achieved the caching, and gzipping utilized throughout it.
First and foremost, let’s get the obvious out of the way first:
- What Is Caching?
- What Is GZipping?
What Is Caching?
Caching is the temporary storage of frequently accessed data in higher speed media (typically SRAM or RAM) for more efficient retrieval. Web caching stores frequently used objects closer to the client through browser, proxy, or server caches. By storing “fresh” objects closer to your users, you avoid round trips to the origin server, reducing bandwidth consumption, server load, and most importantly, latency.
Caching is not just for static sites, even dynamic sites can benefit from caching. Graphics and multimedia typically don’t change as frequently as (X)HTML files. Graphics that seldom change like logos, headers, and navigation can be given longer expiration times while resources that change more frequently like XHTML and XML files can be given shorter expiration times. By designing your site with caching in mind, you can target different classes of resources to give them different expiration times with only a few lines of code.
Client Caching
Client caching of web sites refers to the storage of CSS, Scripts, Images, HTML, etc on the computer of the person browsing the website. Â For the most part this is an extremely efficient method for getting your site to load really fast, by loading these files from their local computer instead of having to download them from the server again.
What happens if the content on the page changes? Â Well, other bits of data are sent along the path of the request to the web page you are visiting. Â These are called headers, and certain headers can specify if the page, or resource has been changed recently, or even set expiration dates/times for said content. Â This will allow client web browsers to download updated versions as they happen.
Proxy Caching
Web proxy caching enables you to store copies of frequently-accessed web objects (such as documents, images, and articles) off server, and then serve this information to users on demand. It improves server performance and frees up network bandwidth to the server for other tasks.
Server Caching
Server caching directly refers to the storage of static/dynamic data in the server memory or hard disk, rather than in the clients web browser’s internal cache. Â This usually is just an internal method to cache data for a web application, for example, data is pulled from a database, if this data is unchanged, then there is no need to pull it again, so we store it in the server cache and simply pull it from there.
What Is GZipping?
Gzipping refers to the method of compressing and un-compressing static content sent to the client’s web browser. Â Files like minified javascripts, minified css stylesheets, some fonts all benefit greatly from this. Â By making the files to send smaller, there is less network traffic when a browser requests a web page.
Now that we got all the nitty gritty details out of the way, how about some code for how we do it?  This is by no means the 100% way to do it, but it is what we have found that works the best for us, and our applications (and I am sure there could be improvements).  Especially our CDN 🙂
This is all done in .Net, and really only consists of a couple classes in our CA. Â The main work is done in our web.config file. Â So, without any further ado… Â here’s the code:
Web.Config
<?xml version="1.0"?> <configuration> <system.runtime.caching> <memoryCache> <namedCaches> <add name="default" cacheMemoryLimitMegabytes="0" physicalMemoryLimitPercentage="25" pollingInterval="00:02:00"/> </namedCaches> </memoryCache> </system.runtime.caching> <system.web> <pages validateRequest="false" buffer="true"/> <httpRuntime executionTimeout="360" maxRequestLength="102400" useFullyQualifiedRedirectUrl="false" minFreeThreads="8" minLocalRequestFreeThreads="4" appRequestQueueLimit="100" requestValidationMode="2.0" enableKernelOutputCache="false"/> <customErrors mode="Off"/> <compilation debug="false" strict="false" explicit="true" targetFramework="4.0"/> <httpHandlers> <add verb="GET" path="*.*" validate="false" type="CA.ProcessAsync"/> </httpHandlers> </system.web> <system.webServer> <validation validateIntegratedModeConfiguration="false"/> <modules runAllManagedModulesForAllRequests="true"/> <handlers> <add name="all" verb="GET" path="*.*" type="CA.ProcessAsync" preCondition="integratedMode"/> </handlers> </system.webServer> </configuration>
ProcessAsync.vb
Imports System.Threading.Tasks Imports System.Web Public Class ProcessAsync Implements IHttpAsyncHandler Private Sub DoIt(ByVal context As HttpContext) Try Dim _file As String, _name As String, _ext As String _file = context.Server.MapPath(context.Request.FilePath.Replace(".ashx", "")) _name = _file.Substring(_file.LastIndexOf("") + 1) _ext = _file.Substring(_file.LastIndexOf(".") + 1) With context.Response If _ext.Equals("aspx") Then Dim handler As Web.IHttpHandler = UI.PageParser.GetCompiledPageInstance(context.Request.Path, context.Request.PhysicalPath, context) context.Server.Transfer(handler, True) Else .ClearHeaders() .AddHeader("Powered By", "o7t.In ~ o7th Web Design") Parallel.Invoke(Sub() Select Case _ext Case "png", "jpg", "jpe", "jpeg", "tif", "tiff", "gif", "bmp", "cur", "ico", "pdf", "swf", "woff" Case Else Common.GZipOutput(context) End Select End Sub, Sub() Common.SetBrowserCache(context) End Sub, Sub() .ContentType = Common.SetContentType(_ext) End Sub, Sub() .AddHeader("Content-Disposition", "inline; filename=" & _name) End Sub) .WriteFile(_file) End If End With Catch ex As Exception End Try End Sub Private _Delegate As AsyncProcessorDelegate Protected Delegate Sub AsyncProcessorDelegate(context As HttpContext) Public Function BeginProcessRequest(ByVal context As HttpContext, ByVal cb As AsyncCallback, ByVal extraData As Object) As IAsyncResult Implements IHttpAsyncHandler.BeginProcessRequest _Delegate = New AsyncProcessorDelegate(AddressOf ProcessRequest) Return _Delegate.BeginInvoke(context, cb, extraData) End Function Public Sub EndProcessRequest(ByVal result As IAsyncResult) Implements IHttpAsyncHandler.EndProcessRequest _Delegate.EndInvoke(result) End Sub Public ReadOnly Property IsReusable() As Boolean Implements IHttpHandler.IsReusable Get Return True End Get End Property Public Sub ProcessRequest(ByVal context As HttpContext) Implements IHttpHandler.ProcessRequest DoIt(context) End Sub End Class
Common.vb
Public Class Common Public Shared Sub GZipOutput(ByVal context As Web.HttpContext) With context If IsGZipSupported(context) Then Dim accEncoding As String accEncoding = .Request.Headers("Accept-Encoding") If accEncoding.Contains("gzip") Then .Response.Filter = New System.IO.Compression.GZipStream(.Response.Filter, System.IO.Compression.CompressionMode.Compress) .Response.AppendHeader("Content-Encoding", "gzip") Else .Response.Filter = New System.IO.Compression.DeflateStream(.Response.Filter, System.IO.Compression.CompressionMode.Compress) .Response.AppendHeader("Content-Encoding", "deflate") End If End If End With End Sub Public Shared Sub SetBrowserCache(ByVal context As Web.HttpContext) With context.Response .AddHeader("Cache-Control", "store, cache") .AddHeader("Pragma", "cache") .AddHeader("Cache-Control", "max-age=21600") .AddHeader("ETag", Date.Now.Ticks) .AddHeader("Expires", DateTime.Now.AddYears(1).ToString("ddd, dd MMM yyyy hh:mm:ss") + " GMT") .AddHeader("Vary", "Accept-Encoding") .Cache.SetVaryByCustom("Accept-Encoding") .Cache.SetOmitVaryStar(True) .Cache.VaryByParams.IgnoreParams = True .Cache.SetAllowResponseInBrowserHistory(True) .Cache.SetCacheability(Web.HttpCacheability.Public) .Cache.SetValidUntilExpires(True) .Cache.SetLastModified(DateTime.Now.AddYears(-1).ToString("ddd, dd MMM yyyy hh:mm:ss") + " GMT") .CacheControl = "public" ' .Expires = 24 * 60 * 366 .ExpiresAbsolute = DateTime.Now.AddYears(1).ToString("ddd, dd MMM yyyy hh:mm:ss") + " GMT" End With End Sub Public Shared Function SetContentType(ByVal _Ext As String) As String Select Case _Ext Case "pdf" Return "application/pdf" Case "rdf" Return "application/rdf+xml" Case "svg" Return "image/svg+xml" Case "tif", "tiff" Return "image/tiff" Case "swf" Return "application/x-shockwave-flash" Case "png" Return "image/png" Case "jpg", "jpe", "jpeg" Return "image/jpg" Case "gif" Return "image/gif" Case "bmp" Return "image/bmp" Case "css" Return "text/css" Case "js" Return "text/js" Case "atom" Return "application/atom+xml" Case "xml", "dtd", "xls", "config" Return "application/xml" Case "txt", "asc" Return "text/plain" Case "htm", "html", "shtml" Return "text/html" Case "xhtml", "xht" Return "application/xhtml+xml" Case "cur" Return "image/x-win-bitmap" Case "ico" Return "image/vnd.microsoft.icon" Case "eot" Return "application/vnd.ms-fontobject" Case "woff" Return "application/x-font-woff" Case "otf", "ttf" Return "application/octet-stream" Case Else Return "text/html" End Select End Function Private Shared Function IsGZipSupported(ByVal _Context As Web.HttpContext) As Boolean Dim accEncoding As String = _Context.Request.Headers("Accept-Encoding") If (Not accEncoding Is Nothing) Then If (accEncoding.Contains("gzip") Or accEncoding.Contains("deflate")) Then Return True Else Return False End If Else Return False End If End Function End Class
Enjoy folks, and thanks for reading!