No this isn’t a test of the blog, or the beginning of some creepy horror movie. It’s about session management and connection timeout. Over the years we have tweak and improved our process considerably and shared bit and pieces in previous post and videos. But this will be a complete writeup and includes a sample project for you to download. You can also see the demo in action at timeout.wxperts.com
First a bit of background. We are talking about WebDev Session type sites (previously called Dynamic). When you publish the site, or via the WAS default value, you specify the timeout for a session. The default is 20 minutes.
This means that if a user sits on your page without doing anything, after 20 minutes, their session will be terminated, the site will generate an error and they will need to log back in and start over. Every time the project “touches” the server, the timeout is reset and the 20 minute count down starts over, so changing the page, or executing any server code resets the countdown.
This also means that from the time a user leaves your site, logs off, closes their browser, etc. the WAS server is maintaining that session for 20 minutes before it terminates it. That doesn’t sound like a lot, but on an active site that could easily mean 50-100 “vampire” sessions running, eating up memory and resources, even though the user has left the site.
The obvious first solution to this was to set the timeout to 5 minutes. But there are two problems with that solution. First your site has any complex pages, say an invoicing app, etc., it is possible that a user may be inactive for more than 5 minutes, and you haven’t met an angry user until you meet one that worked on filling out a complex form for 20 minutes, but has to do some research to complete the form and comes back to the page after 5 minutes to find out it has timed out and they have to start over!
There is a second and not so obvious 2nd issue with setting the timeout to 5 minutes. When the session times out, it displays an error and then after a few sessions it launches the first dynamic page of your site. Guess what that does, starts another session and the countdown all over. So if a user comes to your site and then decides to go for a walk, leave for the day, whatever, but leaves your site open in their browser they will continually being creating new sessions. Not ideal to say the least!
The answer to both of these issues is “Are you still there?” logic combined with an AWP page. Almost every online banking site has similar logic, after so many minutes of inactivity, a popup ask “Are you still there?” and if you don’t answer in a few seconds, you are logged off. When you are logged off, we direct you to an AWP page, which doesn’t start a new session, which solves the second issue from above.
So let’s did into the sample project and see how it all works. First a warning, I am using our File Manager class for this project, because I am lazy and all the projects where we have this code running are using it, but it will be very easy for you to adjust it for standard Hxxxx commands if you “must”
The heart of this logic is one additional database table, called current connection.
This is the table that we will use to track the connected users. Unfortunately we don’t have access to the session tracking the WAS is already doing so we create our own to work side by side with it.
UserType is just a string field, we use for informational purposes to distinguish internal/company users from external/public users, and really doesn’t have anything to do with the logic.
WebUserID links to our actual User security table and UserName is pulled from that table so we can use it for display, elsewhere.
IPAddress, BrowserType, BrowserVersion, and BrowserPlatform are all populated using standard WebDev commands are are just for information purposes.
CurrentPage tells us what page the user is on, and is part of our timeout logic as you will see shortly.
SessionBegan is when the user first logged in and is again just for informational purposes.
LastActivity is the last time the user did something, and is part of our timeout logic
And finally SessionEnd is set when the user actually logged off, which by the way NEVER happens, most users just close the browser tab these days. It is just for informational purposes
You can of course tweak the table design as needed to fit your project. Now lets take a look at the other project elements.
Let’s take a quick look at the Project Code
Line 2-7 are setting up the database connection, Line 9 makes the connection, and line 10 let’s the FileManger classes know about the connection
Line 12 is our techinque for declaring local varaibles and keeping them organized.
Line 18, is where some of the magic is. It settings the timer for “touching” the server. The WAS session timeout should be 1 minute longer than this setting to avoid any timing issues. I normally have this set for 5 minutes, and the server set for 6 minutes. But for the demo I have it set for 1 minute and the server for 2 minutes, so you don’t have to wait so long to see the effect.
Line 24 is the other half of the magic, it triggers the “Are you still there?” popup after X times of firing the KeepAlive without any activity. I normally have this set to 4, which means after 4 times (20 minutes) the popup appears.
You may be wondering why this two global variables are not declare in the Global class with the rest of my global variables. The reason is we need to access these variables from browser code and we can’t access Class variables, but we can access global server variables.
Let’s take a look at that Global Class
Lines 4-6 are used to track the current user, and in a “real” project would be part of my current user record class, but are just hardcoded global variables for this demo.
Line 9-10 are the variable used for the session management. The first two are for the File Manager class, the rec holds the CurrentConnection record for the user once he logs in and mgr is used for updating the record, etc.
LastPageChange is where the “real” magic is. That is what is used when deciding if we have reached a timeout as you will see once we review that code.
You will notice there are 2 template. tplBase and tplInternalPages. tplBase is just the basic layout of the site, and then tplInternalPages is based on the tplBase tempalte. If you follow my design philosophy, you know that is the way I always recommend doing the design, that way if I want to change something in the basic layout, such as the logo, I just need to do it on the tplBase and it will flow through to the entire rest of the site.
tplBase doesn’t have any code, and doesn’t do any session management or anything so nothing for us to really look at on it. So let’s move on to tplInternalPages.
tplInternalPages as the name implies, is use for all the pages of the site once the user logs into the site. It has all the session management code, so in turn all the internal pages will have it as well.
There are a few lines of browser code in the Load event of the template
Line 1 is just creating a global browser variable that we will use to track the 30 second count down timer.
Line 2 makes a call to the KeepAliveBrowser procedure, and Line 3 sets up a timer to call the same procedure every X minutes. Note: the X minutes is base on the value we set in the project code, for this demo that’s 1 minute.
We will look at the KeepAliveBrowser procedure shortly, but first let’s look at the popup, that is on the template.
The code behind the Yes button is:
The browser code is simply closing the popup and ending the 30 second countdown and the server code is setting the LastPageChange to the current time, which effectively restarts the timeout logic.
The code behind the No button, which is automatically press when the 30 second countdown completes, is:
This code deletes the current connection record, clears all the global variables, and redirects to the ShowMessage page which is an AWP page and will effectively severs the connection to the WAS session.
Now lets look at the KeepAliveBrowser procedure, which is a local browser procedure of the template
We use AJAXExecute to execute the KeepAliveAJAX procedure, which we will review shortly. If the procedure returns a one then we display the “Are you still there?” popup set the countdown static to 30 and setup a timer that runs the AutoLogoffCountDown browser procedure every second.
When we review the KeepAliveAJAX procedure you will see it is actually returning a boolean value, however AJAX only supports strings being returned, which is why I have the 1 in quotes.
Now lets look at the AutoLogoffCountDown browser procedure which is another local browser procedure on the template and the last bit of code to review on the template.
This code simply counts down 30 seconds, really what ever we set stcCountDown to in the KeepAliveBrowser procedure. At the end of 30 seconds it ends the timer, and “presses” the No button.
That is it for the template, now lets look at the KeepAliveAJAX server procedure that is called from the KeepAliveBrowser procedure.
Note the green circle next to AJAX in the header, indicating this procedure has been enabled for calling via AJAXExeucte.
Line 4 sets up the boolean variable we will be returning and defaults it to False, meaning to not trigger the “Are you still there” popup.
The IF on Line 5 determines if a session has been started (meaning a record has been created in the CurrentConnection table), if not Line 23 calls the class method StartSession to create one. For the non OOPified, that’s just a fancy way to say procedure 🙂 We will look at that method in a bit
If a session has been started then Line 7 see’s if the user is still on the same page they were last time. If not Line 16 changes LastPageChange to the current time, effectively resetting the timeout logic. If the user is still on the same page, then Line 8 determines how long the user has been on that page and then line 10 determines if the user has been long enough to trigger the “Are you still there” popup, and if so line 12 sets the boolean being returned to true.
Line 18 and 19, set the Last Activity time, and the current page on the Current Connection record and Line 20 updates the record.
Finally Line 26 returns the boolean.
Let’s go over to the CurrentConnectionManager class and take a look at the StartSession method that was called on line 23 of KeepAliveAJAX
It’s a lot of code, by WL standards at least, but all it is doing is creating a CurrentConnection record and returning that record. Line 4-6 use the Global variables holding the info about the current logged in user. Line 9-61 are using a few standard WebDev functions to populate some of the fields, then Line 63 is setting the current page using the MyPage..Name property, 64 and 65 Set the SessionBegan and Last Activity to the current time, and Line 66 create the record (the FileManager version of Hadd). Line 67 set the global LastPageChange variable to the current time, initializing the timeout logic, and finally 68 returns the created record.
There is one other method in the CurrentConnectionManager class we need to take a look at, ClearOldSessions
This simply delete’s all of the Current Connection Records have haven’t had any activity in X minutes. X is determined by adding 1 to the Global KeepAliveMinutes variable. If you have been paying attention, you know that this would also correspond to the session timeout on the WAS server. So what we are doing is removing any CurrentConnection records who’s session has been been terminated by the WAS.
Note: You could eliminate this code and keep the records for historical/analysis purposes, but we like it to only have records in it that represent the actual sessions living on the server at the moment.
You may be wondering how that ClearOldSession method ever gets ran, if you take a look at the Global Server Procedures you will notice there is one with the exact same name.
That looks pretty simple, and perhaps redundant. But notice the like clock icon and the 1:30 icons. The 1:30 indicates that this is an automated procedure and the clock icon indicates that it is a WebDev task. If you click on the 1:30 at the far right of the header line, you will get a configuration window for this
I have this configured as a WebDev task that runs once every minute. WebDev task run on the server, independent of users. Think of it as basically the WAS server runs the procedure on a timer. The advantage to this is, if we set it up as a normal automated task, then every active user would be running the task once a minute. Now how that would effect performance when you have 150 active users. Don’t ask me how I know this 🙂
You can assign a class method as a WebDev task, so we have to create a global procedure to call the method, and set that procedure up as a WebDev task.
We are almost done, I promise 🙂
The first dyanmic page of the site is the Login page, lets take a look at it. Not the most secure site, since I just give you buttons to login as 1 of 3 users, but that all we need for the demo.
The first thing to note is this page is based on tplBase, because we don’t want session management on this page. It would seem stilly to task if they are still there while sitting on the login page, wouldn’t it?
So that means the only code on this page is the login code behind the buttons. Lets look at the first button.
Line 2-4 set the global variable that hold the user information, and would of course normally involve you standard code to check the user id, password, etc.
Line 8 makes sure there isn’t already a session, there shouldn’t be, but those that know me, know I am a big fan of belt and suspenders, both in real life and in my code 🙂
Line 9 starts the session, and we have already seen that code, and line 12 launches the menu page.
That’s it, because we built all of our session management into the class and the templates, there just isn’t a lot for us to do on the actual pages.
There is no extra code on the MainMenu page or Page1. They are there just to simulate navigating a site.
The final page is the ShowMessage page. We referred to it back when we looked at the code behind the No button on the “Are you Still there” popup. The most important thing about this page is that it is an AWP page, to avoid creating a session on the WAS server, as discussed above
The page is based on the tplBase template because we definitely don’t want any session management logic on the page.
The only code on the page is accepting an incoming parameter and changing the static to whatever was passed in on that parameter
And the Log Back In button relaunches the website.
And we have finally reach the end, that is all the logic required for an “Are you still there?” feature similar to what is used on online banking sites, etc.
As promised here is a link to download the project from my Dropbox
I am sure some of you already have your brain in overdrive thinking of things you can do to extend and/or use this code beyond this demo. Here are a few examples that we have in production.
You can use the CurrentConnection table to control the number of users logged in, or to prevent a user from logging in multiple times, or from multiple locations. This can come in handy if your licensing model is based on concurrent users, as you can prevent everyone logging in with the same userid. However it can get tricky, for example if a user loses their connection, they can’t log in again until their existing current connection records expires, you might think you could use the IP as part of the logic, but since most business users are behind a firewall, all the users for a company will likely have the same IP address.
You can add an administration page that display the current users, which can be handy for support calls as you can see what page they are on, etc. You could even extend this further to incorporate a message facility into the CurrentConnection record, especially with the use of WebSockets.
If you maintain the records, instead of deleting them when the expire, you could use the information for all kinds of analytics.
But those are all topics for future post. If you extend the logic for a use I haven’t mentioned please be sure to let everyone know via the comments for this post!
Edit: August 31st, 2020 – Page Display Dialog
One of our clients discovered an issue with the ShowMessage call, if you have a page that is opened via PageDisplayDialog (thanks Femi!). The issue is when it tries to do a PageDisplay from a page that was opened via PageDisplayDialog, it generates an error due to the nature of PageDiplayDialog
To correct the issue we created a new page template to be used with our PageDisplayDialog pages, called tplDialog. The example app now has an extra button to open a page via PageDisplayDialog to show the new template in action.
The only real difference is how we call the ShowMessage page. Instead of calling it via PageDisplay, we use FileDisplay from the browser code to call it the same as we would an external page from another site.
The template is the same as the tplIntenalPages, with the exception of:
- it has a border, so our page being opened with PageDisplayDialog, appears similar to a popup.
- In the LOAD browser event there is an additional variable that allows you to set the URL of your ShowMessage page, this is the complete URL as if you were typing it in a browser URL. Note: when testing local this would still be opening the page from your URL unless you added code to use localhost when testing
AreYouThereTimer is int KeepAliveBrowser() Timer(KeepAliveBrowser,KeepAliveMinutes * 6000) ShowMessageURL is string = "http://timeout.wxperts.com/us/ShowMessage.awp"
- And there a a few changes to the code behind the “No Log Me Off” button on the popup. First we make it AJAX enabled, so we can have a browser event fire after the server event.
- And we build the full URL with the new browser variable just created and the message then use FileDisplay function to open the ShowMessage page.
tmpString is string = ShowMessageURL + "?P1=" + URLEncode("Your Session has expired, please log in again to continue") FileDisplay(tmpString)
- Note: We really wouldn’t need any server code, expect we want to delete the connection record from our database. If you aren’t doing that you could have your FileDisplay code in the onclick event instead