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 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
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
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!
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.
' Declare our variables
' To try and cut down on possible name collisions
' with existing scripts, I've prefixed them w/ caching
' 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.
' 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.
' The rest of the remaining script continues to process.
' *** Begin Functions *************************************
Function PageIsFresh(iTimeInMinutes) ' As Boolean
' 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")
' 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 = TrueElse
bPageIsFresh = FalseEnd 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 = TrueThen
' 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 = FalseElse
' Somewhat pointless since we know it's already
' True, but what the hell.
bPageIsFresh = TrueEnd IfEnd If
' Set the global flag telling us if we need to update
' based on the current "freshness" of the page.
If bPageIsFresh Then
cachingForceRefresh = FalseElse
cachingForceRefresh = TrueEnd If
' Ok... the horrible term of "fresh" is done
' being used now.
PageIsFresh = bPageIsFresh
End FunctionFunction ShowCachedPage() ' None
End FunctionFunction PageIsBeingRefreshed() ' As Boolean
' 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 = FalseElse
' Again somewhat pointless since we know it's
' already True, but again... what the hell.
' Haven't we done this once already? ;)
bRefresh = TrueEnd IfEnd If
' Set the return value
PageIsBeingRefreshed = bRefresh
' 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
strHTML = objXmlHttp.responseText
Set objXmlHttp = Nothing
' Debugging line.
' 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.
Set objFile = NothingSet objFSO = Nothing
' Update our last updated date!
' Wheh... try saying that 3 times quickly. ;)
Application(GetAppVarName()) = Now()
' Wrapper for the way we determine our application
' variable name since this might want to be changed.
Function GetAppVarName() ' As String
' Read in from global var
strTemp = cachingDynamicPageURL
GetAppVarName = ConvertPageNameToVarName(strTemp)
' 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
strTemp = strPageName
strTemp = Replace(strTemp, "/", "~s~")
strTemp = Replace(strTemp, ".", "~p~")
ConvertPageNameToVarName = strTemp
' 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
strTemp = strPageName
strTemp = Replace(strTemp, "~s~", "/")
strTemp = Replace(strTemp, "~p~", ".")
ConvertVarNameToPageName = strTemp
' *** End Functions ***************************************
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.
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
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.
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
That's all I've got for now folks. Here's the code in a
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.