The ColdFusion application lifecycle can be a tricky beast to understand because it hinges more on memory space association and less so on what you might traditionally consider a "running" application. As such, the concept of actively "ending" or "killing" a ColdFusion application or user session is not something that lends itself well to ColdFusion application framework. ColdFusion 9 introduced the ApplicationStop() function to aid in its new Object-Relational Mapping Hibernate integration; but even with this new function, application and session lifecycle still act completely independently.
With all the fuzziness about what it means to be a ColdFusion application, I sometimes see people make the assumption that they can end a user's ColdFusion session simply by clearing the Session scope. Unfortunately, this is not true and will only lead to corrupted sessions. If you've used this approach before and it worked (I know I have), it most likely worked by sheer coincidence; meaning, you probably cleared a business-logic-identifier that signaled to your business logic that the user was no longer "logged in." The user's ColdFusion session, however, carried on quite well regardless.
The problem with clearing the Session scope is that the Session scope is not what relates the user to your ColdFusion application; just the opposite in fact - the Session scope is what is given to the user after that relationship has been established. The entities that relates a user to an active session are the CFID and CFTOKEN values (yes, this is a bit different if you enable J2EE sessions). These values persist as cookies and are passed, by the browser, back to the server with every single page request. It is these cookies that associates a user to a given session, which then in turn, makes available a given Session scope.
To demonstrate this, I have set up a simple ColdFusion application that approaches session destruction in two ways: clearing the Session scope and expiring the cookies. Let's take a quick look at the Application.cfc to see how the application has been defined:
<cfcomponent output="false" hint="I define application settings and event handlers."> <!--- Define the application settings. ---> <cfset this.name = hash( getCurrentTemplatePath() ) /> <cfset this.applicationTimeout = createTimeSpan( 0, 0, 5, 0 ) /> <cfset this.sessionManagement = true /> <cfset this.sessionTimeout = createTimeSpan( 0, 0, 0, 30 ) /> <!--- Define page request settings. ---> <cfsetting requesttimeout="10" showdebugoutput="false" /> <cffunction name="onSessionStart" access="public" returntype="void" output="false" hint="I initialize the session."> <!--- Set up a hit count variable so that we can see how many page requests are recorded in this user's session. ---> <cfset session.hitCount = 0 /> <!--- Return out. ---> <cfreturn /> </cffunction> <cffunction name="onRequestStart" access="public" returntype="boolean" output="false" hint="I initialize the page request."> <!--- Increment the session hit count (if it exists). ---> <cfif structKeyExists( session, "hitCount" )> <!--- Increment. ---> <cfset session.hitCount++ /> </cfif> <!--- Return true so the page can process. ---> <cfreturn true /> </cffunction> </cfcomponent>
There's nothing special going on here. When the session starts up, I am creating a hitCount variable to record page requests. Then, in the onRequestStart() event handler, I simply increment that hitCount value; of course, since we are experimenting with clearing Session scopes, I am checking to see if that value exists before I increment it.
Now that you see how the application is set up, let's take a look at our main display page:
<cfoutput> <h1> Clearing The ColdFusion Session Scope </h1> <p> Hit Count: <!--- Make sure the hitCount exists - if we cleared the session scope, then this will not be there. ---> <cfif structKeyExists( session, "hitCount" )> #session.hitCount# <cfelse> <em>N/A</em> </cfif> ( <a href="index.cfm">refresh</a> ) </p> <p> <a href="clear.cfm">Clear Session</a> | <a href="rebuild.cfm">Rebuild Session</a> | <a href="expire.cfm">Expire Cookies</a> </p> <!--- Dump out the sesion for debugging. ---> <cfdump var="#session#" label="Current Session" /> <br /> <!--- Dump out the cookie for debugging. ---> <cfdump var="#cookie#" label="Current Cookies" /> </cfoutput>
On this page, not only are we outputting the hitCount value stored in the session, we are also outputting the Session and Cookie scopes to see what value are available. When we hit this page for the first time, we get the following output - take special note of the where the CFID and CFTOKEN values show up:
As you can see, the CFID and CFTOKEN values are available in both the Session and Cookie scopes. Don't let that distract you, though - the session-scoped ones are not all that important. Take note that the current CFID value is 17603.
Ok, now, let's try to clear the Session scope by clicking on the "Clear Session" link. That will bring us to this page:
<!--- Clear the session scope. All this does is clear the contents of the session scope; it has no bearing on the person's session from an interaction standpoint (other than you might start breaking code). ---> <cfset structClear( session ) /> <!--- Redirect back to index page. ---> <cflocation url="index.cfm" addtoken="false" />
As you can see, this page simply clears all variables from the user's associated session object and then relocates them back to the primary display page. Sometimes, the assumption is made that this will "end" a user's session; but, when we get back to the display page, you can see that it does not:
If the use of structClear() actively ended the user's ColdFusion session, then this subsequent request to the application would have initiated a new session and a new call to the onSessionStart() application event handler. As you can see, however, the Session scope has not been repopulated; the user's previous ColdFusion session is still "running", albeit with a severely crippled Session scope.
As I mentioned before, the user's session is still running because the Session scope is a byproduct of the session and not the mechanism behind it. The drivers of the mechanism are the CFID and CFTOKEN cookies which, as you can see above, are still being passed successfully after the Session scope has been cleared. For fun, we can even try to rebuild our previous Session scope based on these cookies. By clicking on the "Rebuild Session" link, we'll be taken to this page:
<!--- Since we completely cleared out our session scope, in order to rebuild it, we pretty much have to go back to the source: our Application.cfc. Let's create an instance of it and use it to re-populate our session with the **programmer** defined session information. ---> <cfset app = createObject( "component", "Application" ) /> <!--- Rebuild the programmatic session. ---> <cfset app.onSessionStart() /> <!--- Using the onSessionStart() event handler will only re-populate the session with programmer-defined information, we have to try to put back in some of the automatic data that the ColdFusion framework would have created during session initialization. ---> <cfset session.cfid = cookie.cfid /> <cfset session.cftoken = cookie.cftoken /> <cfset session.sessionid = "#app.name#_#cookie.cfid#_#cookie.cftoken#" /> <!--- Redirect back to index page. ---> <cflocation url="index.cfm" addtoken="false" />
Once we've cleared the Session scope, we can't get all of those original values back; what we can do, however, is put the Session scope back into a non-corrupted state. To do this, we need to do two things: re-initialize the Session scope and re-populate the CFID and CFTOKEN values. As you can see above, in order to re-initialize the Session scope, I am creating an instance of the Application.cfc ColdFusion component and then manually invoking the onSessionStart() class method. Because this method refers to the Session scope directly, it will work on the user's current Session. To get the CFID and CFTOKEN values, all we need to do is copy them from the Cookie scope. The SessionID value is then a combination of the application name, the CFID, and the CFToken.
After we've done this, the user is redirected back to the main display page, which gives the following output
As you can see, the Session scope has been repopulated. Keep in mind, however, that this does not mean that the user's session has been restarted or that this is a new session in any way. Remember, the ColdFusion session is powered by the relationship established by the cookies being passed back by the browser. If we truly want to "end" the current session (so to speak), we have to stop the browser from reposting those cookies. To do this, we have to expire the current CFID and CFTOKEN cookies. By clicking on the "Expire Cookies" link, we get taken to this page:
<!--- Loop over all the cookies and expire them. In reality, we only need to expire the CFID and CFTOKEN session identifiers, but for our purposes, clearing all the cookies is sufficient. ---> <cfloop item="name" collection="#cookie#"> <!--- Have them expire immediately. This way, when the response is sent to the browser, all of their cookies will be cleared. ---> <cfcookie name="#name#" value="" expires="now" /> </cfloop> <!--- Redirect back to index page. ---> <cflocation url="index.cfm" addtoken="false" />
As you can see here, we are simply looping over all of the current cookies (that ColdFusion knows about) and setting them to expire immediately. When these expiration requests get flushed to the browser with the current response, it will prevent the browser from reposting the CFID and CFTOKEN values. In doing so, on the subsequent request to the main display page, the ColdFusion server will think that the user is a new user and provide them with a new session. This will give us the following output:
As you can, ColdFusion recognized the request for the display page as coming from a new user and therefore generated new CFID and CFTOKEN values (from 17603 to 17604). While this created a new session, please keep in mind that the previous session is still technically "running;" meaning, it has neither timed out nor has the onSessionEnd() event handler been invoked. The pervious session will only really end once it has been inactive for the SessionTimeout duration as defined by the Application.cfc.
Now, while calling structClear() on the ColdFusion Session scope doesn't end the user's session, it does have a place. If you are going to try and end the user's session prematurely (and I don't mean that negatively), calling structClear() on the Session has a value in that it will trigger garbage collection on the Session-scoped values. So, while the session itself is still active, ColdFusion will be able to free up the memory space more immediately.
It should be noted that all of this pertains to standard ColdFusion session management. If you enable J2EE sessions, there are completely different rules, most of which I cannot speak to from experience.
ColdFusion applications are very interesting at the mechanical level and can be a bit confusing. If you were confused at all before, I hope that this exploration has brought some level of clarity to the matter.
Want to use code from this post? Check out the license.