fusiongrokker

Entries for month: December 2007

cf_rewrite

Over the weekend I was struck with the idea that implementing mod_rewrite behavior in ColdFusion should be easy as pie with a few CGI variables and a couple of lines in Application.cfm. Much to my surprise, it was even easier than I thought! Read on for the code and a demo.

Before getting into the code, it's probably best if you understand how url rewriting works in general. This Wikipedia article explains most of what you need to know, and this page from the Apache2 docs explains how mod_rewrite works in Apache -- and more importantly, gives examples.

Update: It's been brought to my attention that the CGI variables will differ depending on whether you're using Apache, IIS, or another server software. It should still be possible and the concepts will be the same, you just may need to adjust the code to fit your environment. My code was developed on CF8 / IIS, and my demo runs on CF5 / IIS.

To accomplish the same thing with CF code, we need 2 pieces of information from the ever-helpful CGI scope: scriptname and pathinfo. Scriptname is everything after the TLD (.com/etc) until the end of the actual script name. Pathinfo contains everything after the TLD, including the query string. The reason we can't just use querystring is that once you convert your URLs to be /in/a/simulated/folder/format, they are not populated into querystring, only into path_info.

So for example, if my URL was http://somesite.com/Vader/isms.cfm?ism=iamyourfather, then CGI.SCRIPTNAME would have the value /Vader/isms.cfm and CGI.PATHINFO would have the value /Vader/isms.cfm?ism=iamyourfather.

For comparison, when using /simulated/folder/format, the URL would be http://somesite.com/Vader/isms.cfm/ism/iamyourfather. In this case, CGI.SCRIPTNAME is still the same: /Vader/isms.cfm, and CGI.PATHINFO is slightly different, but probably what you expect: /Vader/isms.cfm/ism/iamyourfather

Finally, the code!

What we need to do is capture the data from the URL and translate it back into actual URL variables. To do this, simply remove CGI.SCRIPTNAME's value from the beginning of CGI.PATHINFO, as well as what would now be the leading slash; then loop over the remaining value as a slash-delimited list, and assign URL variables with the values from the next list item:

<cfset newQ = right(cgi.path_info, len(cgi.path_info)-len(cgi.script_name)-1) /> <cfif newQ contains "/">     <cfloop from="1" to="#ListLen(newQ, '/')#" step="2" index="e">         <cfset url['#listGetAt(newQ, e, "/")#'] = listGetAt(newQ, e+1, "/") />     </cfloop> </cfif>  

You can see a demo page here, where I've added the above code to my Application.cfm, and the page I'm linking to will dump the URL scope.

This is a fairly simple system of name/value pairs embedded as what I call "simulated folders." It would be fairly simple to setup a more complex system using Regex and your own rules, and this should provide a good base for that. Also note that this system will break if you use an odd number of slashes -- for example if you add a trailing slash to the end, or if you use the format 1/2/3, without a value for the variable name you're creating in position 3.

Update (again)!

It bothered me that the code I provided above will throw errors so easily, so I fixed that. Of course, I was lazy and didn't update this post to reflect that, but now that it's been linked by J.J. Merrick, I suppose it would be prudent.

This new code uses the modulus operator to drop off any cases where a name is provided without a value, and thus doesn't throw an error when there are an odd number of tokens in the url string. I've updated the demo to use this code, so check it out here. Notice that the demo now has 3 tokens (Vader, Rules, Dude), but that no errors are thrown and the "dude" variable isn't created.

<cfset newQ = cgi.path_info/> <cfif not len(cgi.path_info) eq len(cgi.script_name)>     <cfset newQ = right(newQ, len(newQ)-len(cgi.script_name)-1) />     <cfif newQ contains "/">         <cfloop from="1" to="#(listLen(newQ, '/') - (listLen(newQ, '/') mod 2))#" step="2" index="q">             <cfset url['#listGetAt(newQ, q, "/")#'] = listGetAt(newQ, q+1, "/")/>         </cfloop>     </cfif> </cfif>

Posted in ColdFusion December 03 2007