
| home | AJAX (8) || C#.NET (7) || Coldfusion Development (16) || DHTML (15) || Flash Development (19) || jQuery (8) || MSSQL (2) || UNIX (10) |
| 12.17.07 | Custom Coldfusion Caching Engine |
CFCACHE is a black box item. It works well for “normal” pages, but it’s one caveat since version 6 of Coldfusion was that it automatically pre-pended the cache file with a <!— URI —> right at the top of the file. This ruins any DOCTYPE declaration in IE and there’s no way around it - at least that I’ve found. If you have a DOCTYPE declaration on a file that’s cached via CFCACHE it will default IE to quirks mode if CFCACHE pushes a cached file to the user.
I’ve tried using the Application.CFC onRequestEnd to parse out comments, but this function is ignored if there is a valid cache file being pushed to the user. It’s impossible to try and parse the stored cache file in the onRequestEnd because the file hasn’t quite been created yet by the time that function has been called. The onRequestStart function is also ignored if there is a valid cache file to be pushed to the user.
This left me with needing to program a custom function to mimic the functionality of the CFCACHE tag, but not add the evil CF comment at the top of every cached file. I also added the ability to cache a page as per specific CLIENT or SESSION variables that might or might not be set (excluding the default variables such as CFID, etc.).
This is version 0.1 - simple a single-file caching mechanism in the current template directory. For example, I saved the code below as cache.cfm and created the actual page I want to cache: index.cfm. The index.cfm contains a simple:
<cfoutput>
#TimeFormat(NOW(), "long")#<BR>
Test text
<!--CACHE-MESSAGE-->
</cfoutput>
And the caching code:
<!--- the actual original requested file - native/absolute path --->
<cfset _original_file = GetDirectoryFromPath(ExpandPath("*.*")) & "index.cfm">
<!--- setup the MD5 HASHed url string with session and client vars in WDDX packets -- first, remove the CFNoCache URL argument --->
<cfset qString = REReplace(CGI.QUERY_STRING,"\&CFNoCache=TRUE","","ALL")>
<cfset qString = REReplace(qString,"CFNoCache=TRUE","","ALL")>
<cfif Len(qString) gt 0>
<cfset qString = "?" & qString>
</cfif>
<!--- append SESSION vars in url encoded wddx packet --->
<cfif IsDefined('SESSION') and not StructIsEmpty(SESSION)>
<!--- remove default SESSION variables --->
<cfset pruneSession = StructCopy(SESSION)>
<cfset StructDelete(pruneSession, "cfid")>
<cfset StructDelete(pruneSession, "cftoken")>
<cfset StructDelete(pruneSession, "sessionid")>
<cfset StructDelete(pruneSession, "urltoken")>
<!--- serialize into simple wddx string --->
<cfwddx action="cfml2wddx" input=#pruneSession# output="urlSession">
<cfif Len(qString) gt 0>
<cfset qString = qString & "&__urlSESSION=" & URLEncodedFormat(urlSession)>
<cfelse>
<cfset qString = "?__urlSESSION=" & URLEncodedFormat(urlSession)>
</cfif>
</cfif>
<!--- append CLIENT vars in url encoded wddx packet --->
<cfif IsDefined('CLIENT') and not StructIsEmpty(CLIENT)>
<!--- remove default CLIENT variables --->
<cfset pruneClient = StructCopy(CLIENT)>
<cfset StructDelete(pruneClient, "cfid")>
<cfset StructDelete(pruneClient, "cftoken")>
<cfset StructDelete(pruneClient, "hitcount")>
<cfset StructDelete(pruneClient, "lastvisit")>
<cfset StructDelete(pruneClient, "timecreated")>
<cfset StructDelete(pruneClient, "urltoken")>
<!--- serialize into simple wddx string --->
<cfwddx action="cfml2wddx" input=#pruneClient# output="urlClient">
<cfif Len(qString) gt 0>
<cfset qString = qString & "&__urlCLIENT=" & URLEncodedFormat(urlClient)>
<cfelse>
<cfset qString = "?__urlCLIENT=" & URLEncodedFormat(urlClient)>
</cfif>
</cfif>
<!--- 80||443 detection for url string --->
<cfif CGI.server_port is "443">
<cfset uriHash = Hash("https://" & HTTP_HOST & CGI.SCRIPT_NAME & qString)>
<cfelse>
<cfset uriHash = Hash("http://" & HTTP_HOST & CGI.SCRIPT_NAME & qString)>
</cfif>
<cfset _cache_file = _original_file & ".cache_#uriHash#.cfm">
<!--- path for storing cached files - MAKE SURE YOU HAVE PERMISSIONS SET CORRECTLY --->
<cfset _cache_dir = GetDirectoryFromPath(ExpandPath("*.*"))>
<!--- the threshhold for cache-refreshing - in terms of seconds --->
<cfset _cache_threshhold = 10>
<cftry>
<!--- check to see if the cached template exists --->
<cfif FileExists(_cache_file)>
<!--- if so validate the timestamp on the cache refresh interval --->
<cfdirectory action="list" directory="#_cache_dir#" name="cacheAge" filter="#GetFileFromPath(_cache_file)#">
<cfif IsDefined('cacheAge.DATELASTMODIFIED') and Len(cacheAge.DATELASTMODIFIED) gt 0 and IsDate(cacheAge.DATELASTMODIFIED) and DateDiff("s",
cacheAge.DATELASTMODIFIED, NOW()) gt _cache_threshhold>
<cfthrow message="cached file is stale, recache it">
<cfelse>
<!--- send the fresh cached file to the user --->
<cfoutput><cfinclude template="#GetFileFromPath(_cache_file)#">#Chr(10)#<!--CACHED--></cfoutput>
<!--- now parse out the dynamic content in the templated cached file --->
<cfset pageContent = getPageContext().getOut().getString()>
<cfset getPageContext().getOut().clearBuffer()>
<cfset pageContent = REReplace(pageContent, "<!--CACHE-MESSAGE-->", "<!--CACHE-MESSAGE:#NOW()#-->", "all" )>
<cfset writeOutput(pageContent)>
<cfset getPageContext().getOut().flush()>
</cfif>
<cfelse>
<cfthrow>
</cfif>
<cfcatch>
<!--- if not, fetch the pure HTML for the specific template piped through the onRequest --->
<cfsavecontent variable="cacheText"><cfinclude template="#GetFileFromPath(_original_file)#"></cfsavecontent>
<!--- use an exclusive lock on the file - no throw becuse we're already in a catch --->
<cflock name="#_cache_file#" timeout=50 throwOnTimeout="no" type="Exclusive">
<!--- write the file - MAKE SURE YOU HAVE PERMISSIONS SET CORRECTLY --->
<cffile action="write" file="#_cache_file#" output="#cacheText#">
</cflock>
<!--- now, output the uncached version to the user --->
<cfoutput>#cacheText#</cfoutput>
<cfoutput>#Chr(10)#<!--NOTCACHED--></cfoutput>
</cfcatch>
</cftry>
<cfsetting enablecfoutputonly="No">
Download this code: jimcache.cfm
This works with J2EE sessions enabled or disabled. With session or client variables turned on or off.
| Javier Julio on 4.4.08 at 10pm |
|
Rejoice! You won’t need your caching engine anymore. I just happened to stop by your blog again earlier today and afterwards I noticed CF8 Updater 1 was released. Looking at the bugs fixed, yours is one of them. Nice eh? :) 70372 - The cfcache tag inserted a comment that invalidated XHTML and XML pages. Do yourself a favor and go get it! |
| Jim Palmer on 4.11.08 at 4pm |
|
Hah - finally! Nice catch on the release notes. Looks like the jazzed up the average execution time to 4x less! Not only that but supposedly fully supporting the 64bit 1.6 JVM as well?! This will be interesting to try different memory options in the jrun.xml and load test the jeebus out of the machine. |
2 Comments