开发者

How do I execute an authenticated AJAX request without resetting the tomcat's session timeout?

I've got an existing Grails Web application that is in production and has a 30 minute session timeout. We are running Tomcat (tcServer).

When a user is authenticated and on certain pages I want to make some periodic polling ajax requests to the server that do not extend this 30 minute session timeout - so that our session timeout isn't thwarted.

The question is similar to this unanswered asp.net question, but none of the answers there will do and this in the Java/Tomcat realm.

How do I execute an authenticated AJAX request开发者_JAVA技巧 without resetting the tomcat's session timeout?

Is there some sort of filter or url-matching mechanism that I can use to exclude requests from extending the 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());
        }
    }
}
0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜