When ColdFusion 8 came out, I never really paid any attention to Application.cfc's new OnMissingTemplate() event handler. This method allows you to handle 404 errors at the application level rather than the server level. At a glance, it seems like an awesome new feature until you realize that it has one glaring issue: it only works for non-existing CFM templates. Meaning, that if you request a .GIF or .ZIP file that doesn't exist, the OnMissingTemplate() event handler does not get triggered.
Missing GIF and ZIP files might not seem like a big issue. And, to be honest, I've never used URL rewriting to deal with them (although I've seem some really cool examples that do do this such as with on-the-fly thumbnail creation and screen-scraping prevention). To me, the real reason that the CFM caveat is such a huge limitation is due to the fact that it doesn't work on directories. So, while this may trigger the OnMissingTemplate() event handler:
... this will NOT trigger it:
While both URLs represent non-existing files, only the first specifies a .CFM file where as the second only assumes a default page in the made-up directory. This to me is the deal-breaker. This to me is why I never paid much attention to OnMissingTemplate(). Navigating by directory is such a core and naturally intuitive navigational strategy that I think it renders OnMissingTemplate() useless.
Or is this perhaps maybe not true? Perhaps navigation by directory is not really a good strategy? Perhaps it's not so different than messing with URL parameters or changing file names. After all, if a user changes a file name, we don't assume that it will lead somewhere good; so, why assume that removing the file name altogether from the URL will lead somewhere good?
To be honest, I can't really present a good argument other than that of sheer precedence. People tend to think that if they are viewing a page in a sub-directory and they want to get to the landing page of that sub-directory, they can simply remove the file name. And, in most of the cases, that's absolutely true. In fact, I'd go so far as to argue that this URL architecture strategy is core to both a user-friendly and search engine optimized site.
So, is ColdFusion 8's OnMissingTemplate() event handler useful? Given my rant above, I'd say that it is not. And, sadly enough, I don't think there's anything that can be done about this because the server has no real knowledge of the ColdFusion application until it hits a ColdFusion page (which is bound to the ColdFusion application service). Otherwise it's just serving up files.
Ironically, I didn't start this post with the intent to rant so much; I actually wanted to just give an example of the OnMissingTemplate() usage. The above few paragraphs just sort of came out as I was writing. I think they provide some good food for thought, but let's take a look at the OnMissingTemplate() event handler to see how it works. The real meat of the code is in the Application.cfc:
<cfcomponent output="false" hint="I define the application and event handlers."> <!--- Define application. ---> <cfset this.name = hash( getCurrentTemplatePath() ) /> <cfset this.applicationTimeout = createTimeSpan( 0, 0, 20, 0 ) /> <cffunction name="onRequestStart" access="public" returntype="boolean" output="false" hint="I execute when a request needs to be initialized."> <!--- Define arguments. ---> <cfargument name="template" type="string" required="true" hint="I am the template that the user requested." /> <!--- Define the page settings. ---> <cfsetting requesttimeout="10" showdebugoutput="false" /> <!--- Initialize the FORM scope. ---> <cfset form[ "onRequestStart" ] = true /> <!--- Return true to let the page load. ---> <cfreturn true /> </cffunction> <cffunction name="onRequest" access="public" returntype="void" output="true" hint="I execute the page template."> <!--- Define arguments. ---> <cfargument name="template" type="string" required="true" hint="I am the template that the user requested." /> <!--- Include the index page no matter what. This way, we can have a front-controller based application no matter what URL was requested. ---> <cfinclude template="./index.cfm" /> <!--- Return out. ---> <cfreturn /> </cffunction> <cffunction name="onMissingTemplate" access="public" returntype="boolean" output="true" hint="I execute when a non-existing CFM page was requested."> <!--- Define arguments. ---> <cfargument name="template" type="string" required="true" hint="I am the template that the user requested." /> <!--- Execute the request initialization and processing. These will not be executed implicity for non- existing CFM templates. ---> <cfset this.onRequestStart( arguments.template ) /> <cfset this.onRequest( arguments.template ) /> <!--- If we've made it this far, everything executed normally. Return true to signal to ColdFusion that the event processed successfully (and that the request is complete). ---> <cfreturn true /> </cffunction> <cffunction name="onError" access="public" returntype="void" output="true" hint="I execute when an uncaught error has occurred."> <!--- Define arguments. ---> <cfargument name="exception" type="any" required="true" hint="I am the uncaught exception object." /> <cfargument name="event" type="string" required="false" default="" hint="I am the event in which the error occurred." /> <!--- Output the exception. ---> <h1> Error: </h1> <cfdump var="#arguments.exception#" /> <cfabort /> <!--- Return out. ---> <cfreturn /> </cffunction> </cfcomponent>
As you can see in the above code, the Application.cfc has a new method, OnMissingTemplate(), which, like OnRequest(), takes one argument - the ColdFusion template requested by the user. While there's really very little code to see, there are a few major points to understand:
When a user requests a non-existing CFM page and OnMissingMethod() gets triggered, the OnRequestStart() and OnRequest() event handlers do NOT get implicitly triggered as with a normal page request. That is why we have to manually invoke the two request-based methods from within the OnMissingMethod() event handler. NOTE: OnApplicationStart() and OnSessionStart() will execute if necessary.
The return value of the OnMissingMethod() event handler is very important. If it returns True (or Void), then ColdFusion assumes that the event processed successfully and the page request is considered to be finished. If the method returns False, however, ColdFusion assumes that the event did not process normally and invokes the error handler, OnError(), with a Missing Template error.
The documentation states that: If an error occurs within the onMissingTemplate function, the error handler is not invoked. Therefore, you should use try/catch blocks in your missing template handler and, if the catch block cannot handle the error, it should set the function return value to false so the standard error handler can report the error... this is NOT true. If an error occurs during your OnMissingTemplate() event handler, ColdFusion WILL invoke the OnError() event handler automatically with the appropriate exception object.
Other than the new OnMissingTemplate() method, there's not much going on this Application.cfc; it's simply a front-controller architecture that executes the Index.cfm page no matter which template was requested by the user. To test this, and the underlying behavior of OnMissingTemplate(), I set up a simple Index.cfm page:
<h1> Index.cfm </h1> <!--- Output the name of the file that was actually requested by the user (perhaps not the same as the one currently being executed as the front controller). ---> <h2> <cfoutput> Requested: #getFileFromPath( cgi.script_name )# </cfoutput> </h2> <cfdump var="#form#" label="Form Scope" /> <br /> <cfdump var="#url#" label="Url Scope" /> <br /> <cfdump var="#cgi#" label="CGI Scope" show="cf_template_path, script_name, path_translated, path_info" />
This page just CFDump's out various scopes and file names. When I navigate to this existing front controller:
... we get the following page output:
This is all to be expected. Now, when I navigate to the non-existent sub-directory file:
... we get the following page output:
Notice that in both cases, the URL parameters were successfully copied into the URL scope. Also notice that the CGI scope reflects the values as if both the existing index.cfm and the non-existent subindex.cfm files did, in fact, exist and executed normally. This is actually cool and something that I can really appreciate about ColdFusion 8's new OnMissingTemplate() event handler. This kind of behavior is much more difficult to create with 404 handling (which currently powers my site) and requires some real fenagling.
Sorry that this blog post was kind of all over the place; it definitely started out going in the wrong direction, and then finally got pulled back into the example. But, I don't think it was wasteful - I think the points I covered about the cons of the OnMissingTemplate() event handler are valid and I would love to get people's thoughts on this.
Want to use code from this post? Check out the license.