CURRENT PROJECTS
loading
CATEGORIES AND POSTS
loading
overset
DEVELOPMENT LOG FOR JIM PALMER
Posted 12/17/2007 in coldfusion


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:
<cfsetting enablecfoutputonly="Yes">

<!--- 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">
This works with J2EE sessions enabled or disabled. With session or client variables turned on or off.
comments
loading
new comment
NAME
EMAIL ME ON UPDATES
EMAIL (hidden)
URL
MESSAGE TAGS ALLOWED: <code> <a> <pre class="code [tab4|tabX|inline|bash]"> <br>
PREVIEW COMMENT
TURING TEST
gravatar