Current Session Handling Issues
- Session timeouts during an Ajax request causes error response (which can result in blank pages for the user)
- Session timeouts cause the form to be lost which could result in lost data
Note session handling relies on history management, for more information see History and Breadcrumbs
The ViewSessionPolicy object will hold configuration related to session handling. The View component will contain a UserSessionPolicy property, therefore policies can be scoped to a particular view or set of views.
- Enum timeoutAction - indicates the action that should occur for a session timeout. Valid options are:
- REDIRECT_TO_FLOW_START - redirect to the initial get request of the current flow. For example, on a document this would request a new document instance. On a wizard this would request the first page of the wizard
- REDIRECT_TO_URL - redirect to the URL given by the property redirectUrl
- SHOW_TIMEOUT_VIEW - render the session timeout view. This will be a view with id 'Uif-SessionTimeoutView' that can be customized as needed
- PERFORM_REFRESH - changes the method to call to 'refresh'. For example if a save was requested and a session timeout has occurred, the refresh method will be invoked instead. By default this just redisplays the view but can be overridden by the controller
- REQUEST_AS_NORMAL - continues the request as normal (does nothing to modify the request)
- String redirectUrl - url for redirecting when the timeoutAction is REDIRECT_TO_URL (for example the application home URL)
- boolean persistFormToDataStore - indicates when a session times out if the form should be stored to a date store. A call will be made to the DataObjectService to persist the form with the associated data provider (initially this will be a relational database). This allows requests for forms that have been cleared from memory (session) to resume as normal.
- boolean showTimeoutWarning - indicates whether the session timeout should be tracked on the client to give the user a notification when the session is about to timeout. The user can keep the session alive through the dialog
Detecting Session Timeout
A session timeout will be detected based on a session key. When the user's session is created a unique session id will be created and stored with the user session. For all views the session id will be maintained as a hidden. When the view is submitted or requested with a given session id and that session id does not match the id on the current session (note in the case of a timeout a new session will be created before this logic executes), the session timeout policy will be enforced. Likewise the absence of a form for a given requested form key will be treated like a session timeout.
If persistFormToDataStore is true for the view session policy, before the session is timed out the form will be persisted through the data object service. This will be done through a session listener, so the form will not be persisted for each request.
Callbacks will be implemented on UifFormBase for preparing the form before and after persistence. The method invocations are as follows:
Session Timeout -> Session Listener -> Form Instance#prepareFormForStorage() -> Session Service#storeForm
When a later request is made for the form, the method invocations are as follows:
Binding Interceptor -> Session Service#getForm -> Form Instance#rebuildFormFromStorage() -> Binding -> Controller Invocation
For standard browser requests when a timeout occurs the user will be forwarded to the login page. Once the login is made the request is resubmitted (and the view session policy then enforced).
With ajax calls however it cannot be determined whether the request has been forwarded to the login page. Furthermore, since in Rice we do not have control over the login filter we cannot write anything back that will be detected. Therefore our only option is to put a filter before the login filter. This filter will determine if the session has been established, and if not and the call was made via ajax send back an error code.
The ajax caller will then pick up the error code and send a request to the UIF listener controller login method. This is to trigger the login page. The contents of the login page will then be rendered in a lightbox (this is so the original view and context will be maintained). Submitting the login will invoke the listener login method again (creating the user's session) which will render a view that onload simply closes the lightbox and invokes a method on the parent to trigger the initial ajax call. Since the parent view has not refreshed, the initial ajax call can be kept in memory and resubmitted.
The end result of this is the user will see the login form appear in a lightbox, then closed and the ajax request made. What happens next will depend on the view session policy (as does for standard browser requests). Either the user will be redirected, shown an error view, or the request will complete as normal (for example changing pages).
Note: this timeout behavior for ajax timeouts can be modified by overriding the Ajax error handler or the timeout handler on the KRAD request object.
Question: Should we do this for value added ajax? For example, ajax queries, suggest? Or in these cases simply write a message to the page stating the session has timed out and the request could not be completed?
Managing Session Client Side
Switching to managing session timeouts client side would bring a few nice benefits:
- User activity and form clearing. By implementing a Heartbeat (simple ajax call to the server to indicate the user is still active) we can do a better job of clearing forms. If a form does not receive a heartbeat, we can assume the user has closed the window (or navigated away) and clear the form.
- Managing session client side will prevent the server side session from timing out if the user is still active in the view (active in the client but has not made a server request)
- The user can be notified their session has expired before they make a request
To implement this first a Heartbeat will be implemented (configured for a certain interval, ex 30s). Note this will keep the server side session alive indefinitely.
A client side timer will be implemented based on the configured session timeout. Each time a major event occurs the timeout will be reset. A major event includes items such as a server request, focus event, mouse move (might be too chatty), ... If the timer expires a request will be made to clear the session server side. At this point a dialog can be shown letting the user know their session has expired.
Question: Should we offer this as an option?
Back Button Form State
The use of the back button and browser caching can cause issues with form state. For example typically when the back button is used the browser will attempt to present the contents from cache for the last get. If a cached paged is found and used, this might not reflect the current state of the form from the session. This is because the cache paged is not updated from a post (at least that is what is cited in most documentation, although from testing that doesn't always seems to be the case) nor updated from Ajax requests.
As an example suppose a user requests a new document instance whose form is assigned the id 1. The user fills out data and invokes the save action. Then they navigate to another view, and then click the back button. Here they might get the cache from the initial get request for the document. It might look like a new document instance to the user, but it will still have form id of 1 in the DOM. The client data for this form is now out of sync from the server.
One possibility here it to implement token checking to determine if the page is being presented from cache. We could then force a call to post the form and call the refresh method, which generally just redisplays the view. This would pick up the updated form data.
Caching might not be a concern for most Kuali applications running on https. When using https browsers do not cache pages (need to verify this in an https environment). Although this prevents the form sync issue, it could be a frustration to users. For example suppose a user performs a lookup that brings back search results, they then click an edit link that navigates them to a maintenance document. If they now use the back button, the browser would request the initial get URL again showing the user a new lookup view (without the previous results).
For this problem we might be able to take advantage of the history API and push a refresh call on the previous view into history (when the user enters a new view). When they use the back button now, it will trigger the refresh for the previous view which will pick up the latest form data. So on the lookup example the user would see their previous search results.
Browsers like Firefox have auto-complete features that keep the users data entered into the form on a reload. More information can be found here: http://stackoverflow.com/questions/7377301/firefox-keeps-form-data-on-reload
Should we add the necessary jQuery to fix this?