How do I execute an authenticated AJAX request without resetting the tomcat's session timeout?
I'd go with a Grails filter that does something similar to what The-MeLLeR is proposing without the unnecessary loop through all sessions:
class AjaxTimeoutFilters { int sessionTimeout = 30 * 60 * 1000 private static final String TIMEOUT_KEY = 'TIMEOUT_KEY' def filters = { all(controller:'*', action:'*') { before = { if (request.xhr) { Long lastAccess = session[TIMEOUT_KEY] if (lastAccess == null) { // TODO return false } if (System.currentTimeMillis() - lastAccess > sessionTimeout) { session.invalidate() // TODO - render response to trigger client redirect return false } } else { session[TIMEOUT_KEY] = System.currentTimeMillis() } true } } }}
The session timeout should be dependency-injected or otherwise kept in sync with the value in web.xml.
There are two remaining issues. One is the case where there's an Ajax request but no previous non-Ajax request (lastAccess == null). The other is how to redirect the browser to a login page or wherever you need to go when there's an Ajax request after 30 minutes of no non-Ajax activity. You'd have to render JSON or some other response that the client would check to know that it's been timed out and do a client-side redirect.
Nope not possible...
One option is the following:
1) create a javax.servlet.Filter and store the timestamp of the last (non-ajax) pageview on the session.
2) create a javax.servlet.http.HttpSessionListener to store all the active sessions.
3) use a background thread to invalidate all expired sessions.
Sample Code:
import javax.servlet.*;import javax.servlet.http.*;import java.io.IOException;import java.util.ArrayList;import java.util.List;public class LastAccessFilter implements Filter, HttpSessionListener { private static final Object SYNC_OBJECT = new Object(); private static final String LAST_ACCESSED = "lastAccessed"; private boolean backgroundThreadEnabled; public void destroy() { synchronized (SYNC_OBJECT){ backgroundThreadEnabled = false; SYNC_OBJECT.notifyAll(); } } public void doFilter(ServletRequest req, ServletResponse resp, FilterChain chain) throws ServletException, IOException { if (req instanceof HttpServletRequest) { HttpServletRequest httpServletRequest = (HttpServletRequest) req; if(!isAjax(httpServletRequest)){ httpServletRequest.getSession().setAttribute(LAST_ACCESSED, System.currentTimeMillis()); } } chain.doFilter(req, resp); } public static boolean isAjax(request) { return "XMLHttpRequest".equals(request.getHeader("X-Requested-With")); } public void init(FilterConfig config) throws ServletException { Thread t = new Thread(new Runnable() { @Override public void run() { while (LastAccessFilter.this.backgroundThreadEnabled) { synchronized (SYNC_OBJECT) { try { SYNC_OBJECT.wait(3000); } catch (InterruptedException e) { e.printStackTrace(); } if (LastAccessFilter.this.backgroundThreadEnabled) { HttpSession[] sessions; synchronized (activeSessions){ sessions = activeSessions.toArray(new HttpSession[activeSessions.size()]); } cleanupInactiveSessions(sessions); } } } } private void cleanupInactiveSessions(HttpSession[] sessions) { for (HttpSession session : sessions) { Object lastAccessedObject = session.getAttribute(LAST_ACCESSED); if(lastAccessedObject == null) continue; long lastAccessed = (Long)lastAccessedObject; if(System.currentTimeMillis() > (lastAccessed + 1800000)){//30 Minutes session.invalidate(); } } } }); t.setDaemon(true); this.backgroundThreadEnabled = true; t.start(); } private final List<HttpSession> activeSessions = new ArrayList<HttpSession>(); @Override public void sessionCreated(HttpSessionEvent httpSessionEvent) { synchronized (activeSessions) { this.activeSessions.add(httpSessionEvent.getSession()); } } @Override public void sessionDestroyed(HttpSessionEvent httpSessionEvent) { synchronized (activeSessions) { this.activeSessions.remove(httpSessionEvent.getSession()); } }}