ColdFusion 8's OnMissingTemplate() - So Close To Being Good

Posted July 1, 2009 at 10:23 AM

Tags: ColdFusion

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:

www.mysite.com/made-up-directory/index.cfm

... this will NOT trigger it:

www.mysite.com/made-up-directory/

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:

Application.cfc

 Launch code in new window » Download code as text file »

  • <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:

  1. 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.
  2. 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.
  3. 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:

Index.cfm

 Launch code in new window » Download code as text file »

  • <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:

./index.cfm?test=1

... we get the following page output:

 
 
 
 
 
 
OnMissingTemplate() - Index.cfm Page That Exists. 
 
 
 

This is all to be expected. Now, when I navigate to the non-existent sub-directory file:

./sub/subindex.cfm?test=1

... we get the following page output:

 
 
 
 
 
 
OnMissingTemplate() - SubIndex.cfm Page That Does NOT Exist. 
 
 
 

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.

Download Code Snippet ZIP File

Post Comment  |  Ask Ben  |  Permalink  |  Other Searches  |  Print Page





Reader Comments

Jul 1, 2009 at 10:51 AM // reply »
6 Comments

I haven't tried this for this specific problem but couldn't you use Apache's mod_rewrite to add index.cfm to any URL not containing a cfm?


Jul 1, 2009 at 10:54 AM // reply »
6,516 Comments

@Brian,

You could (I'm not on Apache personally), but my concern is that if you are already doing URL rewriting, you might as well just stick to that - why mix and match. Unless you simply add "index.cfm" to all no-file directories globally and then let the given site developer handle via OnMissingTemplate().


Jul 1, 2009 at 10:56 AM // reply »
13 Comments

Ben nice post... I share your disappointment about folders not being handled. My guess the ColdFusion web server connectors aren't notified of those 404's, so it can't handle them with onMissingTemplate. I think they would have to rewrite the connectors to support this, but I could see how many customers many not want CF to handle all 404s.

This is why I have always used web server url rewriting (eg mod_rewrite), then you can just setup a rule to send any URI you want to a cfm.


Jul 1, 2009 at 10:59 AM // reply »
5 Comments

Hi Ben,

I use a custom 404.cfm file in IIS to get my index.cfm on the end of any URL missing it. Here's an example:

<cfset requestedURI = ListRest(cgi.query_string, ";") />
<!--- strip port --->
<cfset requestedURI = Replace(requestedURI, ":80", "", "ALL") />
<!--- if just asking for folder's default doc, append index.cfm --->
<cfif ListLast(requestedURI) DOES NOT CONTAIN "index.cfm" AND ListFind("gif,jpg,png,pdf,xml,css,.js,htm,tml", Right(requestedURI, 3)) EQ 0>
<cfif requestedURI CONTAINS "/?">
<cfset requestedURI = Replace(requestedURI, "?", "index.cfm?") />
<cfelseif requestedURI CONTAINS "?">
<cfset requestedURI = Replace(requestedURI, "?", "/index.cfm?") />
<cfelseif Right(requestedURI, 1) NEQ "/">
<cfset requestedURI = requestedURI & "/index.cfm" />
<cfelse>
<cfset requestedURI = requestedURI & "index.cfm" />
</cfif>

<cflocation url="#variables.requestedURI#" addtoken="false" statuscode="301" />
<cfabort>
</cfif>
<cfoutput>
<cfheader statuscode="404" statustext="Object Not Found">
<p>#variables.requestedURI#</p>
<p>This document can not be found.</p>
</cfoutput>

This allows me to still make good use of onMissingTemplate throughout my app.


Jul 1, 2009 at 11:04 AM // reply »
6,516 Comments

@Pete,

Agreed - not sure CF should handle all 404s, plus, I'm not sure how easy that would be to code (I know nothing about that aspects of the server world).

@Daniel,

Word up, that's I do it on www.nycfug.com. If you go to a sub-directory of the CFUG site, it will redirect you to ./index.cfm in that same directory. I use a 404 handler in IIS and the OnMissingTemplate(). It's the only place I've ever used it (and I only did so because I got heat for having a CFUG site not visibly powered by CFM pages - a change I would push back on now).


Jul 1, 2009 at 11:57 AM // reply »
18 Comments

I've been using onMissingTemplate() to intercept requested templates and render them inside a layout. It's been working pretty well.

I previously used onRequest(), but ran into issues with webservices. http://livedocs.adobe.com/coldfusion/7/htmldocs/wwhelp/wwhimpl/common/html/wwhelp.htm?context=ColdFusion_Documentation&file=00000698.htm

"Do not implement the onRequest method in any Application.cfc file that affects .cfc files that implement web services, process Flash Remoting or event gateway requests; ColdFusion MX will not execute the requests if you implement this method."


Jul 1, 2009 at 12:28 PM // reply »
15 Comments

Due to how requests work its not really possible for CF to act as a URL rewiriter since those happen before the web server decides where to send the request which is where CF comes to the party.

As with any technology they have advantages and disadvantages including some advantages over standard URL rewriters. I've used them in quite interesting ways to ensure application security.

They really can't do what you are asking for but that doesn't make them bad!


Jul 1, 2009 at 2:13 PM // reply »
39 Comments

Typically, it is nice to have either IIS or Apache do some rewriting of the URL. That way everything does go through index.cfm, which then calls a controller (in the MVC architecture).

This also renders the onMissingTemplate() tag useless, as the template will always be found.

I think that it is more of the job of the router to find out whether the URI is valid or not, not the job of the Application.cfc (or even the server).

I really haven't ever found the need for the tag.


Jul 1, 2009 at 2:33 PM // reply »
6,516 Comments

@Sam,

I am not blaming ColdFusion for not being able to hook into plain directory requests. I don't even think that should be able to happen as ColdFusion doesn't run the whole server (its only a service on top of the server).

That's why I really like Brian's idea of simply using re-write to add index.cfm to any directory. That way, you can get the most out of your CF-specific code with just a bit of configuration.

@Brandon,

I think by doing minimal re-write in Apache / IIS, you can get a lot of power out of the OnMissingTemplate() event handler. After all, I think we want to push as much into the CF layer as it is more easily maintained and more flexible (than the re-write command files).


Jul 1, 2009 at 5:44 PM // reply »
14 Comments

Maybe I missed this (and I don't have the setup handy at the moment to give it a try), but if your default document for a given website is index.cfm (and only index.cfm), then does OnMissingTemplate() get triggered for a url like http://mysite.com/no_such_directory/?


Jul 1, 2009 at 5:51 PM // reply »
18 Comments

@Rich,

No, onMissingTemplate() will only fire if you have a .cfm extension in your url.


Jul 1, 2009 at 5:54 PM // reply »
40 Comments

The most obvious use for onMissingTemplate() is when you actually want to handle a request for a .cfm file that doesn't exist. ColdFusion processes all .cfm requests and so in that case it would leave you with a ColdFusion template not found error page rather than showing the web servers 404 Not Found page.

The only way (I think?) to handle this before CF8 was to set the missing template path in the CF Admin, but this was a global setting not a per-Application setting (which could be problematic if you only run a single instance of CF), hence the need for onMissingTemplate() in Application.cfc. The ability to let you do some processing of the original request and the potential for faux-URL rewriting is nice because it's there if you want to use it but definitely not the main point of the feature :)


Jul 2, 2009 at 6:09 PM // reply »
2 Comments

ColdFusion in the standard install won't do this, because the web server isn't passing the requests into CF. In Apache, the filetypes sent to CF are defined by the config line:

AddHandler jrun-handler .jsp .jws .cfm .cfml .cfc .cfr .cfswf

We have modified this in order to have CF serve other files, specifically .ly and .css files. If you add .gif or any other file type to that line, then OnMissingMethod would in fact pick it up, e.g.:

AddHandler jrun-handler .jsp .jws .cfm .cfml .cfc .cfr .cfswf .gif .jpg

Also look around in the xml files in the multi-server install. I don't have the specific details at hand at the moment, but I know that we had to modify one of the XML config files (maybe the web.xml) to add the filetypes.


Jul 3, 2009 at 9:25 AM // reply »
2 Comments

A random comment from our SEO guy to all of you who are saying use URL re-writing to add an index.cfm to all of the directory ending URLs (i.e. http://www.pioneerservices.com/) . Our guy had us actually change it so it takes off the index.cfm and leaves the URL ending with a directory. According to him this is more search engine friendly. For one thing it helps with duplicate content problems as apparently google and others index the two URLs separately. Apparently it's also considered better for the user for the reason that Ben mentioned, people just expect it to work that way. I have no idea if he's right on either count but thought I'd throw it out there since I just spent a fair amount of time putting that rule together.


Jul 3, 2009 at 9:36 AM // reply »
6,516 Comments

@Jennifer,

That's an interesting point about the SEO and the duplicate content. However, one thing that is nice about URL re-writing is that it is transparent to the user; in other words, the rewriting happens on the server and the user does not have to see it. So, we could use rewriting to add "index.cfm" to directory access requests and the user would still see the directory-only URL.


Jul 3, 2009 at 9:46 AM // reply »
2 Comments

I guess I should have clarified more. In an effort to "clean up" our search engine traffic they actually had me 301 redirect all those ones that had the index.cfm (actually in our case it's a default.cfm) to the directory only URL. So the user actually does see the change. We are 301'ing quite a few things actually trying to get duplicate content out of the engines because apparently that is a big deal here lately and it was indexing quite a few pages twice. Again, this is what our marketing department is telling us so I'm not sure of the validity... but it's an interesting thought process.


Jul 3, 2009 at 10:00 AM // reply »
6,516 Comments

@Jennifer,

To be honest, given the concept of a default document, I am surprised that a directory-only and a default document view are considered duplicate content.


Jul 27, 2009 at 4:24 AM // reply »
1 Comments

Ben nice post... I share your disappointment about folders not being handled. My guess the ColdFusion web server connectors aren't notified of those 404's, so it can't handle them with onMissingTemplate. I think they would have to rewrite the connectors to support this, but I could see how many customers many not want CF to handle all 404s.


Jul 27, 2009 at 8:10 AM // reply »
6,516 Comments

@Sbs,

Agreed. It's disappointing, but I don't know if there's anyway to work around it. So far, my favorite idea is simply to do a mod rewrite / isapi rewrite for non-index.cfm URLs.


Lys
Aug 25, 2009 at 5:16 AM // reply »
1 Comments

@Ben, Starting Macromedia ColdFusion MX on Unix platforms cause the webserver connector wizard to run every time with following message:

" Starting ColdFusion MX... There may be a few moments before you can access the ColdFusion MX administrator. This is normal......"

Can I cancel this ? thanks
Sarah Lys


Sep 6, 2009 at 1:10 PM // reply »
6,516 Comments

@Lys,

I am not that familiar with Unix platforms and ColdFusion. But, when you boot up ColdFusion, it takes a few moments to kick off. I am not sure what you are trying to cancel?


Sep 28, 2009 at 11:59 AM // reply »
1 Comments

Not sure I really follow this thread - why exactly would you want to bog CF down with static page requests? IIS or Apache is likely your underlying webserver, and like proper use of any tool in your toolbox, you should allow the webserver to be the webserver -- not CF.

I do not believe the intention with OnMissingTemplate() was to substitute for your webserver's basic 404 processing -- it is likely there since your webserver is passing requests of a certain file extension into CF and perhaps may not be validating that the file exists before doing so (or, showing you that webserver can access file but CF cannot).

As for folders not triggering it - if the folder doesn't exist, it also does not have a CF file within it of the default file extension (aka index.cfm). CF will never get this request ... and your underlying webserver handles the 404. Again, I don't understand the logic behind forcing ALL page requests through CF vs allowing IIS/Apache to do the work it was designed to do -- handle the traffic flow, pass to CF as needed but otherwise serve up all static page requests itself.

By routing static content thru CF, you are making CF run the basic minimum overhead functions on each static page request (start/end on app/session/request). Likely really minor, timewise, when you look at a single request, but doesn't seem best practice -- I'd rather keep processing as lean as possible in order to scale into a high-usage webserver.


Sep 28, 2009 at 12:01 PM // reply »
6,516 Comments

@Muji_mu,

We're not talking about static content here. We're talking about Search Engine friendly URLs that need to be translated into dynamic, ColdFusion content.

Were it just static content, I agree - just let the web server do its thing.


Post Comment  |  Ask Ben

Recent Blog Comments
Nov 21, 2009 at 11:03 AM
Groovy Operator Overloading Does Not Work In The ColdFusion Context
Hi Ben, Thanks for this informative post. Now I am reading ur old posts too ... read »
Nov 21, 2009 at 10:56 AM
HostMySite.com Has The Best ColdFusion Hosting
@Mehul, Yes very nice people, however several downtimes per day which was not acceptable. Hence we had to move out. I am glad you are having good luck with them so far. ... read »
Nov 20, 2009 at 11:32 PM
Five Months Without Hungarian Notation And I'm Loving It
I've used headless camel case for years for not only ColdFusion variables, but also SQL tables and fields... pretty much everything involving code. I also subscribe to the "don't abbreviate and clea ... read »
Nov 20, 2009 at 11:00 PM
Five Months Without Hungarian Notation And I'm Loving It
@Marcel, Yeah, I always err on the side of longer but more readable variable names. As for the camel casing of CF methods and the headless camel casing of custom items, I get around this by always ... read »
Nov 20, 2009 at 10:56 PM
Five Months Without Hungarian Notation And I'm Loving It
I use the following and love it: my.namespace.MyComponents.functionMethodsOrUDF() CONSTANT_VALUES_OR_PROPERTIES One thing I always try is to CamelCaseBuiltInColdFusionFunctions() so others can tell ... read »
Nov 20, 2009 at 5:38 PM
Learning ColdFusion 8: CFImage Part I - Reading And Writing Images
Hi Ben, Great article. I've been looking around to see if ColdFusion image engine can programatically create the following "wrap around" effect: http://www.creativepro.com/article/photoshop-s-she ... read »
Nov 20, 2009 at 5:35 PM
Maintaining ColdFusion Sessions Across SMS Text Message Requests Without Cookies
@Dave: I talked to Gert he suggested: <cfhttp method="get" url="http://{some cf website}" result="stuff" addtoken="yes" /> Note the addition of cfhttp attribute addtoken. That should persist y ... read »