Earlier this week, Chris Long explained to me that he was able to fix a ColdFusion error in his application by adding conditional logic within the onRequestEnd() event handler for a request that performed a CFLocation. It had been understanding that a CFLocation tag aborted any further page processing. And so, when Chris told me that he was running ColdFusion 9.0.1, I wanted to see if there were any changes in the onRequestEnd() behavior between ColdFusion 9 and the earlier releases. And, as it turns, yes - the behavior of the onRequestEnd() event handler has changed in ColdFusion 9.
To test demonstrate this difference, I have created a ColdFusion Application.cfc framework component that defines the onRequestStart() and onRequestEnd() event handlers. Each of these event handlers log their own execution to a TXT file where they can be tracked across page requests:
<cfcomponent output="false" hint="I define the application settings and event handlers."> <!--- Define the application settings. ---> <cfset this.name = hash( getCurrentTemplatePath() ) /> <cfset this.applicationTimeout = createTimeSpan( 0, 0, 1, 0 ) /> <!--- Define the log file path. ---> <cfset this.logFilePath = ( getDirectoryFromPath( getCurrentTemplatePath() ) & "log.txt" ) /> <cffunction name="onRequestStart" access="public" returntype="boolean" output="false" hint="I intialize the request."> <!--- Log the request initialization. ---> <cffile action="append" file="#this.logFilePath#" output="onRequestStart: #getFileFromPath( arguments[ 1 ] )#" addnewline="true" /> <!--- Return true so the request can process. ---> <cfreturn true /> </cffunction> <cffunction name="onRequestEnd" access="public" returntype="void" output="false" hint="I teardown the request."> <!--- Log the request end. ---> <cffile action="append" file="#this.logFilePath#" output="onRequestEnd: #getFileFromPath( arguments[ 1 ] )#" addnewline="true" /> <!--- Return out. ---> <cfreturn /> </cffunction> </cfcomponent>
Then, I created two index files (index.cfm and index2.cfm). On the first index file, I have a link to a redirect page which relocates the user to index2.cfm. This redirect page does nothing more than invoke a CFLocation tag:
<!--- Redirect to index 2. ---> <cflocation url="./index2.cfm" addtoken="false" />
Once I had these files in place, I then went from the first index to the second index (by way of redirect.cfm) using both ColdFusion 8 and ColdFusion 9. Here are my results:
ColdFusion 8 - Log File
As you can see, the "redirect.cfm" script execution only gets logged by the onRequestStart() event handler. In ColdFusion 8, a CFLocation tag prevents the onRequestEnd() event handler from executing. Doing the same thing in ColdFusion 9, however, yielded these results:
ColdFusion 9 - Log File
As you can see, the "redirect.cfm" script execution gets logged by both the onRequestStart() and onRequestEnd() event handlers. Clearly, in ColdFusion 9, the onRequestEnd() event handler will execute after a CFLocation has been invoked.
Outside of the onRequestEnd() event handler, CFLocation still aborts any further processing of the given request. However, in ColdFusion 9, that "further processing" no longer includes the onRequestEnd() event handler. I don't use the onRequestEnd() event handler all that much in my applications, so I have no gut feeling as to whether this is a welcome change or an unexpected one. In any case, it is a change of which change you should be aware.
Hmm, interesting. Now I need to revisit what is allow in OnRequestEnd(). Could be used for certain types of authenticated apps or maybe frameworks, not sure.
But cool find and nice to know
I think the onRequestEnd() provides some excellent hooks for logging since you flush the content to the client so as not to slow down their "experience."
Nice catch Ben. Do you know if this was an intentional change in CF9 or a bug?
If its intentional it opens up all sorts of nice things such as logging, but on the flipside it could break a lot of things too.
I remember seeing a code example where the author called a page header from the onRequestStart and the page footer from the onRequestEnd method.
Ben, Thanks for sharing and in my opinion, this is the correct behaviour in CF9. This gives much more control on how and what you want to achieve in onRequestEnd function and this way you are sure if you want to log anything in onRequestEnd method, that would be logged.
+1 to @Philip
This is in reply to your blog about sorting:
I enhanced a function about complex array sorting in CF. Did you find my blog when you searched for it, please have a look here and let me know what you think?
Sorry Guys, its off the topic, but I can't find a way to reply on Aaron's blog. Aaron, did you turn off commenting on your blog?
My guess is this is considered a "fix". I think there were a number of people who felt that the onRequestEnd() should always be called since the requests does, technically, always "end" at some point.
I think it definitely has the potential to break existing code, or so create odd behavior. I think that's why it's critical for people to be aware of it.
I've got some more testing I'd like to do with it.
I think it makes sense, to a degree, that this always fires. I'm reserving full judgment until I do a bit more testing.
Just to clear this up, is the rest of the template executed?
If your redirect.cfm page had:
<cflocation url="./index2.cfm" addtoken="false" />
<cffile action="append" file="log.txt" output="Hello!" addnewline="true" />
...the template stops running and the "Hello!" wouldn't be appended?
And... is there any difference to the <cfheader statuscode="301" /> behavior?
I had that exact same thought this morning and did double check the behavior. Trying to log to the file right after the CFLocation did *not* result in anything be written to the log file. So, it seems that only the onRequestEnd() actually get's fired.
According to what I found before, CFLocation is definitely different from simply executing a CFHeader/302/301 header:
Yep, this broke some functionality on our site where we were storing user messages in a persistent scope and then calling cflocation before clearing out the message on the onRequestEnd() on the subsequent page.
Now that onRequestEnd() fires even though we've redirected our message gets cleared immediately!
We'll work around it but in my opinion this is not a 'fix' as described in their release notes but a change in behaviour.
Thanks Ben for posting this. We were trying to figure out how come all of our CFLocation tags stopped working after we upgraded our CF8 server to CF9 for the past few weeks.
The CFLocation tag was appending the same path twice to the end of the URL, causing the redirects to break. We stopped using the OnRequestEnd() and now CFLocation is working again.
By the way, it was great meeting you at CFUnited.
Yeah, I am not sure this is much of a "fix" either. I suppose it makes sense, since your request is "ending"; but at the same time, you could also argue that for a CFAbort tag as well.
Great to meet you as well :)
What was that conditional logic that Chris Long explained? I wanted to try to use it in an application.cfc onRequestEnd() to determine when a CFLocation was executed. I am using CF8 and don't have the "fix" from CF9.
Yeah, this has just stung us after our company upgraded to 9.0.1.
I've posted this description of the problem, together with a request for a change:
Maybe this just slipped under my radar but I completely missed this change. Totally confused me when I was helping someone out with an app being redeveloped on CF9.
@Ben, didn't see much mention in your post about CFAbort behaving the same way (onrequestend still executes). But that's what I ran into as well.
It makes sense to me to have this behaviour as it'll be useful for having somewhere to put code for the end of literally any request. The problem I have is I'd like to know what happened to that request. Was it normal, a redirect, a cfabort... Is Cfcontent the same when used for a download? Be nice to have an extra argument in onRequestEnd(targetPage, abortType=[normal|abort|location|content|other])
I ended up recreating the old behaviour by creating a request scope variable at the very end of my onRequest method. Then testing for it's existence in onRequestEnd and aborting if it's not there.
Wait, you're saying that CFAbort *also* allows the onRequestEnd() event handler to fire in CF 9+... hmm. I'll have to test that one with my own eyes in the morning :)
Wow - I just tested what you said about CFAbort:
I can't believe that's actually true. This feels very much like a bad move to me.
I don't think it is a bad move.
CF8 had the bug of not executing the onRequestEnd, and has now been fixed in CF9.
To clarify the meaning of CFAbort. It is to stop the further execution of the code in the page. Yes. It stops what it says, but the control has to still get passed on to finish the page request. So, it is perfectly plausible to continue the call to onRequestEnd.
I definitely see this as a good move in CF9.
Offcourse the different behavior when compared to CF8 can cause some issues to some applications, but they have to correct it as the bug in CF8 is now fixed.
Yes, I was aware of this change from 8 to 9. Personally, it seems more 'logical' that onRequestEnd fires after the primary event is 'aborted'. Similar to the cfexit/exit context options, it would be nice to have an option to make it abort 'all' events of the request.
Additionally, I found the onReqeustEnd() event fires twice if you abort while in the event itself. Happily it does not create an infinite loop: