Building a Simple Page Caching System
Building a Simple Page Caching System

Building a Simple Page Caching System

by John Peterson

Introduction

Not too long ago I wrote an article about the different types of server-side caching you could use to increase performance of selected asp pages. (If you haven't read it yet, you might want to read it now since this article builds upon it.) At the request of our users, I'm now going to cover building a simple system to implement the page caching technique I discussed in that article.

The Concept

The basic premise is to build an include file that you can insert at the top of any asp page and have it cache a static copy of the page that automatically gets refreshed after a given period of time.

In order to accomplish this, we need a few things. The first is a way to retreive a copy of the page to cache. The next is a place to store that page. Then we need a mechinism to determine when to refresh the cache. Finally we need a system to alternate between showing the cached and non-cached versions.

Note: This system would be quite a bit less complex if the cache controlling page and the page to be cached were seperated, but in order to make this operate within the confines of an easy to use include file, this isn't really feasible... so bear with me as the code can get a little confusing.

Before we get to any code... let me walk you through the basic process. A request for the asp page comes in. The first decision is whether or not we can use a previously cached version. If we can then we use ASP 3.0's Server.Transfer command to point the user to it. If we can't then we use the dynamic version. When we use the dynamic version, it means the cache has expired so it only makes sense to update the cache. To do this we need to get a copy of the page. We do this by requesting the page via an http component with a special QueryString that tells the page to give us the dynamic version, but also tells it not to initiate another update. If we didn't we'd end up in an infinite loop of updating the cached version and instead of reducing the load and increasing performance, we'd increase the load and probably bring the server to it's knees so I'm warning you now... be very careful when tinkering with the script!

The Code

Okay, so now that I've given you the quick overview, here's the code. Instead of giving you it piece by piece with a paragraph thrown in here and there, I'm just posting the one big block of code and commenting heavily to try and make things easier to follow and understand.

caching.asp



<%

' Declare our variables

' To try and cut down on possible name collisions

' with existing scripts, I've prefixed them w/ caching

Dim cachingDynamicPageURL

Dim cachingStaticPageURL

Dim cachingForceRefresh



' Get the URLs of the pages.

' I use .htm for the cached version of our .asp files, but

' you could replace it with any extension if you have htm

' files on your server that you're afraid of overwriting.

cachingDynamicPageURL = Request.ServerVariables("URL")

cachingStaticPageURL  = Replace(cachingDynamicPageURL, ".asp", ".htm")



' Here's the basic logic... the implementation is

' mainly contained in the functions which follow:



' This first conditional decides whether or not the

' cached version is recent enough to show.  This is

' where the timeout (in minutes) should be changed

' if needed.

If PageIsFresh(1) Then

	' Simply a wrapper for our Server.Transfer command.

	' Transfers control to the cached page and stops

	' execution of this one.

	ShowCachedPage()

Else

	' If we need to do an update then the request to

	' update can't also initiate an update... so this

	' checks to see if one is already in progress.

	If PageIsBeingRefreshed() Then

		' Does the actual update to the cache file.

		UpdateCachedPage()

	End If

	' The rest of the remaining script continues to process.

End If



' *** Begin Functions *************************************

Function PageIsFresh(iTimeInMinutes) ' As Boolean

	Dim dLastUpdated

	Dim bPageIsFresh



	' Check and see when we last updated... I use a

	' variable based on the page name so we can use

	' this for lots of files on the same server.

	dLastUpdated = Application(GetAppVarName())

	' If the last update variable is blank... set a

	' default value of Jan 1, 2000

	If dLastUpdated = "" Then

		dLastUpdated = CDate("January, 1, 2000")

	End If



	' Compare Now to the last updated date and set

	' the flag indicating if the page has been

	' updated within the given timeframe.

	If DateDiff("n", dLastUpdated, Now()) < iTimeInMinutes Then

		bPageIsFresh = True

	Else

		bPageIsFresh = False

	End If



	' Debugging Line

	'Response.Write bPageIsFresh & "<br />"



	' Override the value we just determined based on

	' the Application var if we find the appropriate QS...

	' which is: ?nocache=true

	If bPageIsFresh = True Then

		' A little difficult to read and understand:

		'bPageIsFresh = _

		'  (Not(LCase(Request.QueryString("nocache")) = "true"))

		

		' Longer simpler version.

		If LCase(Request.QueryString("nocache")) = "true" Then

			bPageIsFresh = False

		Else

			' Somewhat pointless since we know it's already

			' True, but what the hell.

			bPageIsFresh = True

		End If

	End If



	' Set the global flag telling us if we need to update

	' based on the current "freshness" of the page.

	If bPageIsFresh Then

		cachingForceRefresh = False

	Else

		cachingForceRefresh = True

	End If



	' Ok... the horrible term of "fresh" is done

	' being used now.

	PageIsFresh = bPageIsFresh

End Function



Function ShowCachedPage() ' None

	Server.Transfer(cachingStaticPageURL)

End Function



Function PageIsBeingRefreshed() ' As Boolean

	Dim bRefresh

	

	' Default to the value set when we checked if

	' the page was "fresh" enough

	bRefresh = cachingForceRefresh

	

	' If it's true then we need to check and make

	' sure we're not overriding it to false via QS.

	' QS command is: refresh="false"

	If bRefresh Then

		' Again a little difficult to read and understand:

		'bRefresh = _

		'  LCase(Request.QueryString("refresh")) = "true"

		

		' Again a simpler version

		If LCase(Request.QueryString("refresh")) = "false" Then

			bRefresh = False

		Else

			' Again somewhat pointless since we know it's

			' already True, but again... what the hell.

			' Haven't we done this once already?  ;)

			bRefresh = True

		End If

	End If

	

	' Set the return value

	PageIsBeingRefreshed = bRefresh

End Function



' Finally a simple, straight-forward function... sort of!

Function UpdateCachedPage() ' None

	Dim strFullURL, strFullPath

	Dim objXmlHttp, strHTML

	Dim objFSO, objFile



	' Build the URL of our cache refreshing data request.

	' Be SURE to include the nocache and refresh line.

	strFullURL = "http://" & Request.ServerVariables("SERVER_NAME")

	strFullURL = strFullURL & cachingDynamicPageURL

	strFullURL = strFullURL & "?nocache=true&refresh=false"

	

	' Get out full file system path.

	strFullPath = Server.MapPath(cachingStaticPageURL)



	' Send the request and get the result as text.

	' I assume it's good... you might want to check for

	' something it's supposed to contain and abort

	' if it's not there.

	Set objXmlHttp = Server.CreateObject("Msxml2.ServerXMLHTTP")

	objXmlHttp.open "GET", strFullURL, False

	objXmlHttp.send

	strHTML = objXmlHttp.responseText

	Set objXmlHttp = Nothing



	' Debugging line.

	'Response.Write strHTML



	' Take our recently acquired text and write it to a file.

	Set objFSO = Server.CreateObject("Scripting.FileSystemObject")

	Set objFile = objFSO.OpenTextFile(strFullPath, 2, True)



	' I do a replace for illustration in the sample code:

	objFile.WriteLine Replace(strHTML, "Dynamic", "Static")

	

	' Here's the line without it.

	'objFile.WriteLine strHTML



	objFile.Close

	Set objFile = Nothing

	Set objFSO = Nothing



	' Update our last updated date!

	' Wheh... try saying that 3 times quickly.  ;)

	Application.Lock

	Application(GetAppVarName()) = Now()

	Application.Unlock

End Function



' Wrapper for the way we determine our application

' variable name since this might want to be changed.

Function GetAppVarName() ' As String

	Dim strTemp

	

	' Read in from global var

	strTemp = cachingDynamicPageURL

	

	GetAppVarName = ConvertPageNameToVarName(strTemp)

End Function



' Pages can contain characters that variable names can't so I

' built this little function to weed them out.  I can't think

' of any other common file characters that don't work, but if

' you run across any simply update this function.

Function ConvertPageNameToVarName(strPageName) ' As String

	Dim strTemp



	strTemp = strPageName



	strTemp = Replace(strTemp, "/", "~s~")

	strTemp = Replace(strTemp, ".", "~p~")



	ConvertPageNameToVarName = strTemp

End Function



' This can't be expected to always reverse the above, but for

' simple filenames it should work most of the time.  Not that

' I can think of any reason you'd need to do it anyway, but

' just in case it ever comes up... here it is.

Function ConvertVarNameToPageName(strPageName) ' As String

	Dim strTemp



	strTemp = strPageName



	strTemp = Replace(strTemp, "~s~", "/")

	strTemp = Replace(strTemp, "~p~", ".")



	ConvertVarNameToPageName = strTemp

End Function

' *** End Functions ***************************************

%>

Implementation

The code was written to be pretty easy to implement. Simply upload the include file, change the amount of time the caching lasts to an appropriate value for your content, and add a line for the include file to the top of the asp page you want to cache.

Here's a basic sample script that I've included in the zip file.

sample.asp



<%@ Language=VBScript %>

<%

Option Explicit

Response.Buffer = False

%>

<!-- #include file="caching.asp" -->

<html>

<head>

<title>Dynamic Page</title>

</head>

<body>



<h2>Dynamic Page</h2>



<p>Page Rendered: <%= Now() %></p>



</body>

</html>

There are a couple potential problems that might arise during the installation of this code. The first has to do with the NTFS security settings on the caching file. The anonymous internet user will need change (RWD) access to it in order to refresh the cache. The other issue has to do with multiple users refreshing the cache simultaneously. While this shouldn't be too big a deal, it could cause some problems on extremely busy sites so if you're expecting extreme traffic, you'll probably be better off implementing this in seperate pages and devising a little more sophisticated locking mechinism.

Possible Problems

The code uses direct access to global varibales from within functions. I normally frown upon this type of thing, but it was quick and easy and well... I got lazy. So sue me... it's not as pretty as usual, but it works. ;)

Another issue is that I'm still having the same trouble with the ServerXMLHTTP object that I mention in the code in this sample, so until someone gets me a fix, you might have to use XMLHTTP, but be forewarned it really isn't server safe so you'd probably be better off using a 3rd party component or the Internet Transfer Control for the http work... at least for now.

Conclusion

It may not be the fastest or best caching solution around, but like I said in the title, it is a simple asp-only solution that enables you to do page level caching with a minimum of fuss and almost no setup.

That's all I've got for now folks. Here's the code in a zip file so you can download an play with it. Once we solve this ServerXMLHTTP issue, I'll put up a sample so you can play with it, but for now that's all she wrote.

Additional Information:
Server Side Caching Options - The article that led to this one

Related Products:
Post Point Software - Maker of XBuilder and XCache commercial caching software
WebGecko - Maker of Active Page Generator (APGen) commercial caching software

Close    To Top
  • Prev Article-Web Design:
  • Next Article-Web Design:
  • Now: Tutorial for Web and Software Design > Web Design > ASP > Web Design Content
    Photoshop Tutorial
     

    Special Effect

      3D Effect
      Photoshop Articles
    Programming Tutorial
     

    C/C++ Tutorial

      Visual Basic
      C# Tutorial
    Database Tutorial
     

    MySQL Tutorial

      MS SQL Tutorial
      Oracle Tutorial
    Geek Tutorial
     

    Blogging Tutorial

      RSS Tutorial
      Podcasting Tutorial
    Graphic Design Tutorial
      Coreldraw Tutorial
      Illustrator Tutorial
      3D Tutorials
    Webmaster Articles
     

    Domain Service

      Web Hosting
      Site Promotion
    Java Tutorial/ Articles
     

    Java Servlets

      JavaEE Tutorial
     

    JavaBeans Tutorial

    XML Tutorial/ Articles
     

    XML Style

      AJAX Tutorial
      XML Mobile
    Flash Tutorial/ Articles
     

    Flash Video

      Action Script
      Flash Articles
    OS Tutorial/ Articles
      Linux Tutorial
      Symbian Tutorial
      MacOS Tutorial
    Personal Tech
      Hardware Tutorial
      Software Tutorial
      Online Auction