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.