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.
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 ;).
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
Is always going to have the MD5 of E11E3AC688A1204495A211A12B05429D.
No matter which server in a cluster it comes from.