
ColdFusion

ColdFusion
Introduction
I’ve come up with a rather nice way of saving bandwidth in ColdFusion. To understand it we’ll need to talk about the “ETag” header, the following is a step-by-step guide to how it works.
- The web browser requests a file.
- The server gets it the file ready.
- It then slaps an “ETag” header on it, the tag only changes if the file content changes.
- The server sends this back to the web browser (status code 200).
- The web browser caches the file and makes a note of the ETag.
- The user returns to the same page and the web browser requests the file again.
- This time the web browser spots it has a cached copy with an ETag.
- When the request is made, the browser adds an “If-None-Match” header with the ETag as the value (cgi.HTTP_IF_NONE_MATCH)).
- The server compares the browser etag against the one it would response with…
- If they match, it returns a tiny 304 “Not Modified” response with no content
- The web browser takes notice of this and uses the cached copy instead.
- If they didn’t match, the server responds with a normal 200 code and the full content of the file.
This process is probably happening for all your static files (images, css, javascript etc…) as IIS and Apache will take care of this process for you. You can even watch it in action by using FireFox + FireBug and the “Net” tab set to “All”, goto a site and refresh the page. On subsequent refreshes you should see a lot of 304 responses, which if you were packet sniffing you’d notice how they didn’t contain anything apart from some http headers.
ColdFusion usage
In my example, I didn’t want to change any code in my existing applications. There was too much data coming in from various sources to easily track when something would have changed. My answer was to wait until the very last moment, grab all the output generated and Hash it. This would then provide me with a simply unique code for the ETag that would only change if the content changed, didn’t need any code changes and would provide the goal of saving bandwidth.
<!--- Code would live in onrequestend.cfm / app.cfc method ---> <cfset content = GetPageContext().getCFOutput().getString() /> <cfset hashed = Hash(content) /> <cfheader name="ETag" value='"#hashed#"' /> <cfif StructKeyExists(CGI, 'HTTP_IF_NONE_MATCH') and CGI.HTTP_IF_NONE_MATCH contains hashed> <!--- If the browser supplied us with an <span class="hiddenSpellError" pre="an "-->etag and it matches, return nothing but a 304 ---> <cfcontent reset="yes" /> <cfheader statuscode="304" statustext="Not Modified" /> <cfabort /> <cfelse> <cfheader name="Content-Length" value="#Len(content)#" /> </cfif>
That’s it. We just wait until the end of the request, grab all the output, hash it and use it as an ETag. If the ETag generated matches one provided by the client, clear the buffer and response with a 304 code, saving your bandwidth in the process.
Better ways to implement this
My usage is nowhere near the best way to take advantage of ETags in ColdFusion. If you can keep track of the data used in generating the response, you could do this a lot earlier than the “request end” and save yourself processing power as well. For example, if the response was just a blog post you could use the last update date of the post as the eTag. But if your blog page has data pulled in from other places (navigation, news list, recent comments etc…) you’d have to take them into account as well, before generating your eTag.
Things to watch out for
If you’ve used CFFlush, the output buffer isn’t going to contain everything and the hash in my example wouldn’t be 100% accurate. Also I haven’t tested what happens if you use the CFContent tag, but I think it’d be better to avoid using it if you do. Let me know if you try it out though
.
Contributions
A huge thanks to both Ben Nadel (yet again!) with his exploration of the GetPageContext() function, without which I wouldn’t have figured out how to get hold of the output buffer’s contents. And Peter Coppinger (aka Topper) for creating a small function for doing ETags easily that would be very useful if you’re generating and checking etags earlier than the request end.
Recommendations against using ETags
In the comments below, you’ll notice Henry Ho links to an article by Yahoo that suggests against using ETags. While this is a good valid reason for the situation given, I just wanted to point out that it isn’t applicable to ColdFusion generating the ETag.
The article mentions the fact that if you had a group of servers, each would generate a different etag for the same file / request. However, we’re basing our ETag on an MD5 hash of the ColdFusion template output, which would be the same no matter what server it was executed on. So while you should take notice of Henry Ho’s point about ETags and check your IIS / Apache setups if using multiple servers, you shouldn’t worry about using this as the ETags will be consistent
Example:
<html><body><h1>Page</h1></body></html>
Is always going to have the MD5 of E11E3AC688A1204495A211A12B05429D.
No matter which server in a cluster it comes from.

Oh and I do have a Railo version on the way, just getting confused with the fact that getPageContext().getOut().getString() doesn’t return anything when inside cfsilent… weird
getPageContext().getOut().getString() does not work the same way in Railo as it does in CF. If found that in Railo, you must use:
getPageContext().getOut().toString()
Thanks Greg, I’ll try that out.
Didn’t some people suggest Against using ETags?
I couldn’t remember the reasons, but I thought ETags is not very popular.
Here it is: http://developer.yahoo.com/performance/rules.html#etags
That’s true for letting the web server create the ETags for you, as there’s a risk each server in a cluster would return a different ETag for the same files.
However, think about it, we’re generating our ETag from the contents of the output ColdFusion is generating. The content won’t be different from server to server so our hash won’t be either. If we each ran the same CF template of 100 different servers, they should all return the same content and all result in the same hash (MD5).
Also, there is a fix for IIS that’ll stop each server generating a different etag to get around the issue mentioned on that Yahoo page.
A very interesting post, to be sure. ETag is something I have not looked into at all yet, although I know it is somewhere in the YSlow performance suggestions.
nice and clean solution, thanks!
This is a okay solution, however ColdFusion strongly suggests not using the OnRequestEnd function on applications that use cfAjaxProxy. Be forewarned. http://help.adobe.com/en_US/ColdFusion/9.0/Developing/WSc3ff6d0ea77859461172e0811cbec10e40-7fdd.html