Speed Up The Web Part III

Speed Up The Web Part III

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 at o7th Web Design, 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!

Categories


Let Us Help

Get You Online

Contact Us Today

Important Cookie Information
Our website uses cookies. By continuing to browse the site you are agreeing to our use of cookies. For more details about cookies and their use, please see our Cookie Policy.