Link to home
Start Free TrialLog in
Avatar of paddycobbett
paddycobbett

asked on

How can i determine when a session has expired?

I need to work out how to determine when a session has expired/timed out. In my application.cfm i declare session management (as attached in the code sample). I presume that all data associated with a SESSION is destroyed when the SESSION timeouts. However, the problem is that the session puts some data in the application scope which i would like to destroy when the SESSION timeouts also, so i need to detect when this occurs. Or to give another reason, i'd like the user to be displayed a reason for being "logged out", i.e. "Please sign in again as your session has expired", rather than just being told to "sign in", right now the system can't distinguish between the 2 cases.

So all i really need to know is how can i determine when a SESSION has timed out? I'm aware of application.CFC, which has a onSessionEnd() (or something like that), however i've had problems using cfajaxproxy with application.CFC, so have remained with application.CFM (this also appeared to not be an uncommon problem having looked on blogs). Either way, the application.cfc is a new inclusion to coldfusion, so what did people do previously to detect the end of a session?

Additionally, i noticed (and correct me if i'm wrong), that the session time out is fixed, i.e. it's not related to "idle time". Is there a built in way to specify an "idle timeout". Or if there's a way to extend the timeout, which i could do by adding a few minutes after each page click (inserting some code in the header of each page for example).

Thanks in advance.
<CFAPPLICATION 	name="myapp" 
	sessionmanagement="Yes" 
	setclientcookies="Yes" 
	sessiontimeout="#CreateTimeSpan(0, 3, 0, 0)#">

Open in new window

Avatar of SidFishes
SidFishes
Flag of Canada image

you should never tie session and application variables... app variables are meant to last for the app life and should be used for things like dsn name and other non-changing variables. Also, it is available to all clients so you really -can't- tie sessions and ap vars.

  The only way to -test- for a session timeout is to validate whether the session variable(s) still exist when the client reloads the page.

<cfif session.somevar eq "">
Your session has timed out. PLease logon again
</cfelse>
normal page
</cfif>

or you can use cflocation
<cfif session.somevar eq "">
<cflocation url-"sessionTimedOut.cfm">
</cfif>

session timeout -is- defined by client access of a particular session or as you call idle timeout.If you have session timeout set to 20 minutes and a client accesses the page once every 10 minutes, the session will never timeout (until the application times out)
>  Or to give another reason, i'd like the user to be displayed a reason for being "logged out", i.e. "Please sign in again as your session has expired", rather than just being told to "sign in", right now the system can't distinguish between the 2 cases.

You're correct that the session variable will no longer exist and you can use Sid's suggestion to test it.  However, if the variable does not exist, you first need to check if it's defined, not just that it's empty as that will throw an error.

<cfif NOT IsDefined("session.login_id")>
     <!---- show the login page ---->
</cfif>

Try adding a browser cookie to the login process to show the user has logged in.   It will not disappear when the session expires so you can then check it's existance to see if the user has been timed out or not.

On successful login, do this...
 <cfcookie name="loggedIn" value="true">  <!---- no expire, so cookie will go when browser closes ---->


Then modify your test to include the cookie check...
<cfif NOT IsDefined("session.login_id")>
   <cfif IsDefined("cookie.LoggedIn")  and cookie.loggedIn is true>
          <!---- Your session expired ---->
   <cfelse>
          <!---- first time login ---->
   </cfif>
</cfif>

Tip ...   In case the computer a shared by multiple users, you may want to remove the cookie in case another user comes along and never logs in.    So on the login screen (or the "you timed out screen")   you can get rid of the cookie..

 <cfcookie name="loggedIn" value="" expires="NOW">

of course gd is correct about the not isdefined ..that's the proper way to check if the session is still valid
Did this answer your question paddy?
Avatar of paddycobbett
paddycobbett

ASKER

Hi thanks guys, yes ofcourse, seems perfectly logical, i now see how to distinguish the 2 cases mentioned. Regarding however the first part of my question:

"However, the problem is that the session puts some data in the application scope which i would like to destroy when the SESSION timeouts also, so i need to detect when this occurs."

For this, i need to determine *which* session it was that timed out. Now, i did anticipate a comment (as SidFishes made) expressing concern about mixing SESSION and APPLICATION variables, which i know is not generally recommended, however i think in my case it is justified. What happens for example when data needs to be shared between sessions? Allow me to elaborate my case so that you can suggest a different design, or if it is justified in my case then suggest how to determine which session has expired.

To give an example. Say each user of my system is able to make sub-users, however they all share the same data of the main user. I have a class (component) system, which materialises the data (in the database) in to software objects. All interaction with the system is done with the objects, and no code directly accesses the database. So if simultaneous sub-users (from the same user) access the system simultaneously then they must use the SAME software objects (rather than creating them new from the db), otherwise they would be out of sync.

Currently this is what happens:
 1. sub-user A accesses system and the data is pulled from the database into software objects.
2. these objects are the pooled in the APPLICATION scope
3. sub-user B then accesses system and requests the same objects and so is given a reference to the same objects grabbed from the pool.

Now what i would like to do is, if a sub-users logs off and is the LAST sub-user OR the ONLY sub-user logged on for that "user account", then delete the pooled data in the APPLICATION scope. Essentially the data in the pool (in the APPLICATION scope) persists as long as there are users still using it (still needing it), however when all users for a particular "user account" are no longer on the system, that memory can be reclaimed.

Therefore i need to know how to determine *which* sub-user has just logged off, to then determine if that same "user account" is being used by other sub-users, because if not then i can "unpool" that data, to free up the RAM space on the server.

In a nut-shell, data needs to be shared across simultaneous sub-users, however it only needs to be available (in main memory atleast) as long as any user (currently logged on) needs it.

Thinking about it, no data is actually created in the SESSION scope, it is just referenced with the SESSION variable when retrieved from the pool in the APPLICATION scope, so this may explain the combination of the 2.

Any suggestions? Any idea how i can determine which session has just expired? I know application.cfc has a function called onSessionEnd() which would have been ideal (i think), but cannot use that for reasons explained. Please let me know if there's anything unclear. Many thanks.
Since the application scope is available to all users, not just a sub-set or group of users, perhaps you should move this solution to the database.  It's easy enough to create a table that will assign identifiers by group to various users and then delete the main record when the children are all gone.

There are some problems with tracking logouts as you'd like to do.  First, if the user does not explicity click logout, you may have some trouble ending their session.  The session will timeout, but without the application.cfc file you don't have anything there to execute when it happens, and if the user has already closed his browser, there is really nothing there at all to execute.   In other words, nothing will run if the user times-out unless the user is at his browser attempting to access another page.

One method you could use is to update the database table with the user's last "action date."   That is, every time the user loads a page, update the table with the current time stamp.  If the user doesn't load a page for X minutes, then you can close that record and log him off.   This would be done by perhaps a scheduled task which runs every few minutes and searches for timestamps older than X minutes and deleting them.  When the user tries to open his next page, the record is gone and thus he is "timed-out."

If the user closes his browser and walks away, the scheduled task cleans up his record after the allocated timeout period.
Thanks, yes as you rightly pointed out if a user was to always explicitly log out, then i could respond to that. My question was whether there was a way to determine that some other way, and you pointed out what i suspected that it isn't (apart from using application.cfc). Your suggestion seems reasonable to me, to update a record when a user performs any activity. This could even go in the application.cfm, and then have a scheduled task which polls user datetime activity values. I was concerned about server stress from this polling, but infact since i'm not fussed if an inactive user is detected *immediately*, it could be run with low priority at quite long intervals, which would ease my concern. How would you recommend i create the scheduled task?
Just clicked what you mean't by your first comment. Yes ofcourse, i wouldn't have to poll each user, i could poll groups of users as a whole! Like it :)
I''ve got some code I just put together for a Who's Online widget which might be of some use... I'll post it when I get to work
ASKER CERTIFIED SOLUTION
Avatar of gdemaria
gdemaria
Flag of United States of America image

Link to home
membership
This solution is only available to members.
To access this solution, you must be a member of Experts Exchange.
Start Free Trial
while gd's code is probably the best idea, this bit of code could probably be modified to do jsut what you asked. If you set the ActivityTimeOut to = session timeout then you would know pretty closely whether the user is active or not..

with each page load the activity timeout clock gets reset so you can know very closely who is active

have a look (it's at least useful as an example)
Application.cfc onRequest 
----------------------------------------
<!--- Begin Who's Online section --->
<!--- determines timeout to heck if user still is active. 
set to 0 will check on each page load and will cause list to change a lot as people "timeout"
while viewing or working on pages
set to 10 minutes will give a more general idea of activity.
remember that activity timeout and session timeout may be different.
   --->
<cfset ActivityTimeout = 0>		
<!--- if we still have a session id but have been cleared from the struct reset the struct
		this would be due to having an activity online timeout which is shorter than
		your session timeout. --->
		
	<!-- check if session exists -->
	<cfif structKeyExists(session,"whosOnFirst")>
	<!-- check if key in who's online exists -->
	<cfif NOT StructKeyExists(Application.UsersInfo, session.whosOnFirst)>
	  	<cfset temp = StructInsert(Application.UsersInfo, session.whosOnFirst, now())>
	 </cfif>
	 </cfif>
	 
  <cfloop collection="#Application.UsersInfo#" item="uName">
<!--- if the struct matches the session id then it's us so update our activity time --->
	<cfif structKeyExists(session,"whosOnFirst")>
	<cfif uName eq session.whosOnFirst>
	     <cfset user_cfid = uName>
		 <cfset user_time = Now()>
 	  	<cfset temp = StructUpdate(Application.UsersInfo, user_cfid, user_time)>
	</cfif>		
	</cfif>	
	<!--- look for values in the struct which are larger than ActivityTimeout 
	       and delete them as their timeout period will have expired.   --->
		<cfif Evaluate(DateDiff("n", StructFind(Application.UsersInfo, uName), Now())) GT ActivityTimeout>
	    	<cfset StructDelete(Application.UsersInfo, uName)>
		</cfif>
 </cfloop>	
<!--- end Who's Online section --->
 
Application.cfc onRequest in your successful login section
----------------------------------------
 
<!--- begin Who's Online section --->
<cfparam name="session.whosOnFirst" default="">
<!--- Test for existence of UserInfo and create if necessary  --->
 <cflock timeout="15" scope="APPLICATION" type="EXCLUSIVE">
    <cfif NOT isDefined("Application.UsersInfo")>
          <cfset Application.UsersInfo = StructNew()>
    </cfif>
</cflock>
<cfif isDefined('form.userName')>
	<!--- create new user info for the struct --->
	<cflock name="#CreateUUID()#" timeout="15" type="EXCLUSIVE">
	     <cfset user_cfid = Evaluate(CFID) & "," & form.userName>
		 <cfset user_time = Now()>
	</cflock>
	<!--- set a session id so we can use it to verify that this session is still active in application.cfm --->
	<cfset session.whosOnFirst = user_cfid> 
	<cflock scope="APPLICATION" type="EXCLUSIVE" timeout="15">
	<!--- If the user does not exist in the struct, insert it --->
	 <cfif NOT StructKeyExists(Application.UsersInfo, user_cfid)>
	  	<cfset temp = StructInsert(Application.UsersInfo, user_cfid, user_time)>
	 </cfif>
	</cflock>
 </cfif>
<!--- end Who's Online section --->
 
whosOnFirst.cfm
----------------------------------------------
<div style="padding:5px;">						
	<cflock scope="APPLICATION" type="EXCLUSIVE" timeout="10">
	    <cfoutput>
	        Users Online : #StructCount(Application.UsersInfo)#<br>
		<cfloop collection="#Application.UsersInfo#" item="uName">
			<cfif Uname eq session.whosOnFirst> 
				<div style="font-weight:bold;border-top:1px dashed;border-bottom:1px dashed;">#listlast(UCASE(Uname))#</div><!--- this is me --->
			<cfelse>
				<div>#listlast(UCASE(Uname))# <br>
				 <span style="font-size:.7em;">
					 Last Activity : #timeformat(structfind(Application.UsersInfo,uname), "hh:mm:ss")#
				</span></div>
			</cfif> 
		</cfloop>	
	    </cfoutput>
	</cflock>
</div>

Open in new window

Thanks for both posts and commitment to finding a resolution to my problem! Will take a look early next week since i will not be around over this weekend. Thanks again!
SOLUTION
Link to home
membership
This solution is only available to members.
To access this solution, you must be a member of Experts Exchange.
Start Free Trial
I've actually used a combination of the 2 solutions posed, you've both been very useful! Will try to update this post with my actual solution when it's finished, although i have enough info to close this question. Thanks again!