ColdFusion's Application.cfc Session Events Are Not Session-Specific
Lately, I've been talking a lot about ColdFusion session management. In particular, I've been exploring what works and what does not work in terms of explicitly ending a user's session. The ColdFusion application framework is incredibly dynamic and powerful, which is great; but, with its flexibility comes a need to really test the inherent behaviors of the framework to see how they actually work. As I was exploring session termination, it occurred to me that I wasn't sure how the ColdFusion session event handlers were being assigned. Meaning, were they assigned on a per-session basis? Or, were they assigned on a per-request basis?
To test this, I needed set up a situation where a given user could experience their application in two different ways: one with a fully defined Application.cfc including an onSessionEnd() event handler; and one with a partial Application.cfc with a short session and no onSessionEnd() event handler.
I am doing this test to see what happens to established onSessionEnd() event handlers if the last page a given user requests is part of a sub-application that doesn't define an onSessionEnd() event handler. Will the original session event handler be executed within the sub-application context? Or, will the sub-application unwire any existing event handlers associated with the user's session.
To explore this, I set up my root Application.cfc, complete with an onSessionEnd() event handler:
Application.cfc (root - ./Application.cfc )
<cfcomponent output="false" hint="I define the application settings and event handlers."> <!--- Define the application. ---> <cfset this.name = "DualApplicationTest" /> <cfset this.applicationTimeout = createTimeSpan( 0, 0, 10, 0 ) /> <cfset this.sessionManagement = true /> <cfset this.sessionTimeout = createTimeSpan( 0, 0, 1, 0 ) /> <cffunction name="onSessionEnd" access="public" returntype="void" output="false" hint="I finalize the session once it has expired."> <!--- Define arguments. ---> <cfargument name="sessionScope" type="any" required="true" hint="I am the session scope associated to the session that has timedout." /> <cfargument name="applicationScope" type="any" required="true" hint="I am the application scope parent to the session that has timedout." /> <!--- Write to the log file. ---> <cffile action="append" file="#getDirectoryFromPath( getCurrentTemplatePath() )#log.txt" output="CFID: #arguments.sessionScope.cfid#" addnewline="true" /> <!--- Return out. ---> <cfreturn /> </cffunction> </cfcomponent>
As you can see, this ColdFusion application framework component defines a one-minute session timeout and an onSessionEnd() event handler that logs the expired CFID to the file, log.txt. This way, any requests made in this application context should use the given event handlers.
Then, I set up a sub-application that used the same name but no event handlers:
Application.cfc (sub - ./sub/Application.cfc)
<cfcomponent output="false" hint="I define the application settings and event handlers."> <!--- Define the application. Notice that we are using the same name as our root application. ---> <cfset this.name = "DualApplicationTest" /> <!--- The only purpose here is to see if the previous request's Application.cfc event hanlders will be used at all with this page request. Specifically, we want to see if the onSessionEnd() event handler will be picked up. ---> <cfset this.sessionManagement = true /> <cfset this.sessionTimeout = createTimeSpan( 0, 0, 0, 1 ) /> </cfcomponent>
As you can see, both the root and the sub application share the same name, "DualApplicationTest". This will allow them to access the same ColdFusion memory space and set properties on the same application and sessions. This sub-application, however, has a one-second session timeout. With this one second session timeout and a lack of the onSessionEnd() event handler, I can test to see if the event handlers defined in the root Application.cfc will be executed in the sub-application context.
As it turns out, they are not - when the sub-application session times out, the original onSessionEnd() event handler does not get executed.
This is interesting; but, I wasn't ready to draw any conclusions just yet. I felt that I needed to do one further test. This time, I wanted to use the same application setup; but, in addition to the one user entering the sub-application context, I wanted a different user (with a different session), making requests to the root application context. In this way, I could see if the ColdFusion application framework event handlers were defined in relation to the session or simply to the most recent application page request.
To test this, I set up a page in the root-application context that would refresh itself every two seconds. This page would be used by the secondary session to make sure that a subsequent page request would be made after the primary session entered the sub-application context:
<cfoutput> <!DOCTYPE HTML> <html> <head> <title>Session Heartbeat</title> <!--- Refresh page every few seconds. ---> <meta http-equiv="refresh" content="2;url=./heartbeat.cfm"> </meta> </head> <body> <p> Now: #timeFormat( now(), "hh:mm:ss TT" )# </p> <p> CFID: #session.cfid# </p> </body> </html> </cfoutput>
As you can see, this page uses the META-refresh tag to make requests to the application every two seconds. In doing so, we can see if the application event handlers defined in this session's page request will affect the session events triggered by the primary session in the sub-application context.
As it turns out, they do - as you can see in the video, the ColdFusion application event handlers defined in the requests made by heartbeat.cfm were wired up to react to the session events triggered by the user in the sub-application context. Keep in mind that these two sessions were owned by different users and had different CFID / CFTOKEN values.
This is very interesting. While it has been demonstrated that application and session settings (ie. management and timeouts) are defined on a per-session-request basis, it appears that ColdFusion application event handlers are defined purely on a request-basis, irregardless of the session associated with the given page request. To be honest, I am not sure how I feel about this. From a performance standpoint, I suppose it makes the most sense; by using a request-basis for defining event handlers, the ColdFusion server doesn't have to keep references to separate event handler definitions for every single session. But, from an intent-basis, I think this behavior might be off the mark.
The ColdFusion application framework might seem simple at first; but, the more you dig into it, the more dynamic and complex it becomes. I've been digging into it for a long time and I'm still learning new things! Explorations like this are incredibly helpful for me and I hope that they are starting to shed some light on the many nuances and caveats that come with the Application.cfc component and the ColdFusion application framework.
Want to use code from this post? Check out the license.
Very nice detective work on this!
It makes perfect sense why some of my code don't work sometimes that rely on the app.cfc events/functions...
This should be better documented by Adobe.
Back to the drawing board and rethink my app mechanism.
The ColdFusion application framework is really awesome because of what it makes available. At face value, it does a lot for very little; but, once you get into the tiny details, there's really a lot that can be done - it's just super important to understand the full application life cycle. Like I said in the post, I've been looking into this a long time and I'm *still* finding this stuff :) It's a beefy framework.
This is actually exactly what I would have expected to happen. I think the mistake here is that you're looking at the OnSessionStart/End methods as being a part of the session, but really they are part of the application. Although you've declared that these two applications are the same application (same name), that doesn't create inheritance from one application to the other it just says that they can access a similar "namespace" for information. Basically, think of them as two separate programs with a shared scope; your sub program has no idea that there was an OnSessionStart/End in the other application, so it doesn't do anything with it.
I think that this test proves that naming your applications the same thing does not truly create a unified program out of separate applications, but rather it just kind of smashes the application scope together. Kind of like the applicationtoken attribute of <cflogin/>.
You make a good point regarding the event handlers as being a setting of the application, not of the session. That's actually is a very convincing viewpoint. I had been thinking of them in terms of the session - but, the event handlers are really how the *Application* handles events, one of which, happens to be session termination within it.
As far the inheritance issue, you are correct that one application doesn't "extend" the other one; however, they actually are the same application in the sense that any ColdFusion application is an "application."
Remember, the application isn't the Application.cfc file; rather, the Application.cfc file simply the means by which a given page request can be associated with an existing (or new) ColdFusion application. As such, when two Application.cfc files share the same name on the same instance, they are the "Same" application in that they associate with the memory space and can update the same settings. That is why, in the first example, the onSessionEnd() never executes after the sub-application is entered (the sub-application de-wired the onSessionEnd() event handler from the application with the given name).
I think we're saying the same thing, but with different points of view. I prefer not to use conjoined applications in ColdFusion because I don't feel that they are a complete solution.
I'm not really a fan of the fact that you can declare the same application in two or more different Application.cfc files if those application files don't truly create a single unified application. I'd prefer that if two "applications" have the same name, that they would share session and application start handlers; if they are really one application, it would make sense for them to be the same. The problem with ColdFusion's implementation of this is that there is no distinction between either application if they share the same name, so there is no way to enforce that kind of determination. This leaves me with what I feel is an incomplete solution to the problem of breaking an application into discreet parts, so I choose not to use them. I've also found that in most cases, the need for a second application was caused by a problem that could have been overcome with a different application design and/or it created a high degree of coupling between the two applications which always came back to bite me (such as when it became necessary to move the two applications to different servers).
Sorry if thats a little bit off topic :)
I've been thinking about your said all day, regarding the "application" level events rather than session events. I really really like it. I guess I never thought too deeply about that point and it just makes so much sense - everything set in Application.cfc (from a settings perspective) is about the application. The fact that there are session that do stuff is only meta to the situation in so much as that the event bubble up (so to speak) to the application.
Just really clarified it nicely in my head, thanks.
As far as two apps with the same name, I pretty much never do that - I only did it here as a way to test. I agree with you that there is something that doesn't feel quite right about it.