GWT/GXT and Session Timeouts
One of the applications we develop at work is a GWT/GXT web application hosted in tomcat. Recently we started getting complaints about strange behavior when the user’s session timed out. The RPC calls would fail so all sorts of strange error messages would popup, but the user was not redirected back to the login page.
This strange behavior was occurring because, after the initial application load, the only traffic to the server were our RPC calls. When an RPC call is made after the session timeout the call simply fails. The redirect from tomcat is ignored.
I searched the web for any solutions and came across this solution on the EXT-JS forum. The general idea was to setup a couple of timers after a user logs in. One timer will show the user a warning when the session is about to expire, the other timer will show a “Session Expired” dialog and close the browser when the user presses “OK”.
The problem with that implementation is that it doesn’t take into account user activity. So, if the session timeout is set to 30 minutes, the warning dialog would pop up at 29 minutes after the user logs in – regardless of whether the user was active for those 29 minutes. This behavior would be rather annoying to our users. So, I decided to modify this solution to make it a bit less annoying.
In my modified solution, the user does not get a “Session is about to expire” warning. Whenever the session times out a popup message alerts the user that their session has expired. When they click “OK” they are redirected back to the login page.
The basic algorithm is this:
- On GWT module load an RPC call gets the user session timeout in milliseconds.
- A timer is setup to run the RPC call again a few seconds after the timeout.
- If the call succeeds, the user has been active, so the timer is setup again. Otherwise, the session timeout dialog is shown.
Client side
public class Main implements EntryPoint { private Timer sessionTimeoutResponseTimer; private MyServiceAsync service; /** * Added to the first session timeout check to allow for startup time */ private final int INITIAL_TIMEOUT_PAD = 60000; /** * Added to the session timeout check timer. */ private final int TIMEOUT_PAD = 15000; /** * Entry point method. */ public void onModuleLoad() { initSessionTimers(); buildUi(); } private void initSessionTimers() { service = (MyServiceAsync) GWT.create(MyService.class); ((ServiceDefTarget) service) .setServiceEntryPoint(GWT.getModuleBaseURL() + "gwt/myService"); service.getUserSessionTimeout(new AsyncCallback<Long>() { public void onSuccess(Long timeout) { sessionTimeoutResponseTimer = new Timer() { @Override public void run() { checkUserSessionAlive(); } }; sessionTimeoutResponseTimer.schedule(timeout+INITIAL_TIMEOUT_PAD); } public void onFailure(Throwable caught) { displaySessionTimedOut(); } }); } private void checkUserSessionAlive() { service.getUserSessionTimeout(new AsyncCallback<Long>() { public void onSuccess(Long timeout) { sessionTimeoutResponseTimer.cancel(); sessionTimeoutResponseTimer.schedule(timeout+TIMEOUT_PAD); } public void onFailure(Throwable caught) { displaySessionTimedOut(); } }); } private void displaySessionTimedOut() { MessageBox.alert( "Session Timeout", "Your session has timed out.", new Listener<MessageBoxEvent>() { public void handleEvent(MessageBoxEvent be) { Window.Location.reload(); } }); } }
Server side
public class MyServiceImpl extends RemoteServiceServlet implements MyService { @Override public Integer getUserSessionTimeout() { int timeout = getThreadLocalRequest().getSession() .getMaxInactiveInterval() * 1000; return timeout; } }
Note: The method getMaxInactiveInterval() returns the session timeout in seconds that is configured in web.xml. The session timeout in web.xml is specified in minutes.
public interface MyService extends RemoteService { Integer getUserSessionTimeout(); } public interface MyServiceAsync extends RemoteService { void getUserSessionTimeout(AsyncCallback<Integer> callback); }
There is one small problem with this approach. It may keep the session alive longer than the session timeout. To illustrate this, lets say that the session timeout is set to 30 minutes. When the user logs in, a timer is setup to make an RPC call 31 minutes after the user logged in. Let’s say the user works for 15 minutes then switches to something else. At 31 minutes after logging in, the timer will cause an RPC call to be made, which resets the session timeout countdown on the server side. 30 minutes later the timer will then make the call again. This time the session will have timed out and the “Session Timeout” alert is displayed. So, even though the session timeout was set for 30 minutes, the user was inactive for 45 minutes before the session timed out. I suppose with a little thought and another timer you could come up with a way to make the session timeout after exactly 30 minutes of inactivity. Perhaps that will be a topic for future post.