org.eclipse.jetty.server.session.SessionHandler Maven / Gradle / Ivy
Show all versions of ehcache Show documentation
//
// ========================================================================
// Copyright (c) 1995-2018 Mort Bay Consulting Pty. Ltd.
// ------------------------------------------------------------------------
// All rights reserved. This program and the accompanying materials
// are made available under the terms of the Eclipse Public License v1.0
// and Apache License v2.0 which accompanies this distribution.
//
// The Eclipse Public License is available at
// http://www.eclipse.org/legal/epl-v10.html
//
// The Apache License v2.0 is available at
// http://www.opensource.org/licenses/apache2.0.php
//
// You may elect to redistribute this code under either of these licenses.
// ========================================================================
//
package org.eclipse.jetty.server.session;
import java.io.IOException;
import java.util.Arrays;
import java.util.Collections;
import java.util.EnumSet;
import java.util.Enumeration;
import java.util.EventListener;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.CopyOnWriteArrayList;
import javax.servlet.AsyncEvent;
import javax.servlet.AsyncListener;
import javax.servlet.DispatcherType;
import javax.servlet.ServletException;
import javax.servlet.SessionCookieConfig;
import javax.servlet.SessionTrackingMode;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import javax.servlet.http.HttpSessionAttributeListener;
import javax.servlet.http.HttpSessionBindingEvent;
import javax.servlet.http.HttpSessionContext;
import javax.servlet.http.HttpSessionEvent;
import javax.servlet.http.HttpSessionIdListener;
import javax.servlet.http.HttpSessionListener;
import org.eclipse.jetty.http.HttpCookie;
import org.eclipse.jetty.server.Request;
import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.server.SessionIdManager;
import org.eclipse.jetty.server.handler.ContextHandler;
import org.eclipse.jetty.server.handler.ScopedHandler;
import org.eclipse.jetty.util.StringUtil;
import org.eclipse.jetty.util.annotation.ManagedAttribute;
import org.eclipse.jetty.util.annotation.ManagedObject;
import org.eclipse.jetty.util.annotation.ManagedOperation;
import org.eclipse.jetty.util.log.Log;
import org.eclipse.jetty.util.log.Logger;
import org.eclipse.jetty.util.statistic.CounterStatistic;
import org.eclipse.jetty.util.statistic.SampleStatistic;
import org.eclipse.jetty.util.thread.Locker.Lock;
import org.eclipse.jetty.util.thread.ScheduledExecutorScheduler;
import org.eclipse.jetty.util.thread.Scheduler;
import static java.lang.Math.round;
/* ------------------------------------------------------------ */
/**
* SessionHandler.
*/
@ManagedObject
public class SessionHandler extends ScopedHandler
{
final static Logger LOG = Log.getLogger("org.eclipse.jetty.server.session");
public final static EnumSet DEFAULT_TRACKING = EnumSet.of(SessionTrackingMode.COOKIE,SessionTrackingMode.URL);
/* ------------------------------------------------------------ */
/**
* Session cookie name.
* Defaults to JSESSIONID
, but can be set with the
* org.eclipse.jetty.servlet.SessionCookie
context init parameter.
*/
public final static String __SessionCookieProperty = "org.eclipse.jetty.servlet.SessionCookie";
public final static String __DefaultSessionCookie = "JSESSIONID";
/* ------------------------------------------------------------ */
/**
* Session id path parameter name.
* Defaults to jsessionid
, but can be set with the
* org.eclipse.jetty.servlet.SessionIdPathParameterName
context init parameter.
* If context init param is "none", or setSessionIdPathParameterName is called with null or "none",
* no URL rewriting will be done.
*/
public final static String __SessionIdPathParameterNameProperty = "org.eclipse.jetty.servlet.SessionIdPathParameterName";
public final static String __DefaultSessionIdPathParameterName = "jsessionid";
public final static String __CheckRemoteSessionEncoding = "org.eclipse.jetty.servlet.CheckingRemoteSessionIdEncoding";
/* ------------------------------------------------------------ */
/**
* Session Domain.
* If this property is set as a ServletContext InitParam, then it is
* used as the domain for session cookies. If it is not set, then
* no domain is specified for the session cookie.
*/
public final static String __SessionDomainProperty = "org.eclipse.jetty.servlet.SessionDomain";
public final static String __DefaultSessionDomain = null;
/* ------------------------------------------------------------ */
/**
* Session Path.
* If this property is set as a ServletContext InitParam, then it is
* used as the path for the session cookie. If it is not set, then
* the context path is used as the path for the cookie.
*/
public final static String __SessionPathProperty = "org.eclipse.jetty.servlet.SessionPath";
/* ------------------------------------------------------------ */
/**
* Session Max Age.
* If this property is set as a ServletContext InitParam, then it is
* used as the max age for the session cookie. If it is not set, then
* a max age of -1 is used.
*/
public final static String __MaxAgeProperty = "org.eclipse.jetty.servlet.MaxAge";
public Set __defaultSessionTrackingModes =
Collections.unmodifiableSet(
new HashSet<>(
Arrays.asList(new SessionTrackingMode[]{SessionTrackingMode.COOKIE,SessionTrackingMode.URL})));
@SuppressWarnings("unchecked")
public static final Class extends EventListener>[] SESSION_LISTENER_TYPES =
new Class[] {HttpSessionAttributeListener.class,
HttpSessionIdListener.class,
HttpSessionListener.class};
/**
* Web.xml session-timeout is set in minutes, but is stored as an int in seconds by HttpSession and
* the sessionmanager. Thus MAX_INT is the max number of seconds that can be set, and MAX_INT/60 is the
* max number of minutes that you can set.
*/
public final static java.math.BigDecimal MAX_INACTIVE_MINUTES = new java.math.BigDecimal(Integer.MAX_VALUE/60);
/**
* SessionAsyncListener
*
* Used to ensure that a request for which async has been started
* has its session completed as the request exits the context.
*/
public class SessionAsyncListener implements AsyncListener
{
private Session _session;
public SessionAsyncListener(Session session)
{
_session = session;
}
@Override
public void onComplete(AsyncEvent event) throws IOException
{
//An async request has completed, so we can complete the session
complete(((HttpServletRequest)event.getAsyncContext().getRequest()).getSession(false));
}
@Override
public void onTimeout(AsyncEvent event) throws IOException
{
}
@Override
public void onError(AsyncEvent event) throws IOException
{
complete(((HttpServletRequest)event.getAsyncContext().getRequest()).getSession(false));
}
@Override
public void onStartAsync(AsyncEvent event) throws IOException
{
event.getAsyncContext().addListener(this);
}
}
static final HttpSessionContext __nullSessionContext=new HttpSessionContext()
{
@Override
public HttpSession getSession(String sessionId)
{
return null;
}
@Override
@SuppressWarnings({ "rawtypes", "unchecked" })
public Enumeration getIds()
{
return Collections.enumeration(Collections.EMPTY_LIST);
}
};
/**
* Setting of max inactive interval for new sessions
* -1 means no timeout
*/
protected int _dftMaxIdleSecs=-1;
protected boolean _httpOnly=false;
protected SessionIdManager _sessionIdManager;
protected boolean _secureCookies=false;
protected boolean _secureRequestOnly=true;
protected final List _sessionAttributeListeners = new CopyOnWriteArrayList<>();
protected final List _sessionListeners= new CopyOnWriteArrayList<>();
protected final List _sessionIdListeners = new CopyOnWriteArrayList<>();
protected ClassLoader _loader;
protected ContextHandler.Context _context;
protected SessionContext _sessionContext;
protected String _sessionCookie=__DefaultSessionCookie;
protected String _sessionIdPathParameterName = __DefaultSessionIdPathParameterName;
protected String _sessionIdPathParameterNamePrefix =";"+ _sessionIdPathParameterName +"=";
protected String _sessionDomain;
protected String _sessionPath;
protected int _maxCookieAge=-1;
protected int _refreshCookieAge;
protected boolean _nodeIdInSessionId;
protected boolean _checkingRemoteSessionIdEncoding;
protected String _sessionComment;
protected SessionCache _sessionCache;
protected final SampleStatistic _sessionTimeStats = new SampleStatistic();
protected final CounterStatistic _sessionsCreatedStats = new CounterStatistic();
public Set _sessionTrackingModes;
protected boolean _usingURLs;
protected boolean _usingCookies=true;
protected Set _candidateSessionIdsForExpiry = ConcurrentHashMap.newKeySet();
protected Scheduler _scheduler;
protected boolean _ownScheduler = false;
/* ------------------------------------------------------------ */
/**
* Constructor.
*/
public SessionHandler()
{
setSessionTrackingModes(__defaultSessionTrackingModes);
}
/* ------------------------------------------------------------ */
@ManagedAttribute("path of the session cookie, or null for default")
public String getSessionPath()
{
return _sessionPath;
}
/* ------------------------------------------------------------ */
@ManagedAttribute("if greater the zero, the time in seconds a session cookie will last for")
public int getMaxCookieAge()
{
return _maxCookieAge;
}
/* ------------------------------------------------------------ */
/**
* Called by the {@link SessionHandler} when a session is first accessed by a request.
*
* @param session the session object
* @param secure whether the request is secure or not
* @return the session cookie. If not null, this cookie should be set on the response to either migrate
* the session or to refresh a session cookie that may expire.
* @see #complete(HttpSession)
*/
public HttpCookie access(HttpSession session,boolean secure)
{
long now=System.currentTimeMillis();
Session s = ((SessionIf)session).getSession();
if (s.access(now))
{
// Do we need to refresh the cookie?
if (isUsingCookies() &&
(s.isIdChanged() ||
(getSessionCookieConfig().getMaxAge()>0 && getRefreshCookieAge()>0 && ((now-s.getCookieSetTime())/1000>getRefreshCookieAge()))
)
)
{
HttpCookie cookie=getSessionCookie(session,_context==null?"/":(_context.getContextPath()),secure);
s.cookieSet();
s.setIdChanged(false);
return cookie;
}
}
return null;
}
/* ------------------------------------------------------------ */
/**
* Adds an event listener for session-related events.
*
* @param listener the session event listener to add
* Individual SessionManagers implementations may accept arbitrary listener types,
* but they are expected to at least handle HttpSessionActivationListener,
* HttpSessionAttributeListener, HttpSessionBindingListener and HttpSessionListener.
* @see #removeEventListener(EventListener)
*/
public void addEventListener(EventListener listener)
{
if (listener instanceof HttpSessionAttributeListener)
_sessionAttributeListeners.add((HttpSessionAttributeListener)listener);
if (listener instanceof HttpSessionListener)
_sessionListeners.add((HttpSessionListener)listener);
if (listener instanceof HttpSessionIdListener)
_sessionIdListeners.add((HttpSessionIdListener)listener);
addBean(listener,false);
}
/* ------------------------------------------------------------ */
/**
* Removes all event listeners for session-related events.
*
* @see #removeEventListener(EventListener)
*/
public void clearEventListeners()
{
for (EventListener e :getBeans(EventListener.class))
removeBean(e);
_sessionAttributeListeners.clear();
_sessionListeners.clear();
_sessionIdListeners.clear();
}
/* ------------------------------------------------------------ */
/**
* Called by the {@link SessionHandler} when a session is last accessed by a request.
*
* @param session the session object
* @see #access(HttpSession, boolean)
*/
public void complete(HttpSession session)
{
if (session == null)
return;
Session s = ((SessionIf)session).getSession();
try
{
s.complete();
_sessionCache.put(s.getId(), s);
}
catch (Exception e)
{
LOG.warn(e);
}
}
public void complete (Session session, Request request)
{
if (request.isAsyncStarted() && request.getDispatcherType() == DispatcherType.REQUEST)
{
request.getAsyncContext().addListener(new SessionAsyncListener(session));
}
else
{
complete(session);
}
//if dispatcher type is not async and not request, complete immediately (its a forward or an include)
//else if dispatcher type is request and not async, complete immediately
//else register an async callback completion listener that will complete the session
}
/* ------------------------------------------------------------ */
/*
* @see org.eclipse.thread.AbstractLifeCycle#doStart()
*/
@Override
protected void doStart() throws Exception
{
//check if session management is set up, if not set up HashSessions
final Server server=getServer();
_context=ContextHandler.getCurrentContext();
_loader=Thread.currentThread().getContextClassLoader();
synchronized (server)
{
//Get a SessionDataStore and a SessionDataStore, falling back to in-memory sessions only
if (_sessionCache == null)
{
SessionCacheFactory ssFactory = server.getBean(SessionCacheFactory.class);
setSessionCache(ssFactory != null?ssFactory.getSessionCache(this):new DefaultSessionCache(this));
SessionDataStore sds = null;
SessionDataStoreFactory sdsFactory = server.getBean(SessionDataStoreFactory.class);
if (sdsFactory != null)
sds = sdsFactory.getSessionDataStore(this);
else
sds = new NullSessionDataStore();
_sessionCache.setSessionDataStore(sds);
}
if (_sessionIdManager==null)
{
_sessionIdManager=server.getSessionIdManager();
if (_sessionIdManager==null)
{
//create a default SessionIdManager and set it as the shared
//SessionIdManager for the Server, being careful NOT to use
//the webapp context's classloader, otherwise if the context
//is stopped, the classloader is leaked.
ClassLoader serverLoader = server.getClass().getClassLoader();
try
{
Thread.currentThread().setContextClassLoader(serverLoader);
_sessionIdManager=new DefaultSessionIdManager(server);
server.setSessionIdManager(_sessionIdManager);
server.manage(_sessionIdManager);
_sessionIdManager.start();
}
finally
{
Thread.currentThread().setContextClassLoader(_loader);
}
}
// server session id is never managed by this manager
addBean(_sessionIdManager,false);
}
_scheduler = server.getBean(Scheduler.class);
if (_scheduler == null)
{
_scheduler = new ScheduledExecutorScheduler();
_ownScheduler = true;
_scheduler.start();
}
}
// Look for a session cookie name
if (_context!=null)
{
String tmp=_context.getInitParameter(__SessionCookieProperty);
if (tmp!=null)
_sessionCookie=tmp;
tmp=_context.getInitParameter(__SessionIdPathParameterNameProperty);
if (tmp!=null)
setSessionIdPathParameterName(tmp);
// set up the max session cookie age if it isn't already
if (_maxCookieAge==-1)
{
tmp=_context.getInitParameter(__MaxAgeProperty);
if (tmp!=null)
_maxCookieAge=Integer.parseInt(tmp.trim());
}
// set up the session domain if it isn't already
if (_sessionDomain==null)
_sessionDomain=_context.getInitParameter(__SessionDomainProperty);
// set up the sessionPath if it isn't already
if (_sessionPath==null)
_sessionPath=_context.getInitParameter(__SessionPathProperty);
tmp=_context.getInitParameter(__CheckRemoteSessionEncoding);
if (tmp!=null)
_checkingRemoteSessionIdEncoding=Boolean.parseBoolean(tmp);
}
_sessionContext = new SessionContext(_sessionIdManager.getWorkerName(), _context);
_sessionCache.initialize(_sessionContext);
super.doStart();
}
/* ------------------------------------------------------------ */
/*
* @see org.eclipse.thread.AbstractLifeCycle#doStop()
*/
@Override
protected void doStop() throws Exception
{
// Destroy sessions before destroying servlets/filters see JETTY-1266
shutdownSessions();
_sessionCache.stop();
if (_ownScheduler && _scheduler != null)
_scheduler.stop();
_scheduler = null;
super.doStop();
_loader=null;
}
/* ------------------------------------------------------------ */
/**
* @return true if session cookies should be HTTP-only (Microsoft extension)
* @see org.eclipse.jetty.http.HttpCookie#isHttpOnly()
*/
@ManagedAttribute("true if cookies use the http only flag")
public boolean getHttpOnly()
{
return _httpOnly;
}
/* ------------------------------------------------------------ */
/**
* Returns the HttpSession
with the given session id
*
* @param extendedId the session id
* @return the HttpSession
with the corresponding id or null if no session with the given id exists
*/
public HttpSession getHttpSession(String extendedId)
{
String id = getSessionIdManager().getId(extendedId);
Session session = getSession(id);
if (session!=null && !session.getExtendedId().equals(extendedId))
session.setIdChanged(true);
return session;
}
/* ------------------------------------------------------------ */
/**
* Gets the cross context session id manager
* @return the session id manager
*
*/
@ManagedAttribute("Session ID Manager")
public SessionIdManager getSessionIdManager()
{
return _sessionIdManager;
}
/* ------------------------------------------------------------ */
/**
* @return the max period of inactivity, after which the session is invalidated, in seconds.
* @see #setMaxInactiveInterval(int)
*/
@ManagedAttribute("default maximum time a session may be idle for (in s)")
public int getMaxInactiveInterval()
{
return _dftMaxIdleSecs;
}
/* ------------------------------------------------------------ */
@ManagedAttribute("time before a session cookie is re-set (in s)")
public int getRefreshCookieAge()
{
return _refreshCookieAge;
}
/* ------------------------------------------------------------ */
/**
* @return same as SessionCookieConfig.getSecure(). If true, session
* cookies are ALWAYS marked as secure. If false, a session cookie is
* ONLY marked as secure if _secureRequestOnly == true and it is a HTTPS request.
*/
@ManagedAttribute("if true, secure cookie flag is set on session cookies")
public boolean getSecureCookies()
{
return _secureCookies;
}
/* ------------------------------------------------------------ */
/**
* @return true if session cookie is to be marked as secure only on HTTPS requests
*/
public boolean isSecureRequestOnly()
{
return _secureRequestOnly;
}
/* ------------------------------------------------------------ */
/**
* HTTPS request. Can be overridden by setting SessionCookieConfig.setSecure(true),
* in which case the session cookie will be marked as secure on both HTTPS and HTTP.
* @param secureRequestOnly true to set Session Cookie Config as secure
*/
public void setSecureRequestOnly(boolean secureRequestOnly)
{
_secureRequestOnly = secureRequestOnly;
}
/* ------------------------------------------------------------ */
@ManagedAttribute("the set session cookie")
public String getSessionCookie()
{
return _sessionCookie;
}
/* ------------------------------------------------------------ */
/**
* A session cookie is marked as secure IFF any of the following conditions are true:
*
* - SessionCookieConfig.setSecure == true
* - SessionCookieConfig.setSecure == false && _secureRequestOnly==true && request is HTTPS
*
* According to SessionCookieConfig javadoc, case 1 can be used when:
* "... even though the request that initiated the session came over HTTP,
* is to support a topology where the web container is front-ended by an
* SSL offloading load balancer. In this case, the traffic between the client
* and the load balancer will be over HTTPS, whereas the traffic between the
* load balancer and the web container will be over HTTP."
*
* For case 2, you can use _secureRequestOnly to determine if you want the
* Servlet Spec 3.0 default behavior when SessionCookieConfig.setSecure==false,
* which is:
*
* "they shall be marked as secure only if the request that initiated the
* corresponding session was also secure"
*
*
* The default for _secureRequestOnly is true, which gives the above behavior. If
* you set it to false, then a session cookie is NEVER marked as secure, even if
* the initiating request was secure.
*
* @param session the session to which the cookie should refer.
* @param contextPath the context to which the cookie should be linked.
* The client will only send the cookie value when requesting resources under this path.
* @param requestIsSecure whether the client is accessing the server over a secure protocol (i.e. HTTPS).
* @return if this SessionManager
uses cookies, then this method will return a new
* {@link Cookie cookie object} that should be set on the client in order to link future HTTP requests
* with the session
. If cookies are not in use, this method returns null
.
*/
public HttpCookie getSessionCookie(HttpSession session, String contextPath, boolean requestIsSecure)
{
if (isUsingCookies())
{
String sessionPath = (_cookieConfig.getPath()==null) ? contextPath : _cookieConfig.getPath();
sessionPath = (sessionPath==null||sessionPath.length()==0) ? "/" : sessionPath;
String id = getExtendedId(session);
HttpCookie cookie = null;
if (_sessionComment == null)
{
cookie = new HttpCookie(
_cookieConfig.getName(),
id,
_cookieConfig.getDomain(),
sessionPath,
_cookieConfig.getMaxAge(),
_cookieConfig.isHttpOnly(),
_cookieConfig.isSecure() || (isSecureRequestOnly() && requestIsSecure));
}
else
{
cookie = new HttpCookie(
_cookieConfig.getName(),
id,
_cookieConfig.getDomain(),
sessionPath,
_cookieConfig.getMaxAge(),
_cookieConfig.isHttpOnly(),
_cookieConfig.isSecure() || (isSecureRequestOnly() && requestIsSecure),
_sessionComment,
1);
}
return cookie;
}
return null;
}
/* ------------------------------------------------------------ */
@ManagedAttribute("domain of the session cookie, or null for the default")
public String getSessionDomain()
{
return _sessionDomain;
}
/* ------------------------------------------------------------ */
@ManagedAttribute("number of sessions created by this node")
public int getSessionsCreated()
{
return (int) _sessionsCreatedStats.getCurrent();
}
/* ------------------------------------------------------------ */
/**
* @return the URL path parameter name for session id URL rewriting, by default "jsessionid".
* @see #setSessionIdPathParameterName(String)
*/
@ManagedAttribute("name of use for URL session tracking")
public String getSessionIdPathParameterName()
{
return _sessionIdPathParameterName;
}
/* ------------------------------------------------------------ */
/**
* @return a formatted version of {@link #getSessionIdPathParameterName()}, by default
* ";" + sessionIdParameterName + "=", for easier lookup in URL strings.
* @see #getSessionIdPathParameterName()
*/
public String getSessionIdPathParameterNamePrefix()
{
return _sessionIdPathParameterNamePrefix;
}
/* ------------------------------------------------------------ */
/**
* @return whether the session management is handled via cookies.
*/
public boolean isUsingCookies()
{
return _usingCookies;
}
/* ------------------------------------------------------------ */
/**
* @param session the session to test for validity
* @return whether the given session is valid, that is, it has not been invalidated.
*/
public boolean isValid(HttpSession session)
{
Session s = ((SessionIf)session).getSession();
return s.isValid();
}
/* ------------------------------------------------------------ */
/**
* @param session the session object
* @return the unique id of the session within the cluster (without a node id extension)
* @see #getExtendedId(HttpSession)
*/
public String getId(HttpSession session)
{
Session s = ((SessionIf)session).getSession();
return s.getId();
}
/* ------------------------------------------------------------ */
/**
* @param session the session object
* @return the unique id of the session within the cluster, extended with an optional node id.
* @see #getId(HttpSession)
*/
public String getExtendedId(HttpSession session)
{
Session s = ((SessionIf)session).getSession();
return s.getExtendedId();
}
/* ------------------------------------------------------------ */
/**
* Creates a new HttpSession
.
*
* @param request the HttpServletRequest containing the requested session id
* @return the new HttpSession
*/
public HttpSession newHttpSession(HttpServletRequest request)
{
long created=System.currentTimeMillis();
String id =_sessionIdManager.newSessionId(request,created);
Session session = _sessionCache.newSession(request, id, created, (_dftMaxIdleSecs>0?_dftMaxIdleSecs*1000L:-1));
session.setExtendedId(_sessionIdManager.getExtendedId(id, request));
session.getSessionData().setLastNode(_sessionIdManager.getWorkerName());
try
{
_sessionCache.put(id, session);
_sessionsCreatedStats.increment();
if (request.isSecure())
session.setAttribute(Session.SESSION_CREATED_SECURE, Boolean.TRUE);
if (_sessionListeners!=null)
{
HttpSessionEvent event=new HttpSessionEvent(session);
for (HttpSessionListener listener : _sessionListeners)
listener.sessionCreated(event);
}
return session;
}
catch (Exception e)
{
LOG.warn(e);
return null;
}
}
/* ------------------------------------------------------------ */
/**
* Removes an event listener for for session-related events.
*
* @param listener the session event listener to remove
* @see #addEventListener(EventListener)
*/
public void removeEventListener(EventListener listener)
{
if (listener instanceof HttpSessionAttributeListener)
_sessionAttributeListeners.remove(listener);
if (listener instanceof HttpSessionListener)
_sessionListeners.remove(listener);
if (listener instanceof HttpSessionIdListener)
_sessionIdListeners.remove(listener);
removeBean(listener);
}
/* ------------------------------------------------------------ */
/**
* Reset statistics values
*/
@ManagedOperation(value="reset statistics", impact="ACTION")
public void statsReset()
{
_sessionsCreatedStats.reset();
_sessionTimeStats.reset();
}
/* ------------------------------------------------------------ */
/**
* @param httpOnly
* The httpOnly to set.
*/
public void setHttpOnly(boolean httpOnly)
{
_httpOnly=httpOnly;
}
/* ------------------------------------------------------------ */
/**
* @param metaManager The metaManager used for cross context session management.
*/
public void setSessionIdManager(SessionIdManager metaManager)
{
updateBean(_sessionIdManager, metaManager);
_sessionIdManager=metaManager;
}
/* ------------------------------------------------------------ */
/**
* Sets the max period of inactivity, after which the session is invalidated, in seconds.
*
* @param seconds the max inactivity period, in seconds.
* @see #getMaxInactiveInterval()
*/
public void setMaxInactiveInterval(int seconds)
{
_dftMaxIdleSecs=seconds;
if (LOG.isDebugEnabled())
{
if (_dftMaxIdleSecs <= 0)
LOG.debug("Sessions created by this manager are immortal (default maxInactiveInterval={})",_dftMaxIdleSecs);
else
LOG.debug("SessionManager default maxInactiveInterval={}", _dftMaxIdleSecs);
}
}
/* ------------------------------------------------------------ */
public void setRefreshCookieAge(int ageInSeconds)
{
_refreshCookieAge=ageInSeconds;
}
/* ------------------------------------------------------------ */
public void setSessionCookie(String cookieName)
{
_sessionCookie=cookieName;
}
/* ------------------------------------------------------------ */
/**
* Sets the session id URL path parameter name.
*
* @param param the URL path parameter name for session id URL rewriting (null or "none" for no rewriting).
* @see #getSessionIdPathParameterName()
* @see #getSessionIdPathParameterNamePrefix()
*/
public void setSessionIdPathParameterName(String param)
{
_sessionIdPathParameterName =(param==null||"none".equals(param))?null:param;
_sessionIdPathParameterNamePrefix =(param==null||"none".equals(param))?null:(";"+ _sessionIdPathParameterName +"=");
}
/* ------------------------------------------------------------ */
/**
* @param usingCookies
* The usingCookies to set.
*/
public void setUsingCookies(boolean usingCookies)
{
_usingCookies=usingCookies;
}
/* ------------------------------------------------------------ */
/**
* Get a known existing session
* @param id The session ID stripped of any worker name.
* @return A Session or null if none exists.
*/
public Session getSession(String id)
{
try
{
Session session = _sessionCache.get(id);
if (session != null)
{
//If the session we got back has expired
if (session.isExpiredAt(System.currentTimeMillis()))
{
//Expire the session
try
{
session.invalidate();
}
catch (Exception e)
{
LOG.warn("Invalidating session {} found to be expired when requested", id, e);
}
return null;
}
session.setExtendedId(_sessionIdManager.getExtendedId(id, null));
//session.getSessionData().setLastNode(_sessionIdManager.getWorkerName()); //TODO write through the change of node?
}
return session;
}
catch (UnreadableSessionDataException e)
{
LOG.warn(e);
try
{
//tell id mgr to remove session from all other contexts
getSessionIdManager().invalidateAll(id);
}
catch (Exception x)
{
LOG.warn("Error cross-context invalidating unreadable session {}", id, x);
}
return null;
}
catch (Exception other)
{
LOG.warn(other);
return null;
}
}
/* ------------------------------------------------------------ */
/**
* Prepare sessions for session manager shutdown
*
* @throws Exception if unable to shutdown sesssions
*/
protected void shutdownSessions() throws Exception
{
_sessionCache.shutdown();
}
/* ------------------------------------------------------------ */
/**
* @return the session store
*/
public SessionCache getSessionCache ()
{
return _sessionCache;
}
/**
* @param cache the session store to use
*/
public void setSessionCache (SessionCache cache)
{
updateBean(_sessionCache, cache);
_sessionCache = cache;
}
/* ------------------------------------------------------------ */
/**
* @return true if the cluster node id (worker id) is returned as part of the session id by {@link HttpSession#getId()}. Default is false.
*/
public boolean isNodeIdInSessionId()
{
return _nodeIdInSessionId;
}
/* ------------------------------------------------------------ */
/**
* @param nodeIdInSessionId true if the cluster node id (worker id) will be returned as part of the session id by {@link HttpSession#getId()}. Default is false.
*/
public void setNodeIdInSessionId(boolean nodeIdInSessionId)
{
_nodeIdInSessionId=nodeIdInSessionId;
}
/* ------------------------------------------------------------ */
/**
* Remove session from manager
* @param id The session to remove
* @param invalidate True if {@link HttpSessionListener#sessionDestroyed(HttpSessionEvent)} and
* {@link SessionIdManager#expireAll(String)} should be called.
* @return if the session was removed
*/
public Session removeSession(String id, boolean invalidate)
{
try
{
//Remove the Session object from the session store and any backing data store
Session session = _sessionCache.delete(id);
if (session != null)
{
if (invalidate)
{
session.beginInvalidate();
if (_sessionListeners!=null)
{
HttpSessionEvent event=new HttpSessionEvent(session);
for (int i = _sessionListeners.size()-1; i>=0; i--)
{
_sessionListeners.get(i).sessionDestroyed(event);
}
}
}
}
//TODO if session object is not known to this node, how to get rid of it if no other
//node knows about it?
return session;
}
catch (Exception e)
{
LOG.warn(e);
return null;
}
}
/* ------------------------------------------------------------ */
/**
* @return maximum amount of time session remained valid
*/
@ManagedAttribute("maximum amount of time sessions have remained active (in s)")
public long getSessionTimeMax()
{
return _sessionTimeStats.getMax();
}
/* ------------------------------------------------------------ */
public Set getDefaultSessionTrackingModes()
{
return __defaultSessionTrackingModes;
}
/* ------------------------------------------------------------ */
public Set getEffectiveSessionTrackingModes()
{
return Collections.unmodifiableSet(_sessionTrackingModes);
}
/* ------------------------------------------------------------ */
public void setSessionTrackingModes(Set sessionTrackingModes)
{
_sessionTrackingModes=new HashSet<>(sessionTrackingModes);
_usingCookies=_sessionTrackingModes.contains(SessionTrackingMode.COOKIE);
_usingURLs=_sessionTrackingModes.contains(SessionTrackingMode.URL);
}
/* ------------------------------------------------------------ */
/**
* @return whether the session management is handled via URLs.
*/
public boolean isUsingURLs()
{
return _usingURLs;
}
/* ------------------------------------------------------------ */
public SessionCookieConfig getSessionCookieConfig()
{
return _cookieConfig;
}
/* ------------------------------------------------------------ */
private SessionCookieConfig _cookieConfig =
new CookieConfig();
/* ------------------------------------------------------------ */
/**
* @return total amount of time all sessions remained valid
*/
@ManagedAttribute("total time sessions have remained valid")
public long getSessionTimeTotal()
{
return _sessionTimeStats.getTotal();
}
/* ------------------------------------------------------------ */
/**
* @return mean amount of time session remained valid
*/
@ManagedAttribute("mean time sessions remain valid (in s)")
public double getSessionTimeMean()
{
return _sessionTimeStats.getMean();
}
/* ------------------------------------------------------------ */
/**
* @return standard deviation of amount of time session remained valid
*/
@ManagedAttribute("standard deviation a session remained valid (in s)")
public double getSessionTimeStdDev()
{
return _sessionTimeStats.getStdDev();
}
/* ------------------------------------------------------------ */
/**
* @return True if absolute URLs are check for remoteness before being session encoded.
*/
@ManagedAttribute("check remote session id encoding")
public boolean isCheckingRemoteSessionIdEncoding()
{
return _checkingRemoteSessionIdEncoding;
}
/* ------------------------------------------------------------ */
/**
* @param remote True if absolute URLs are check for remoteness before being session encoded.
*/
public void setCheckingRemoteSessionIdEncoding(boolean remote)
{
_checkingRemoteSessionIdEncoding=remote;
}
/* ------------------------------------------------------------ */
/** Change the existing session id.
*
* @param oldId the old session id
* @param oldExtendedId the session id including worker suffix
* @param newId the new session id
* @param newExtendedId the new session id including worker suffix
*/
public void renewSessionId(String oldId, String oldExtendedId, String newId, String newExtendedId)
{
try
{
Session session = _sessionCache.renewSessionId (oldId, newId); //swap the id over
if (session == null)
{
//session doesn't exist on this context
return;
}
session.setExtendedId(newExtendedId); //remember the extended id
//inform the listeners
if (!_sessionIdListeners.isEmpty())
{
HttpSessionEvent event = new HttpSessionEvent(session);
for (HttpSessionIdListener l:_sessionIdListeners)
{
l.sessionIdChanged(event, oldId);
}
}
}
catch (Exception e)
{
LOG.warn(e);
}
}
/* ------------------------------------------------------------ */
/**
* Called when a session has expired.
*
* @param id the id to invalidate
*/
public void invalidate (String id)
{
if (StringUtil.isBlank(id))
return;
try
{
//remove the session and call the destroy listeners
Session session = removeSession(id, true);
if (session != null)
{
_sessionTimeStats.record(round((System.currentTimeMillis() - session.getSessionData().getCreated())/1000.0));
session.finishInvalidate();
}
}
catch (Exception e)
{
LOG.warn(e);
}
}
/* ------------------------------------------------------------ */
/**
* Called periodically by the HouseKeeper to handle the list of
* sessions that have expired since the last call to scavenge.
*/
public void scavenge ()
{
//don't attempt to scavenge if we are shutting down
if (isStopping() || isStopped())
return;
if (LOG.isDebugEnabled()) LOG.debug("{} scavenging sessions", this);
//Get a snapshot of the candidates as they are now. Others that
//arrive during this processing will be dealt with on
//subsequent call to scavenge
String[] ss = _candidateSessionIdsForExpiry.toArray(new String[0]);
Set candidates = new HashSet<>(Arrays.asList(ss));
_candidateSessionIdsForExpiry.removeAll(candidates);
if (LOG.isDebugEnabled())
LOG.debug("{} scavenging session ids {}", this, candidates);
try
{
candidates = _sessionCache.checkExpiration(candidates);
for (String id:candidates)
{
try
{
getSessionIdManager().expireAll(id);
}
catch (Exception e)
{
LOG.warn(e);
}
}
}
catch (Exception e)
{
LOG.warn(e);
}
}
/* ------------------------------------------------------------ */
/**
* Each session has a timer that is configured to go off
* when either the session has not been accessed for a
* configurable amount of time, or the session itself
* has passed its expiry.
*
* @param session the session
*/
public void sessionInactivityTimerExpired (Session session)
{
if (session == null)
return;
//check if the session is:
//1. valid
//2. expired
//3. idle
boolean expired = false;
try (Lock lock = session.lockIfNotHeld())
{
if (session.getRequests() > 0)
return; //session can't expire or be idle if there is a request in it
if (LOG.isDebugEnabled())
LOG.debug("Inspecting session {}, valid={}", session.getId(), session.isValid());
if (!session.isValid())
return; //do nothing, session is no longer valid
if (session.isExpiredAt(System.currentTimeMillis()) && session.getRequests() <=0)
expired = true;
}
if (expired)
{
//instead of expiring the session directly here, accumulate a list of
//session ids that need to be expired. This is an efficiency measure: as
//the expiration involves the SessionDataStore doing a delete, it is
//most efficient if it can be done as a bulk operation to eg reduce
//roundtrips to the persistent store. Only do this if the HouseKeeper that
//does the scavenging is configured to actually scavenge
if (_sessionIdManager.getSessionHouseKeeper() != null && _sessionIdManager.getSessionHouseKeeper().getIntervalSec() > 0)
{
_candidateSessionIdsForExpiry.add(session.getId());
if (LOG.isDebugEnabled())LOG.debug("Session {} is candidate for expiry", session.getId());
}
}
else
_sessionCache.checkInactiveSession(session); //if inactivity eviction is enabled the session will be deleted from the cache
}
/* ------------------------------------------------------------ */
/**
* Check if id is in use by this context
*
* @param id identity of session to check
*
* @return true
if this manager knows about this id
* @throws Exception if any error occurred
*/
public boolean isIdInUse(String id) throws Exception
{
//Ask the session store
return _sessionCache.exists(id);
}
/* ------------------------------------------------------------ */
public Scheduler getScheduler()
{
return _scheduler;
}
/* ------------------------------------------------------------ */
/* ------------------------------------------------------------ */
/* ------------------------------------------------------------ */
/**
* SessionIf
*
* Interface that any session wrapper should implement so that
* SessionManager may access the Jetty session implementation.
*
*/
public interface SessionIf extends HttpSession
{
public Session getSession();
}
/* ------------------------------------------------------------ */
/* ------------------------------------------------------------ */
/* ------------------------------------------------------------ */
/**
* CookieConfig
*
* Implementation of the javax.servlet.SessionCookieConfig.
*/
public final class CookieConfig implements SessionCookieConfig
{
@Override
public String getComment()
{
return _sessionComment;
}
@Override
public String getDomain()
{
return _sessionDomain;
}
@Override
public int getMaxAge()
{
return _maxCookieAge;
}
@Override
public String getName()
{
return _sessionCookie;
}
@Override
public String getPath()
{
return _sessionPath;
}
@Override
public boolean isHttpOnly()
{
return _httpOnly;
}
@Override
public boolean isSecure()
{
return _secureCookies;
}
@Override
public void setComment(String comment)
{
if (_context != null && _context.getContextHandler().isAvailable())
throw new IllegalStateException("CookieConfig cannot be set after ServletContext is started");
_sessionComment = comment;
}
@Override
public void setDomain(String domain)
{
if (_context != null && _context.getContextHandler().isAvailable())
throw new IllegalStateException("CookieConfig cannot be set after ServletContext is started");
_sessionDomain=domain;
}
@Override
public void setHttpOnly(boolean httpOnly)
{
if (_context != null && _context.getContextHandler().isAvailable())
throw new IllegalStateException("CookieConfig cannot be set after ServletContext is started");
_httpOnly=httpOnly;
}
@Override
public void setMaxAge(int maxAge)
{
if (_context != null && _context.getContextHandler().isAvailable())
throw new IllegalStateException("CookieConfig cannot be set after ServletContext is started");
_maxCookieAge=maxAge;
}
@Override
public void setName(String name)
{
if (_context != null && _context.getContextHandler().isAvailable())
throw new IllegalStateException("CookieConfig cannot be set after ServletContext is started");
_sessionCookie=name;
}
@Override
public void setPath(String path)
{
if (_context != null && _context.getContextHandler().isAvailable())
throw new IllegalStateException("CookieConfig cannot be set after ServletContext is started");
_sessionPath=path;
}
@Override
public void setSecure(boolean secure)
{
if (_context != null && _context.getContextHandler().isAvailable())
throw new IllegalStateException("CookieConfig cannot be set after ServletContext is started");
_secureCookies=secure;
}
}
public void doSessionAttributeListeners(Session session, String name, Object old, Object value)
{
if (!_sessionAttributeListeners.isEmpty())
{
HttpSessionBindingEvent event=new HttpSessionBindingEvent(session,name,old==null?value:old);
for (HttpSessionAttributeListener l : _sessionAttributeListeners)
{
if (old==null)
l.attributeAdded(event);
else if (value==null)
l.attributeRemoved(event);
else
l.attributeReplaced(event);
}
}
}
/* ------------------------------------------------------------ */
/*
* @see org.eclipse.jetty.server.Handler#handle(javax.servlet.http.HttpServletRequest, javax.servlet.http.HttpServletResponse, int)
*/
@Override
public void doScope(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException
{
SessionHandler old_session_manager = null;
HttpSession old_session = null;
HttpSession existingSession = null;
try
{
old_session_manager = baseRequest.getSessionHandler();
old_session = baseRequest.getSession(false);
if (old_session_manager != this)
{
// new session context
baseRequest.setSessionHandler(this);
baseRequest.setSession(null);
checkRequestedSessionId(baseRequest,request);
}
// access any existing session for this context
existingSession = baseRequest.getSession(false);
if ((existingSession != null) && (old_session_manager != this))
{
HttpCookie cookie = access(existingSession,request.isSecure());
// Handle changed ID or max-age refresh, but only if this is not a redispatched request
if ((cookie != null) && (request.getDispatcherType() == DispatcherType.ASYNC || request.getDispatcherType() == DispatcherType.REQUEST))
baseRequest.getResponse().addCookie(cookie);
}
if (LOG.isDebugEnabled())
{
LOG.debug("sessionHandler=" + this);
LOG.debug("session=" + existingSession);
}
if (_nextScope != null)
_nextScope.doScope(target,baseRequest,request,response);
else if (_outerScope != null)
_outerScope.doHandle(target,baseRequest,request,response);
else
doHandle(target,baseRequest,request,response);
}
finally
{
//if there is a session that was created during handling this context, then complete it
HttpSession finalSession = baseRequest.getSession(false);
if (LOG.isDebugEnabled()) LOG.debug("FinalSession="+finalSession+" old_session_manager="+old_session_manager+" this="+this);
if ((finalSession != null) && (old_session_manager != this))
{
complete((Session)finalSession, baseRequest);
}
if (old_session_manager != null && old_session_manager != this)
{
baseRequest.setSessionHandler(old_session_manager);
baseRequest.setSession(old_session);
}
}
}
/* ------------------------------------------------------------ */
/*
* @see org.eclipse.jetty.server.Handler#handle(javax.servlet.http.HttpServletRequest, javax.servlet.http.HttpServletResponse, int)
*/
@Override
public void doHandle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException
{
nextHandle(target,baseRequest,request,response);
}
/* ------------------------------------------------------------ */
/**
* Look for a requested session ID in cookies and URI parameters
*
* @param baseRequest the request to check
* @param request the request to check
*/
protected void checkRequestedSessionId(Request baseRequest, HttpServletRequest request)
{
String requested_session_id = request.getRequestedSessionId();
if (requested_session_id != null)
{
HttpSession session = getHttpSession(requested_session_id);
if (session != null && isValid(session))
baseRequest.setSession(session);
return;
}
else if (!DispatcherType.REQUEST.equals(baseRequest.getDispatcherType()))
return;
boolean requested_session_id_from_cookie = false;
HttpSession session = null;
// Look for session id cookie
if (isUsingCookies())
{
Cookie[] cookies = request.getCookies();
if (cookies != null && cookies.length > 0)
{
final String sessionCookie=getSessionCookieConfig().getName();
for (int i = 0; i < cookies.length; i++)
{
if (sessionCookie.equalsIgnoreCase(cookies[i].getName()))
{
requested_session_id = cookies[i].getValue();
requested_session_id_from_cookie = true;
if (LOG.isDebugEnabled())
LOG.debug("Got Session ID {} from cookie",requested_session_id);
if (requested_session_id != null)
{
session = getHttpSession(requested_session_id);
if (session != null && isValid(session))
{
break;
}
}
else
{
LOG.warn("null session id from cookie");
}
}
}
}
}
if (requested_session_id == null || session == null)
{
String uri = request.getRequestURI();
String prefix = getSessionIdPathParameterNamePrefix();
if (prefix != null)
{
int s = uri.indexOf(prefix);
if (s >= 0)
{
s += prefix.length();
int i = s;
while (i < uri.length())
{
char c = uri.charAt(i);
if (c == ';' || c == '#' || c == '?' || c == '/')
break;
i++;
}
requested_session_id = uri.substring(s,i);
requested_session_id_from_cookie = false;
session = getHttpSession(requested_session_id);
if (LOG.isDebugEnabled())
LOG.debug("Got Session ID {} from URL",requested_session_id);
}
}
}
baseRequest.setRequestedSessionId(requested_session_id);
baseRequest.setRequestedSessionIdFromCookie(requested_session_id!=null && requested_session_id_from_cookie);
if (session != null && isValid(session))
baseRequest.setSession(session);
}
/**
* @see java.lang.Object#toString()
*/
@Override
public String toString()
{
return String.format("%s%d==dftMaxIdleSec=%d", this.getClass().getName(),this.hashCode(),_dftMaxIdleSecs);
}
}