ColdFusion 10 backport - SessionInvalidate()
With all my digging around while putting together CFTracker, I realised that a few new functions in the ColdFusion 10 Beta release are possible in older versions. In this example, it’s SessionInvalidate().
SessionInvalidate() is a new function in ColdFusion 10 that will do exactly what you’d expect, invalidate the session. In more detail is appears to do the following:
- Fires onSessionEnd for the session in question.
- Stops the session completely, it’ll disappear from server monitor / CFTracker as if stopped.
- Clears out the contents of the session. A dump of the session, after that function is called, returns empty.
Tools of the trade…
Firing onSessionEnd
How would we manage that, when the function can be called from anywhere? First we need to flip over the application scope to see what’s underneath…
<cfset appClass = CreateObject("java", application.getClass().getName()) />
<cfdump var="#appClass#" />
Obviously, getAppInvoker()
looks promising. Let’s grab that and dump it out as well.
<cfset appEvents = application.getEventInvoker() />
<cfdump var="#appEvents#" />
Well that wasn’t too hard was it? There it is, onSessionEnd, but what’s with the argument java.lang.Object[]
? Looking at the documentation for ColdFusion, we know that onSessionEnd expects to receive two arguments. The application scope and the session scope that is ending. A while guess of throwing these into an array and providing that as the argument appears to do the trick. For testing this, I set a low sessionTimeout and had an onSessionEnd that dumped the argument scope to a HTML file.
Application.cfc
<cfcomponent output="false">
<cfscript>
this.name = 'TestApp';
this.sessionManagement = true;
this.sessionTimeout = CreateTimeSpan(0, 0, 0, 30);
</cfscript>
<cffunction name="onSessionStart" returnType="void" output="false">
<cfset session.stamp = Now() />
</cffunction>
<cffunction name="onSessionEnd" returnType="void" output="false">
<cfargument name="sessionScope" required="true" />
<cfargument name="applicationScope" required="false" />
<cfset var lc = StructNew() />
<cfsavecontent variable="lc.output">
<hr />
<cfoutput>#DateFormat(Now(), "yyyy-mm-dd")# #TimeFormat(Now(), "HH:mm:ss")#</cfoutput>
<cfdump var="#arguments#" />
</cfsavecontent>
<cffile action="append" file="#ExpandPath('.\')#\dump.html" output="#lc.output#" />
</cffunction>
</cfcomponent>
index.cfm
<cfscript>
if (StructKeyExists(url, 'invalidate')) {
appEvents = application.getEventInvoker();
args = ArrayNew(1);
args[1] = application;
args[2] = session;
appEvents.onSessionEnd(args);
}
</cfscript>
<cfdump var="#session#" />
<a href="index.cfm?refresh">Refresh</a><br />
<a href="index.cfm?invalidate">Invalidate</a><br />
<a href="dump.html">View onSessionEnd dumps</a>
When running this, you should see sessions naturally expiring and being dumped into the “dump.html” file. Also, if you start clicking the “Invalidate” link you’ll see additional entries in the log. Success! As least as far as firing onSessionEnd goes, now we need to kill that session since you may have spotted that calling onSessionEnd won’t do the trick.
Stopping the session
This is a trick I’ve already posted about, so if you want more detail you might want to pop off and read "SessionStop(), ending a CF session". I’ll still go over the code a little here though. Hidden away in ColdFusion is coldfusion.runtime.SessionTracker
. This provides a lot of secrets about sessions, but the one we’re interested in is cleanUp(java.lang.String, java.lang.String)
. After a lot of trial and error, I finally figured out that it accepts the application name as the first parameter and session.cfid plus session.cftoken combined using an underscore for the second parameter.
<cfdump var="#CreateObject("java", "coldfusion.runtime.SessionTracker")#" />
Updating our index.cfm
file with the following will allow us to clean the session up after onSessionEnd has fired.
index.cfm
<cfscript>
if (StructKeyExists(url, 'invalidate')) {
appEvents = application.getEventInvoker();
args = ArrayNew(1);
args[1] = application;
args[2] = session;
appEvents.onSessionEnd(args);
sessionId = session.cfid & "_" & session.cftoken;
sessionTracker = CreateObject("java", "coldfusion.runtime.SessionTracker");
sessionTracker.cleanUp(application.appName, sessionId);
}
</cfscript>
<cfdump var="#session#" />
<a href="index.cfm?refresh">Refresh</a><br />
<a href="index.cfm?invalidate">Invalidate</a><br />
<a href="dump.html">View onSessionEnd dumps</a>
Great! onSessionEnd fired and the session is cleaned up after the request. The only thing left to do is clear the session out, as you’ll see from running the code, the CFDump
of the session still shows data. A good old call of StructClear(session)
should do the trick.
Gift wrapped
Putting all of the above together, gives us this “backported” version of the SessionInvalidate()
function from ColdFusion 10. All that’s left is to wrap it in an if
statement to limit the version.
<cfif ListFirst(server.coldfusion.productVersion) Lt 10>
<cffunction name="SessionInvalidate" output="false" returntype="void">
<cfscript>
var lc = StructNew();
// Construct session ID for cleanup
lc.sessionId = session.cfid & '_' & session.cftoken;
// Fire onSessionEnd
lc.appEvents = application.getEventInvoker();
lc.args = ArrayNew(1);
lc.args[1] = application;
lc.args[2] = session;
lc.appEvents.onSessionEnd(lc.args);
// Make sure that session is empty
StructClear(session);
// Clean up the session
lc.sessionTracker = CreateObject("java", "coldfusion.runtime.SessionTracker");
lc.sessionTracker.cleanUp(application.applicationName, lc.sessionId);
</cfscript>
</cffunction>
</cfif>
Github
The function will be available as part of a little project on github called CFBackport. It’s going to be a collection of functions for older versions of ColdFusion to act like those added to newer releases. https://github.com/misterdai/cfbackport
Compatibility
I’ve tested this on Coldfusion 8 and I believe it’ll work on 9. Those of you using ColdFusion 7 will have to try it and let me know, but I have tried to avoid using any newer syntax (see ArrayNew(1)
instead of []
). Any Railo or OpenBD users will have to check their respective engines as their internals won’t be the same as Adobe ColdFusion. Although I’m sure it’s possible ;).