Using client-side caching in ColdFusion
The cheapest request you can think of is the one where you actually don't need to provide an answer - because you have answered it before and you know that facts haven't changed. Now server-side caching is one approach, but in that case you just save yourselves the actual computing, you still have to retrieve your answer from the cache and deliver it to the client. Client-side caching is much more elegant.
In this case we need to deal with the modified-headers. A HTTP reply may carry a header that tells the client the time the requested resource was last modified. The client may then decide to send this same info along in the headers with its next request, allowing the server to look up if the resource has a new modified date and only deliver a full reply, if there's actually something new to tell - otherwise a simple "Not Modified" in the reply-header will be sufficient to tell the client that he can use the answer he still has in his cache.
Let's look at the headers for the first request to a resource http://your.host.com/some/path/browsercaching/foo.cfm. The client request has nothing unusual:
GET /spielwiese/wollny/browsercaching/foo.cfm HTTP/1.1 Host: bluebox.computec.de User-Agent: Mozilla/5.0 (Windows; U; Windows NT 5.1; de; rv:1.9.1.7) Gecko/20091221 Firefox/3.5.7 (.NET CLR 3.5.30729) Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8 Accept-Language: de-de,de;q=0.8,en-us;q=0.5,en;q=0.3 Accept-Encoding: gzip,deflate Accept-Charset: ISO-8859-1,utf-8;q=0.7,*;q=0.7 Keep-Alive: 300 Connection: keep-alive Cookie: some=value; someother=othervalue; Pragma: no-cache Cache-Control: no-cache
The reply however carries three headers that tell the client a) when that resource was last modified (Last-Modified) and how long it should possibly be cached (Cache-Control, Expires):
HTTP/1.x 200 OK Date: Thu, 21 Jan 2010 13:31:21 GMT Server: Apache/2.2.3 (Debian) PHP/5.2.0-9~computec+1 proxy_html/2.5 mod_ssl/2.2.3 OpenSSL/0.9.8c JRun/4.0 Last-Modified: Thu, 21 Jan 2010 13:36:13 GMT Expires: Sat, 20 Feb 2010 13:31:21 GMT Cache-Control: max-age=2592000 Vary: Accept-Encoding Content-Encoding: gzip Connection: close Transfer-Encoding: chunked Content-Type: text/html; charset=UTF-8
Now if the client supports caching, it will include an If-Modified-Since header in its next request to the same resource:
GET /spielwiese/wollny/browsercaching/foo.cfm HTTP/1.1 Host: bluebox.computec.de User-Agent: Mozilla/5.0 (Windows; U; Windows NT 5.1; de; rv:1.9.1.7) Gecko/20091221 Firefox/3.5.7 (.NET CLR 3.5.30729) Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8 Accept-Language: de-de,de;q=0.8,en-us;q=0.5,en;q=0.3 Accept-Encoding: gzip,deflate Accept-Charset: ISO-8859-1,utf-8;q=0.7,*;q=0.7 Keep-Alive: 300 Connection: keep-alive Cookie: some=value; someother=othervalue; If-Modified-Since: Thu, 21 Jan 2010 13:36:13 GMT Cache-Control: max-age=0
Now all we'd need to do is read this header, determine the current last modified date for the resource, compare those two and if there's nothing new, we simply reply with a 304 'Not Modified" status and skip the rest of the page processing, so there's no reply payload. Our reply header would look like this:
HTTP/1.x 304 Not Modified Date: Thu, 21 Jan 2010 13:31:26 GMT Server: Apache/2.2.3 (Debian) PHP/5.2.0-9~computec+1 proxy_html/2.5 mod_ssl/2.2.3 OpenSSL/0.9.8c JRun/4.0 Connection: close Expires: Sat, 20 Feb 2010 13:31:26 GMT Cache-Control: max-age=2592000 Vary: Accept-Encoding
You can use this customtag to send and check the appropriate headers and control the actual delivery of the reply (store as browsercaching.cfm):
<cfparam name="dtLastModified" type="date" default="#now()#"> <cfparam name="cacheTimeSeconds" type="integer" default="2592000"> <cfset variables.strPageModified = GetHttpTimeString(ATTRIBUTES.dtLastModified)> <!--- now set default expires and cache control headers ---> <cfheader name="Expires" value="#GetHttpTimeString(DateAdd('s', ATTRIBUTES.cacheTimeSeconds, Now()))#"> <cfheader name="Cache-Control" value="max-age=#ATTRIBUTES.cacheTimeSeconds#"> <cftry> <!--- if there is an If-Modified-Since header in the request and the time is the same as the last modified date, send a 304 and abort---> <cfif StructKeyExists(GetHttpRequestData().headers, "If-Modified-Since")> <cfset variables.ifmodDate = ParseDateTime(GetHttpRequestData().headers["If-Modified-Since"])> <cfif ParseDateTime(GetHttpTimeString(ATTRIBUTES.dtLastModified)) eq variables.ifmodDate> <cfheader statuscode="304" statustext="Not Modified"> <cfabort> </cfif> </cfif> <cfcatch type="any"></cfcatch> </cftry> <!--- send the Last-Modified header---> <cfheader name="Last-Modified" value="#variables.strPageModified#">
In your code you'd simply have to somehow determine what's your last modified date (in this simple case I use the modified date of the CFML template itself) and pass that along with the cache time in seconds to the cf_browsercaching-Customtag like this (foo.cfm):
<cfset variables.structThisFileInfo = GetFileInfo(CGI.PATH_TRANSLATED)> <cf_browsercaching dtLastModified="#variables.structThisFileInfo.lastmodified#" cacheTimeSeconds="2592000"> Here you'd actually start with computing and outputting your reply if the request gets this far.