Ben Nadel
On User Experience (UX) Design, JavaScript, ColdFusion, Node.js, Life, and Love.
I am the chief technical officer at InVision App, Inc - a prototyping and collaboration platform for designers, built by designers. I also rock out in JavaScript and ColdFusion 24x7.
Meanwhile on Twitter
Loading latest tweet...
Ben Nadel at cf.Objective() 2009 (Minneapolis, MN) with:

Using ColdFusion to Handle 404 Errors (Page Not Found) On Development Server

By Ben Nadel on
Tags: ColdFusion

Handling 404 errors can be tricky no matter what. On your live, production server, you can set up custom 404 handling on a site-by-site basis via IIS custom error templates. However, for many of us, this is trickier on the development server where there are no site. Its just one web server with many sub directories, none of which are usually virtual sites.

To remedy this situation, I have developed a 404 error handler that is defined at the root of the developmental web server. This 404 error handler catches 404 errors and attempts to forward the errors to individual "site" error handlers in an attempt to mimic 404 error handling on a live site. This setup makes a few assumptions:

  1. Your 404 errors are handled by a ColdFusion template named "cferror.cfm"
  2. Your cferror.cfm template is located either in the root of the site (a sub directory on the development server) or at least that it is in the same directory or a parent directory of the requested non-existent page.

The system works by catching the 404 error at the root of the ColdFusion web server. It then takes the requested page and starts crawling up the directories url. For each directory, it check for the existence of the cferror.cfm template. If it is found, the root 404 error handler performs a CFLocation to that cferror.cfm template, sending, as the query string, the same "404;" query string that IIS has defined.

As the system crawls up the directory path, after checking for cferror.cfm, it checks for either an Application.cfm or Application.cfc file. If it hits one of these files, it stops crawling and displays its own Page Not Found message. The idea here is that we don't want to leave the context of the application when search for a 404 error handler.

I have prevented infinite loops (the root 404 error handler constantly CFLocating to itself) by stopping the search once there is no more directory to crawl (signaling that we are in the root directory). I have not tested this, but I assume this means that if the development server's cferror.cfm template is NOT IN THE ROOT of the web server, the infinite looping IS possible.

A word of caution: This template is done to help develop 404 error handling by simulating 404 error handling on the live, production server. It DOES NOT MIMIC live 404 error handling. On the live server, IIS will pass control directly to your site's 404 error handler without doing any URL location changes. Therefore, from your BROWSER's PERSPECTIVE, on the live server, your missing page URL may be in a different directory as your 404 error handler. On the development server, however, the missing page URL is ALWAYS in the same directory as the 404 error handler.

For example, let's say you request a non-existent page "ben-nadel/is/cool.htm" on the live server.

Browser sees:
ben-nadel/is/cool.htm

ColdFusion sees:
cferror.cfm

The web browser is TWO directories down in the site structure. The relative paths would start with "../../".

If you do the same thing on the development server:

Browser sees:
cferror.cfm?404;http://..../ben-nadel/is/cool.htm

ColdFusion sees:
cferror.cfm

Both the web browser and the 404 error handler are in the SAME directory (due to the root 404 error handler's CFLocation). The relative paths would be empty since there is no sub-folder URL.

The cferror.cfm template as it exists in the root of my development server:

  • <!---
  • Set a short timeout since this page should not
  • hog any resources.
  • --->
  • <cfsetting requesttimeout="5" />

  • <!--- Get the query string. --->
  • <cfset strQueryString = CGI.query_string />

  • <!--- Check to make sure that we have a 404 error. --->
  • <cfif Find( "404;", strQueryString )>

  • <!--- Strip out server name. --->
  • <cfset strDirectoryPath = ReplaceNoCase( strQueryString, CGI.server_name, "", "ONE" ) />

  • <!--- Strip out 404 and protocols. --->
  • <cfset strDirectoryPath = REReplaceNoCase( strDirectoryPath, "(404;)|[a-z]{2,5}://", "", "ALL" ) />

  • <!--- Remove any query strings. --->
  • <cfset strDirectoryPath = REReplace( strDirectoryPath, "(\?.*)$", "", "ONE" ) />


  • <!---
  • If there is a file being used, get the directory
  • from teh path.
  • --->
  • <cfif REFind( "\.[\w]*$", strDirectoryPath )>

  • <!--- Remove the file name. --->
  • <cfset strDirectoryPath = GetDirectoryFromPath( strDirectoryPath ) />

  • </cfif>


  • <!---
  • Set the initial current directory value that will
  • be check during the loop.
  • --->
  • <cfset strCurrentDirectory = strDirectoryPath />


  • <!---
  • Loop over the directory path while we still have
  • directories and look for the cferror.cfm.
  • --->
  • <cfloop condition="true">

  • <!--- Get the directory from the path. --->
  • <cfset strCurrentDirectory = GetDirectoryFromPath( strCurrentDirectory ) />

  • <!---
  • Check to see if we need to break. We don't
  • want to find THIS cferror.cfm page as that
  • will just cause a crazy infinite loop. At this
  • point, just let the page finish to show the
  • page not found error.
  • --->
  • <cfif (Len( strCurrentDirectory ) LTE 1)>
  • <cfbreak />
  • </cfif>


  • <!--- Check to see if the cferror.cfm file exists. --->
  • <cfif FileExists( ExpandPath( strCurrentDirectory & "cferror.cfm" ) )>

  • <!---
  • We found an error template, so relocate to
  • that template. To make sure our paths are not
  • crazy, lets cflocation to it and use the same
  • query string that we got.

  • CAUTION: This is not how the LIVE server will
  • handle this action. This is designed for local
  • error handling on the developmental serer.
  • --->
  • <cflocation url="#strCurrentDirectory#cferror.cfm?#CGI.query_string#" addtoken="no" />
  • <cfbreak />


  • <!--- Check to see if we can find an application file. --->
  • <cfelseif (
  • FileExists( ExpandPath( strCurrentDirectory & "Application.cfm" ) ) OR
  • FileExists( ExpandPath( strCurrentDirectory & "Application.cfc" ) )
  • )>

  • <!---
  • We didn't find a cferror.cfm page, but we did
  • hit the root of an application, so break out
  • of this loop. We don't want to crawl up the
  • directory any more than we have too. If this
  • application doesn't catch errors then just stop.
  • --->
  • <cfbreak />


  • <cfelse>

  • <!---
  • We didn't find the file or hit any application
  • roots, so remove the right most slash for next
  • loop. We do this otherwise the
  • GetDirectoryFromPath() will not be able to keep
  • moving up path.
  • --->
  • <cfset strCurrentDirectory = REReplace( strCurrentDirectory, "[\\/]{1}$", "", "ALL" ) />

  • </cfif>

  • </cfloop>

  • </cfif>


  • <!---
  • If we are still here, then we didn't find a directory
  • containing a cferror.cfm. Dump out some info to help
  • the user debug.
  • --->

  • <h2>
  • 404 Page Not Found
  • </h2>

  • <p>
  • <cfset WriteOutput( CGI.query_string ) />
  • </p>

  • <cfdump var="#CGI#" />



Reader Comments

Ben, is this solution appropriate for my production server running about 80 sites? I provide a ColdFusion CMS and each site is an instance (still in their own directory on the prod server) and I am looking for global error handling. Basically want to show the friendly message to the user and email or stare the actual error to me.

Seems something would have to be done in both CF admin and IIS to cover all the bases? (that is, provide a friendly message for any error?)

Providing I wanted to govern all these sites with one solution, can/shold the cferror.cfm file(s) be placed in a common place outside each site root? Thanks for any info.

Jason

Reply to this Comment

@Jason,

I wouldn't use a global error handler to handle ColdFusion errors - I'd set up something like a CFError tag, or the OnError() event handler in each of the given Application.cfc files.

Now, if you're talking about a global missing template handler, then I believe that can be set up in the ColdFusion administrator, but I have not done that before (due to laziness most likely). You have to be careful in that template, however, as it does not support the full array of ColdFusion functionality.

Reply to this Comment

Ben,

Would an onMissingTemplate function in the application.cfc be different from your method?

Thanks!

~kate

Reply to this Comment

@Kate,

The onMissingTemplate() is good, but it only works with CFM/CFC pages; as such, it won't work with anything like .htm pages or even partial URLs that get pasted improperly.

Reply to this Comment

Fantastic!

I'm going to use this all over the place. Well, technically, I'm going to use it in only ONE place, but it's going to be a huge improvement for handling missing pages.

Reply to this Comment

@Leonard,

Recently, I have started using IIS Mod-Rewrite and have found it really nice. But, before that, I was using this 404-handling method with great success for like 4 years.

Reply to this Comment

Hi Ben,

I have a project where we're receiving a list of URL's from auto dealerships to display their car inventory, the problem I'm having is that some of the images don't display because of 404 errors. I'm trying to find a way to display a default placeholder if the URL is bad, but I cannot figure out how to detect the 404 error in the photo URL. Do you have any advice?

Thanks,
Justin

Reply to this Comment

Ben, where we gonna put this template , i mean how this file gets called automatically every time a non existent file is requested.

Reply to this Comment

Ben, Suppose I run a url http://localhost:8500/mytest/blaah.......

it returns me java.io.FileNotFoundException.

But I want to handle this in my customised showing custom error page.

Reply to this Comment

For Apache/Linux:

I use HostMySite, and I set up the 404 handler page. Apply something like my code below on the handler page:

<cfset the404 = cgi.REDIRECT_URL>

<cfif the404 CONTAINS ".php">
<cfset theFileName = listLen(the404,'/')>
<cfset theFileName = listGetAt(the404,theFileName,'/')>
<cflocation addtoken="no" url="/some_directory/#replaceNoCase(theFileName,'.php','.cfm')#">
</cfif>

Reply to this Comment

Very easy the configuration.

I read a lot pages and I can't find the solution.

I open the administrator and change this

Administrator/server settings/Error Handlers/Missing Template Handler

and put the page that you want load and it is missing.

I create a cferror.cfm but it could be a index.cfm

/cferror.cfm

Reply to this Comment

Hey Ben - I know this post is fairly old - but it has helped a great deal in implementing something I need for one of my apps.

I have a scenario I'd like to ask you help with:

Example -

index.cfm has a bad img link. My custom 404 shows the QUERY_STRING value - but the "script_name" is shows the custom 404 page - rather than the index.cfm that contains the bad link. Any clue on how/where I can get that info from my page?

Reply to this Comment

And RIGHT after I ask my question - I come up with me answer. Capturing the CGI.HTTP_REFERER gave me the page (and the URL string) that contains the error...

Reply to this Comment

Hey, Ben. I recently had some code moved from a server running IIS in front of ColdFusion to a server running Apache in front of ColdFusion and the move broke my 404 handling because Apache doesn't populate the CGI.QUERY_STRING value the same way IIS does. I know your post here is about IIS but I thought I would post what I did on Apache in case it helps another reader (and then I'll plan to write a blog post of my own about it too).

Once I found out about the Apache .htaccess file and how it works, I decided that rather than rewrite my ColdFusion code, I would use the .htaccess file to populate the same CGI.QUERY_STRING value as would be automatically populated by IIS; this way the code is portable and can be used with ColdFusion running behind either IIS or Apache (because, as well as I'm aware, if you put a .htaccess file onto IIS, it just gets ignored). Here is what I have created to do that:

RewriteEngine On

RewriteCond %{REQUEST_FILENAME} !-d
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !\.cfm$
RewriteCond %{QUERY_STRING} !=""
RewriteRule ^(.*)$ redirect.cfm?404;http://%{HTTP_HOST}%{REQUEST_URI}?%{QUERY_STRING}

RewriteCond %{REQUEST_FILENAME} !-d
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !\.cfm$
RewriteRule ^(.*)$ redirect.cfm?404;http://%{HTTP_HOST}%{REQUEST_URI}

I believe the only change anyone should need to make to this code would be to the name of the ColdFusion file being used for handling the redirect: in my code above, it's redirect.cfm; you would need to change it to whatever is the name of the ColdFusion file you're using for handling your redirects.

When searching for info on how to handle this, I found indications that people are struggling with how the CGI.QUERY_STRING value is populated with IIS + ColdFusion 10; that's apparently a bug and this info I have provided here does not of course have any bearing on that (sorry: I wish it did!).

Reply to this Comment

Post A Comment

You — Get Out Of My Dreams, Get Into My Comments
Live in the Now
Oops!
Comment Etiquette: Please do not post spam. Please keep the comments on-topic. Please do not post unrelated questions or large chunks of code. And, above all, please be nice to each other - we're trying to have a good conversation here.