Skip to main content
Ben Nadel at cf.Objective() 2010 (Minneapolis, MN) with: Mike Henke
Ben Nadel at cf.Objective() 2010 (Minneapolis, MN) with: Mike Henke ( @mikehenke )

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

By on
Tags:

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#" />

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

Reader Comments

1 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

15,674 Comments

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

15,674 Comments

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

15,674 Comments

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

1 Comments

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

33 Comments

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

33 Comments

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.

1 Comments

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>

1 Comments

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

4 Comments

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?

4 Comments

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

2 Comments

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

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