ColdFusion Muse

Watch out for Coldfusion Mappings When Using onReqest( )

Mark Kruger January 27, 2006 5:19 PM Coldfusion MX 7, Coldfusion Tips and Techniques Comments (10)

I love using the application.cfc file instead of application.cfm. The cfc approach encapsulates several events inside of automatically fired functions that formerly required "hand coding". For example, I used to check to see if application vars existed and set them if they did not. This required thinking about locking and testing one or more variables for existence (isDefined() or structKeyExists()). Using Application.cfc means this job is handled by the onApplicationStart( ) function. One function that belongs to Application.cfc deserves a bit more attention - the onRequest( ) function. Here's an example from a webtop application that forces login.

<cffunction name="onRequest">
   <cfargument name="targetPage" type="String" required=true/>

   <!--- include some local functions --->
   <cfinclude template="udfs.cfm">
   <!--- include local vars --->
   <cfinclude template="localVars.cfm">
      <!---check to see if they are logged in... --->
   <cfinclude template="includes/control.cfm">
   <cfinclude template="#Arguments.targetPage#">

</cffunction>

UDFs and variables

You might notice that I'm using includes from some UDF's and some local variables. If, like me, you have a library of functions and some preset variables that used to go in a script block in application.cfm, you will have to put them here. Put them anywhere else and they will not be scoped correctly. Why? Because from this point forward the entire request is inside this function - so the onRequest( ) function's "this" scope is the "variables" scope.

Controlling Access

The third include in the code above is a "control" script. It checks the user's credentials and displays the login page if the user fails to pass muster (or pass the mustard as my 10 year old son Matthew informed me - "Dad, the teacher said we failed to pass the mustard - what's that mean?"). Again, it must go here in order to set things that we intend to "display". Plus, in this case, we need session and application variables that are fired by the other functions in order to make the control script work.

The target Page

The "targetpage" parameter is passed automatically to the function. It is (as far as I can tell) the value of Cgi.Script_Name. This is important because it includes the slashes. In otherwords, if the script name is "/dirA/dirB/myScript.cfm" Then the cfinclude will look like this:

<cfinclude template="/dirA/dirB/myScripts.cfm">
Why doesn't it just use "myScript.cfm" instead? Because the Application.cfc page might govern an application with subfolders - so naturally it needs the full relative path. It makes an assumption about the path in which it itself is running and uses that assumption in the include.

Mapping Uh-Oh

"So what" you say... If I'm running the application.cfc file from "/dirA/dirB/" then the include will work fine, right? So no matter how deep my application is in the directory structure it will be able to "find" the correct included file. Ah... actually the answer to that is the same answer given by Bob Dole when asked during the presidential campaign if he wore boxers or briefs - "depends".

What if I have a Coldfusion mapping called dirA. In that case Coldfusion will look in whatever directory I specified in the mapping for the file ("dirB/myScript.cfm"), and if it fails it will issue a "file not found" error. This, dear friends, will drive you crazy. You will look in the directory structure and see a file called "myScript.cfm", but when you type it into the browser it will simply say not found. You will check the trusted cache setting. You will jump to the conclusion that you should have been a .NET programmer, but until you remember to check mappings you are going to be quite frustrated.

This happened to us recently. We had an application with an acronym for a name. We used the acronym as a Coldfusion mapping so we could reliably point our createObject( ) code to the right components. What we didn't realize was that this acronym would be a fairly typical choice as a sub-folder for folks installing our application. The result - when they went to the webtop they got the "file not found" error.

To put it another way, you cannot have both a mapping and a subfolder (off of the root) with the same name but different paths and expect to use onRequest( ) in it's most typical fashion. You will have to either

  • Rename the mapping
  • Rename the sub-folder
  • Manipulate the "targetPage" variable
Otherwise you will get a file not found error.

  • Share:

10 Comments

  • Rich Rein's Gravatar
    Posted By
    Rich Rein | 1/27/06 4:45 PM
    Can you elaborate on the scenario that you encountered? Maybe I read too fast, but I feel like I am missing something?

    Something like this?

    Mapping
    -------
    /MySubFolder ---> d:\inetpub\wwwroot\components\MySubFolder

    URL
    ---
    http://www.mysite.com/MySubFolder/index.html, which they are expecting to point to d:\inetpub\wwwroot\MySubFolder\index.html

    If that is the case, it is a little larger problem than just when using application.cfc. you have a webroot mapping (which I believe is a CF default, as well as most web servers), so you shouldn't name a folder just under the webroot the same as another folder which is located elsewhere and has a mapping. AFAIK, the server will first attempt to use the webroot mapping ("/") followed by the directory structure you provided (through the url), then attempt to use an alternate mapping.

    Am I missing something???
  • mkruger's Gravatar
    Posted By
    mkruger | 1/27/06 5:20 PM
    Rich - yes I believe you are missing it. First, in the URL example above, resolution is accomplished not by CF but by the web server, and an HTML file doesn't ever get handed to the CF server so it would not be relevant.

    Second, in the case of a regular old file with NO application.cfc the file is never INCLUDED (never accessed via cfinclude like it is in the onRequest() function). Instead, the file path is determined by web server and passed to the CF engine.

    So if I have this as a url:
    http://www.mysite.com/MySubFolder/index.cfm
    (file served from d:\inetpub\wwwroot\MySubFolder\index.html)

    and I have this mapping

    /MySubFolder ---> d:\inetpub\wwwroot\components\MySubFolder

    I do NOT have a problem (yet) because CF never checks for a mapping. CF checks for mappings when CFINCLUDE or createObject("component","...") is used - but not when serving a file

    .... EXCEPT (and this is my point) for the case when onRequest( ) is used. In this case, cf suddenly becomes aware of mappings becuase the design of onRequest( ) is as a wrapper around the request and it uses CFINCLUDE to do this at runtime.
  • mkruger's Gravatar
    Posted By
    mkruger | 1/27/06 5:26 PM
    One other note.... it's a common misconception that a CF mapping has something to do with the way the WEB SERVER resolves paths or handles files. This is not the case. The web server handles paths before the request by parsing host header against it's metabase and looking for real (physical) or virtual paths. It then "hands off" the process to CF IF the file is a cfm file. At that point CF has a starting point (the file in question) that may or may not be superceded by the Application.cfm or Application.cfc file.

    In short, if I have a virtual mapping to "images" for serving image files and a CF mapping (to a different folder) called "images" for storing and manipulating uploads that point to different folders, I do not have a conflict - although I think I might end up with a migrane :)
  • Jeff Houser's Gravatar
    Posted By
    Jeff Houser | 1/27/06 6:38 PM
    It seems that your post can be summed up as "Don't give a mapping the same name as a real directory". I think that the problem is completely independent of Application.cfc or the OnRequest event. You could have the same problem when using cfobject, cfinclude, cfmodule, cfinvoke, etc...

    Out of curiosity, why did you use onRequest instead of OnRequestStart? with this code, you always run the 'current' code, so there is no obvious benefit to onRequest vs OnRequestStart. The downside of OnRequest is that it breaks web services, Flash Remoting, and event gateways.
  • Sean Corfield's Gravatar
    Posted By
    Sean Corfield | 1/27/06 8:59 PM
    Jeff is right: the mapping issue is related to cfinclude, not to Application.cfc. And it's OK to have a mapping with the same name as a real directory - as long as the mapping points to the same directory!! (which, of course, is actually somewhat pointless) So, yes, you probably shouldn't really have a mapping with the same name as a real directory...

    As for onRequest(), Jeff, onRequestStart() and onRequestEnd() share a 'variables' scope but if you don't have onRequest() then CFMX executes your requested page *outside* the context of Application.cfc. In order to include functions etc in the *same* page scope as your requested page, you *must* use onRequest() - that's what causes all the scopes to tie together.

    Also note that it is Application.cfc's *variables* scope that is shared across the methods - Application.cfc's *this* scope is actually "magic" and represents the settings that would otherwise be attributes in the cfapplication tag (as long as you set the values in the pseudo-constructor).

    The chapter in the CFMX Developer's Guide about application design and structure has a *lot* of great information about Application.cfc BTW.
  • Mkruger's Gravatar
    Posted By
    Mkruger | 1/28/06 8:20 AM
    Jeff - My poin is that with a cfinclude or create object you control the path that you pass to function. in my example where I'm in /dira/dirb/somescript.cfm I can do "cfinclude template="dirb/somescript.cfm", but the onRequest( ) function doesn't give me that option. It is going to use "/dira/dirb/somescript.cfm" - which introduces the problem. And I agree that you should use a mapping name that is the same as a "real" path - at least on general principle. The problem comes in when installing and using third party applications that use directories or mappings on a server with OTHER applications. In other words, sometimes it's out of my control.
  • mkruger's Gravatar
    Posted By
    mkruger | 1/28/06 8:22 AM
    Sean - thanks for the note on onRequest and onRequestStart and onRequestEnd.
  • Jeff Houser's Gravatar
    Posted By
    Jeff Houser | 1/28/06 11:02 AM
    Sean, thanks for the clarification.

    That distinction is not clear in the documentation. In Mark's example, I would have used OnRequest and copied the relevant information into the request scope.

    The docs do not make it clear why to use OnRequest vs OnRequestStart. More examples mmight help.
  • Jeff Houser's Gravatar
    Posted By
    Jeff Houser | 1/28/06 11:09 AM
    Mark,

    In Application.cfc you still have complete control of the path you pass to a cfinclude (as shown do in the first three includes of your example).

    I'm sure you could write code to process "arguments.targetpage" to get the 'proper' path if needed.

    Would a cascading Application.CFC, with an overriden OnRequest solve your directory problem?
  • mkruger's Gravatar
    Posted By
    mkruger | 1/28/06 12:46 PM
    Jeff - yes we can manipulate the path with work arounds as in the example. We typically develop an application suite where all the "main" scripts (the ones that appear in the browser) are in the "main" folder - so we are able to do something like

    #listlast(arguments.targetpage,"/")#

    But the point is we do not have control over the path to begin with and we are forced to re-work it after the fact. That makes it manageable to "something to watch out for".