Counting Sessions with Application.cfc
A few days ago I posted about how you could use ColdFusion to track the number of sessions in an application. One of themes of that post was how much easier Application.cfc makes this process. If you haven't read the first post, please do so now. I'll wait.
So first lets look at the Application.cfc file:
<cfcomponent output="false">
<cfset this.name = "sessioncounter2">
<cfset this.sessionManagement = true>
<cffunction name="onApplicationStart" returnType="boolean" output="false">
<cfset application.sessions = structNew()>
<cfreturn true>
</cffunction>
<cffunction name="onSessionStart" returnType="void" output="false">
<cfset application.sessions[session.urltoken] = 1>
</cffunction>
<cffunction name="onSessionEnd" returnType="void" output="false">
<cfargument name="sessionScope" type="struct" required="true">
<cfargument name="appScope" type="struct" required="false">
<cfset structDelete(arguments.appScope.sessions, arguments.sessionScope.urltoken)>
</cffunction>
</cfcomponent>
The first method I have is onApplicationStart. Note that, outside of the cffunction and return tags, it is one line. For the heck of it, here is the code from the Application.cfm version:
<cfset needInit = false>
<cflock scope="application" type="readOnly" timeout="30">
<cfif not structKeyExists(application,"sessions")>
<cfset needInit = true>
</cfif>
</cflock>
<!--- Yes, I do need to make it. --->
<cfif needInit>
<cflock scope="application" type="exclusive" timeout="30">
<cfif not structKeyExists(application,"sessions")>
<cfset application.sessions = structNew()>
</cfif>
</cflock>
</cfif>
Which do you prefer? So just to be clear - the onApplicationStart method creates the structure we will use to count the sessions. We could use a list, but a structure makes it easy to insert/delete items.
Moving on - the next method, onSessionStart, handles writing our session key to the Application structure. Again - notice that it is one line. This isn't much different from the Application.cfm file - but - there is one crucial difference. Because ours is in onSessionStart, it gets executed one time only.
The last method, onSessionEnd, handles removing the session key from the application structure. If you remember from the last post I had to handle that myself in the sessionCount UDF. Now I don't need to worry about it. I simply remove myself from the Application structure. Do remember though that onSessionEnd can only access the Application scope via the passed in argument.
Ok - so lastly, let's look at the sessionCount UDF:
<cffunction name="sessionCount" returnType="numeric" output="false">
<cfreturn structCount(application.sessions)>
</cffunction>
Nice, eh? You may ask - wny bother? I could certainly just output the result of the structCount myself. But the UDF is nice. What if I change from using an Application structure to a list? Or what if I store active sessions in the database? The UDF gives me a nice layer of abstraction.
Comments
I would like to use something similar to track the user names that are currently logged in.
When a user logs in i set their name as a session variable. Any tips on doing this?
Thanks Ray!
Make sense? Shall I blog an example of that?
Then in my login page i appended the user name to the application.users list.
Then on onSessionEnd i use listFind to find the users name and listDeleteAt to remove the user from the list.
Might be better if i used a structure rather then list.
Thanks Ray!
>
>When a user logs in i set their name as a session >variable. Any tips on doing this?
Instead of using another one you could technically just rework the original and put a structure in a structure for each user with the token being the key, then modify the name field on login. It shouldn't break the structcount and keeps it all in one place.
application.userList = structnew()
application.userList.userid = structnew()
application.userList.userid.sessiontoken = blah
application.userlist.userid.username = blah
That way each is guaranteed to be unique and the top level still gives an immediate user count. Then you could go one deeper and find the token or username or ip whatever else you wanted to store in nested struct and reference in cfloop by collection I suppose.
Another way, if usernames were unique, would be to make the username the top key. You could still do a count without any excess code, then also do 1 structkeylist and get all users online.
My application.cfc looks to see if session.username is defined and if it is not it sends the user to a login page. I have a logout action page that basically kills the users session.username variable
StructDelete(session, "UserName");
On my logout page is there a way i can fire the onSessionEnd function in the application.cfc or end the session so the onSessionEnd function fires?
As far as the goes, I'd make the entire thing into a sessionCounter cfc, called from the appropriate routines in the application (e.g. a HAS A, not an IS A relationship).
That opens up the ability to do, say, a client counter component with the same interface, and and allows you tosubstitute it in if you ever change how your system works, or port it to a new server.
You need to think of two things here.
First there is the concept of the Session as ColdFusion understands it. This is: A user from one browser who hits me and never stops hitting me for 20 minutes. (Or whatever number.)
Then there is the DATA in the session. That data is set by you. (Although CF sets a few small things, like urltoken.)
So you can add, edit, delete that crap whenever. So on your logout you remove the username value.
That doesn't END the session. It just changes the data in the session.
So I wouldn't call onSessionEnd. Just do what you want with the data. Let CF worry about calling onSessionEnd.
Plus - if you did - how would a template call it?
One last question and i will leave you alone.
Am i safe putting code like this in onSessionEnd?
<cfif listFind(application.users, session.username) NEQ 0>
<cfset userListLocation = listFind(application.users, session.username)>
<cfset application.users = listdeleteAt(application.users, userListLocation)>
</cfif>
Second, as indicated I'd add a function to application.cfc named sessionCount that returns the count, black-boxing the entire process. So calling application.sessionCount() from the template does the trick, and w/o needing a separate function defined elsewhere.
Finally, and again as mentioned, I'd make a sessionCounter.cfc, instantiate it in init(), and pass through the calls so that a call to application.sessionCount() in turn calls this.sessionCounter.sessionCount().
That allow you at some point to make, say, a clientCounter.cfc with the same interface, and swap out all of the counting-specific code instantly just by creating a different kind of "counter" object. This keeps your application cfc clean, a must if you start adding extra functionality to it like session counting, security and logins, page tracking, and so on.
As far as that goes, creating a "sessionCounter" cfc let's you use the code inside of an application.CFM file as well. Two for the price of one.
If you want to post it I'll do the code tonight as an example...
@Michael: You can't add a function to app.cfc and call it as application.functionname. I mean you CAN add your own methods, but they don't exist as Application variables you can call later.
Now that being side - taking the _whole_ process and making an API out of it _could_ be a good idea indeed.
<cfset application.sessionCount=this.sessionCount>
Unfortunately, you can't reference 'this' in the method when you do so, so the struct would have to be an application scope variable as well. (A hack, and pretty much a brain-dead implementation of application.cfc if you ask me.) Better to do as I said, and implement the thing as a separate cfc. So on applicationStart().
application.sessionCounter=createObject('component','sessionCounter').init()
Then call application.sessionCounter.sessionStart() in onSessionStart and call application.sessionCounter.count() in the template where needed.
<cfif NOT structKeyExists(application.sessions,session.urltoken)>
<cfset application.sessions[session.urltoken] = 1>
</cfif>

