Skip to main content
Ben Nadel at the jQuery Conference 2010 (Boston, MA) with: Dan Heberden
Ben Nadel at the jQuery Conference 2010 (Boston, MA) with: Dan Heberden

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

By
Published in Comments (33)

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

<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

<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.

Want to use code from this post? Check out the license.

Reader Comments

15,848 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().

20 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.

17 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.

15,848 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).

28 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."

27 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!

42 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.

15,848 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).

45 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 :)

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.

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.

15,848 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.

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.

15,848 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.

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.

15,848 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.

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

15,848 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?

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.

15,848 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.

5 Comments

For missing img sources, i've found this onError javascript to be an easy way to fail gracefully. You could even uses a dyanmic page as your onError source to return different images based on the Params I think. We sometimes have to use images on servers that we don't have direct access to, so this lets the browser deal with it.

<img src="images/theWrongFile.jpg" onError="this.src='images/sorry.jpg'">

15,848 Comments

@Matt,

Very nice suggestion. I hadn't thought of that. There are a few image-related attributes that I need to look into.

2 Comments

Daft resurrection, but...

I'm in the camp of "CF shouldn't handle directory 404's - or any non CF 404's"
It's the servers job, not CF's - CF stays oblivious to the request, let alone the state of the request - unless it's a CF file.. Why is a strength being called a weakness ?

OnMissingTemplate() is a lot more useful than you give it credit for.. It's at least 50% of a basic content management system.

When you give a client a content editing interface - is it not easier to have content purely in the database ? (obviously cached)
oMT() is perfect for me - have oMT() call framework.cfm as a module to wrap the contents, then simply dump the database results into it. Job's a fish.
No files needed to be created, managed, updated or written..

Perfect for 404 type results as well - "Page not found, I think you meant..." Screw that, just serve up the content that matches in the database. Particularly useful with affinity type websites - content not on one scheme ? Fine, flick through the database and see if it's part of another scheme - run a quick ReReplace on the scheme names and serve it up, then a quick CFMail to remind the client to add that page.

Let's leave Mod_rewrite out of it, too.. How often do you want to serve up index.html, default.asp etc ? Never ? Cool, then set the directory to have a single default document "index.cfm" I can't stand webservers set up with every default document under the sun when they only ever use one or two.
Hit your directory only - yes, that's CF kicking in just as you wanted it to. Enjoy.

Gif/jpg - Eh if you want to, why not just build a quick and dirty CF Image service.. <img src="imageserver.cfm?img=mypic.jpg">
Might as well use the opportunity for thumbnails as well - go on, add a size attribute whilst you're at it.. <img src="imageserver.cfm?img=mypic.jpg&size=thumb">

All of the above aside Ben, you've taught me a lot about CF's new abilities whenever a new release comes along and I'll remain eternally grateful for it - and keep coming back for more :) Thanks..

15,848 Comments

@Chris,

I'll agree with you that we don't want ColdFusion handling non-CFM/CFC-based 404s. If an image can't be found, I don't think we need that to be handled in ColdFusion.

The fuziness comes when your URL structure is part of your application logic. I do use rewriting, but only in the most minimal way possible. Basically, I take CFM-based 404s and turn them into path_info requests:

/foo/bar

... becomes:

/index.cfm/foo/bar

Then, I let ColdFusion look at it and determine how to route it. As such, I try to keep ColdFusion involved as much as possible.

And I don't mean to sell onMissingTemplate() short. In fact, I think it's really good; and, it can be the perfect solution for content management systems as you describe.

But, the thing that just hold me up is the sub-directory (non-root) navigation that can't take implicit advantage of the default document.

If the server would automatically append the "index.cfm" default document onto a directory-level request before handing the control off to ColdFusion, that might be one thing. BUT, that kind of approach would probably be asking the server to make too many assumptions about the nature of the ColdFusion application.

At the end of the day, if it works for you, that's awesome - I'm certainly not going to diminish that in any way.

And, thank you for the kind words :) I really appreciate that. I hope to keep delivering value to the community.

2 Comments

See your point - I was a bit knackered the other evening.. 6am-ish, still coding..

Had I thought about it a little more, instead of jumping on my default document high horse and slightly missing the point, I'd have asked why not just simply set a custom 404 handler (in server, not CF) to point to your webroot or a dedicated processor..

Mod_Rewrite is one middleman too many, and not needed.. Even IIS, and dare I suggest it "PWS" (if anybody remembers that) can do a custom 404 using a page on server..

15,848 Comments

@Chris,

No worries at all - the conversation is always welcome. I hope that each of our ideas helps to improve the overall understanding.

As far as 404 errors, I actually used to use that approach. For probably the first 3 years of this blog's life, it was all managed through 404 error handling. In fact, the only reason that I got into mod_rewrite (or IIS Mod-Rewrite as it were) was because we had a client that was using it so I needed to learn something about URL rewriting.

At that point, it just seemed like something new and fun to implement. The 404 error handling approach was never a cause for problem in my experience.

1 Comments

@Ben, thanks for the blog, the posts, the time you take to support the community.

Just wanted to add my $.02 that, in an attempt at a new application style, we were working on a completely "file-less" system and found the onMissingTemplate handler to be just as disappointing as you mentioned.

We even went to great lengths to mess with the IIS error handling, error pages, redirects, etc all to no avail and ideally we would not want to do this as we like our applications to be as portable as possible.

Obviously ISAPI Rewrites, et all would work but does not meet the requirements of portability we wanted.

1 Comments

I have been tasked to do 301 redirects for 65 blog posts
using urls from the Camdon blogcfc app.

my question is 'how is it that you can parse this url before it fails for not finding
a .cfm at the end of the example string'? (because blogcfc doesn't chunk)

my server (Railo/tomcat) just does a 404 when it sees this url:

http://www.mydomain.com/index.cfm/2012/3/1/Some-blog-post-name

I was able to do a cfswitch for the categories, because it had url vars (catid) in the
string, but when I got to this type of request string I get an instant 404.
There's nothing in the path_info, no querystring structure. I'm not sure how to
redirect it if I can't direct it to index.cfm, (where the test redirect is.)

Any hints, tips, suggestions would be much appreciated.

4 Comments

@Jack,

You might try using a feed tag instead. Of course it could be a tad tricky trying to decided which ones you want to pull in though. You can then loop
through the links, through structure.

Although Ray has several blogs out there, it could be different for you since my method works great for pulling from WP.

One thing to note about WP. The categories and tags are basically in one. Although the links are different by swapping out the word category or tag, it will still redirect to category even if the word tag is used where it should show the work category in the url.

The default for the 404 is any file that ends in .cfm. If the file does not end in .cfm the onMissionTemplate() function will not work. But, thanks to Ben, he created that tasty solution for other file types above. I made a few altercations to what you created but what you are doing should work.

Simple point being, every directory you are trying to find should have a file in it ending in .cfm. If I had to make a quick guess at why it might not be working for you, the slash at the end should be there. It is going to treat that as a file and not a directory. This is the reason you are getting the 404.

I believe in love. I believe in compassion. I believe in human rights. I believe that we can afford to give more of these gifts to the world around us because it costs us nothing to be decent and kind and understanding. And, I want you to know that when you land on this site, you are accepted for who you are, no matter how you identify, what truths you live, or whatever kind of goofy shit makes you feel alive! Rock on with your bad self!
Ben Nadel