devbox@COMPUTEC The Computec development blog

21Jan/100

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.
Share and Enjoy:
  • Print
  • Digg
  • del.icio.us
  • Facebook
  • Google Bookmarks
  • LinkedIn
  • MisterWong.DE
  • Netvibes
  • Reddit
  • Slashdot
  • StumbleUpon
  • Technorati
  • Twitter
  • Yahoo! Bookmarks
  • LinkArena
  • Live
  • MySpace
  • Yahoo! Buzz
  • Yigg
  • blogmarks
  • Faves
  • FriendFeed
  • MisterWong

Comments (0) Trackbacks (0)

No comments yet.


Leave a comment


No trackbacks yet.