HTTP Session Management
Sessions are a concept within the Servlet API which allow requests to store and retrieve information across the time a user spends in an application. Jetty provides a number of pluggable options for managing sessions. In this section we’ll look at the architecture of session support in Jetty, review the various pluggable options available and indicate what and how to customize should none of the existing options suit your usecase.
Session Architecture
- Terminology
-
- SessionIdManager
-
is responsible for allocation of session ids
- HouseKeeper
-
is responsible for orchestrating the detection and removal of expired sessions
- SessionHandler
-
is responsible for managing the lifecycle of sessions within its associated context
- SessionCache
-
is an L1 cache of in-use
Session
objects - Session
-
is a stateful object representing a
HttpSession
- SessionData
-
encapsulates the attributes and metadata associated with a
Session
- SessionDataStore
-
is responsible for creating, storing and reading
SessionData
- CachingSessionDataStore
-
is an L2 cache of
SessionData
The session architecture can be represented like so:
The SessionIdManager
There is a maximum of one SessionIdManager
per Server
instance.
Its purpose is to generate fresh, unique session ids and to coordinate the re-use of session ids amongst co-operating contexts.
The SessionIdManager
is agnostic with respect to the type of clustering technology chosen.
Jetty provides a default implementation - the DefaultSessionIdManager - which should meet the needs of most users.
If you do not explicitly configure a SessionIdManager , then when the SessionHandler starts, it will use an instance of the DefaultSessionIdManager .
|
The DefaultSessionIdManager
At startup, if no instance of the HouseKeeper
has been explicitly set, the DefaultSessionIdManager
will create one.
Also at startup, the workerName
is determined.
The workerName
must be unique per Server
, and identifies the server in a cluster.
If a workerName
has not been explicitly set, then the value is derived as follows:
node[JETTY_WORKER_NAME]
where JETTY_WORKER_NAME
is an environment variable whose value can be an integer or string.
If the environment variable is not set, then it defaults to 0
, yielding the default workerName
of "node0"
.
The DefaultSessionIdManager
uses SecureRandom
to generate unique session ids.
The SessionHandler
class, which is used by both the ServletContextHandler
and the WebAppContext
classes, will instantiate a DefaultSessionIdManager
on startup if it does not detect one already present for the Server
.
Here is an example of explicitly setting up a DefaultSessionIdManager
with a workerName
of server3
in code:
Server server = new Server();
DefaultSessionIdManager idMgr = new DefaultSessionIdManager(server);
//you must set the workerName unless you set the env viable JETTY_WORKER_NAME
idMgr.setWorkerName("server3");
server.setSessionIdManager(idMgr);
Implementing a Custom SessionIdManager
If the DefaultSessionIdManager
does not meet your needs, you can extend it, or implement the SessionIdManager
interface directly.
When implementing a SessionIdManager
pay particular attention to the following:
-
the
getWorkerName()
method must return a name that is unique to theServer
instance. TheworkerName
becomes important in clustering scenarios because sessions can migrate from node to node: theworkerName
identifies which node was last managing aSession
. -
the contract of the
isIdInUse(String id)
method is very specific: a session id may only be reused iff it is already in use by another context. This restriction is important to support cross-context dispatch. -
you should be very careful to ensure that the
newSessionId(HttpServletRequest request, long created)
method does not return duplicate or predictable session ids.
The HouseKeeper
There is a maximum of one HouseKeeper per SessionIdManager
.
Its purpose is to periodically poll the SessionHandlers to clean out expired sessions.
This operation is usually referred to as "scavenging" expired sessions.
The scavenging interval is configured by the setIntervalSec(long)
method.
The default value is 600
sec, ie 10
mins.
The HouseKeeper semi-randomly adds an additional |
This code example shows how to configure a HouseKeeper
, along with a DefaultSessionIdManager
:
Server server = new Server();
DefaultSessionIdManager idMgr = new DefaultSessionIdManager(server);
idMgr.setWorkerName("server7");
server.setSessionIdManager(idMgr);
HouseKeeper houseKeeper = new HouseKeeper();
houseKeeper.setSessionIdManager(idMgr);
//set the frequency of scavenge cycles
houseKeeper.setIntervalSec(600L);
idMgr.setSessionHouseKeeper(houseKeeper);
The SessionHandler
Each context can have a single SessionHandler
.
The purpose of the SessionHandler
is to interact with the Request
and Response
to create, maintain and propagate sessions.
It also calls the context-level session listeners at appropriate points in the session lifecycle.
Configuration
The majority of configuration for the SessionHandler can be done via web.xml
<session-config>
declarations, or the javax.servlet.SessionCookieConfig
api.
There are also a few jetty-specific configuration options that we will cover here:
- checkingRemoteSessionIdEncoding
-
Boolean, default
false
. This controls whether or not thejavax.servlet.http.Response.encodeURL(String)
method will include the session id as a path parameter when the URL is destined for a remote node. This can also be configured by:-
setting the
org.eclipse.jetty.servlet.CheckingRemoteSessionIdEncoding
context init paramter
-
- setMaxInactiveInterval
-
Integer, seconds. This is the amount of time after which an unused session may be scavenged. This can also be configured by:
-
defining the
<session-config><session-timeout/></session-config>
element inweb.xml
, although take note that this element is specified in minutes but this method uses seconds. -
calling the
javax.servlet.ServletContext.setSessionTimeout(int)
method, where the timeout is configured in minutes.
-
- setHttpOnly
-
Boolean, default
false
. Iftrue
, the session cookie will not be exposed to client-side scripting code. This can also be configured by:-
using
javax.servlet.SessionCookieConfig.setHttpOnly(boolean)
method -
defining the
<session-config><cookie-config><http-only/></cookie-config></session-config>
element inweb.xml
-
There are also a few session settings that do not have SessionHandler setters, but can be configured with context init parameters:
- org.eclipse.jetty.servlet.MaxAge
-
This is the maximum number of seconds that the session cookie will be considered to be valid. By default, the cookie has no maximum validity time. See also refreshing the session cookie. The value can also be configured by:
-
calling the
SessionCookieConfig.setMaxAge(int)
method.
-
- org.eclipse.jetty.servlet.SessionDomain
-
String, default
null
. This is the domain of the session cookie. This can also be configured by:-
using the
javax.servlet.SessionCookieConfig.setDomain(String)
method -
defining the
<session-config><cookie-config><domain/></cookie-config></session-config>
element inweb.xml
-
- org.eclipse.jetty.servlet.SessionPath
-
String, default
null
. This is used when creating a new session cookie. If nothing is configured, the context path is used instead, defaulting to/
. This can also be configured by:-
using the
javax.servlet.SessionCookieConfig.setPath(String)
method -
defining the
<session-config><cookie-config><path/></cookie-config></session-config>
element inweb.xml
-
Statistics
Some statistics about the sessions for a context can be obtained from the SessionHandler
, either by calling the methods directly or via jmx
:
- sessionsCreated
-
This is the total number of sessions that have been created for this context since Jetty started.
- sessionTimeMax
-
The longest period of time a session was valid in this context before being invalidated.
- sessionTimeMean
-
The average period of time a session in this context was valid.
- sessionTimeStdDev
-
The standard deviation of the session validity times for this context.
- sessionTimeTotal
-
The total time that all sessions in this context have remained valid.
You can reset the statistics counters by either calling the following method directly on the the SessionHandler
, or using jmx
:
- statsReset
-
Resets the
SessionHandler
statistics counters.
The SessionCache
There is one SessionCache
per SessionHandler
, and thus one per context.
Its purpose is to provide an L1 cache of Session
objects.
Having a working set of Session
objects in memory allows multiple simultaneous requests for the same session to share the same Session
object.
A SessionCache
uses a SessionDataStore
to create, read, store and delete the SessionData
associated with the Session
.
There are two ways to create a SessionCache
for a SessionHandler
:
-
allow the
SessionHandler
to create one lazily at startup. TheSessionHandler
looks for aSessionCacheFactory
bean on the server to produce theSessionCache
instance. It then looks for aSessionDataStoreFactory
bean on the server to produce aSessionDataStore
instance to use with theSessionCache
. -
pass a fully configured
SessionCache
instance to theSessionHandler
. You are responsible for configuring both theSessionCache
instance and itsSessionDataStore
More on SessionDataStore
s later, in this section we will concentrate on the SessionCache
and SessionCacheFactory
.
The AbstractSessionCache provides most of the behaviour of SessionCache
s.
If you are implementing a custom SessionCache
we strongly recommend you extend this base class, as the Servlet Specification has many subtleties and extending the base class ensures that your implementation will take account of them.
Some of the important behaviours of SessionCache
s are:
- eviction
-
By default, sessions remain in a cache until they are expired or invalidated. If you have many or large sessions that are infrequently referenced you can use eviction to reduce the memory consumed by the cache. When a session is evicted, it is removed from the cache but it is not invalidated. If you have configured a
SessionDataStore
that persists or distributes the session in some way, it will continue to exist, and can be read back in when it needs to be referenced again. The eviction strategies are:- NEVER_EVICT
-
This is the default, sessions remain in the cache until expired or invalidated.
- EVICT_ON_SESSION_EXIT
-
When the last simultaneous request for a session finishes, the session will be evicted from the cache.
- EVICT_ON_INACTIVITY
-
If a session has not been referenced for a configurable number of seconds, then it will be evicted from the cache.
- saveOnInactiveEviction
-
This controls whether a session will be persisted to the
SessionDataStore
if it is being evicted due to the EVICT_ON_INACTIVITY policy. Usually sessions are written to theSessionDataStore
whenever the last simultaneous request exits the session. However, asSessionDataStores
can be configured to skip some writes, this option ensures that the session will be written out. - saveOnCreate
-
Usually a session will be written through to the configured
SessionDataStore
when the last request for it finishes. In the case of a freshly created session, this means that it will not be persisted until the request is fully finished. If your application uses context forwarding or including, the newly created session id will not be available in the subsequent contexts. You can enable this feature to ensure that a freshly created session is immediately persisted after creation: in this way the session id will be available for use in other contexts accessed during the same request. - removeUnloadableSessions
-
If a session becomes corrupted in the persistent store, it cannot be re-loaded into the
SessionCache
. This can cause noisy log output during scavenge cycles, when the same corrupted session fails to load over and over again. To prevent his, enable this feature and theSessionCache
will ensure that if a session fails to be loaded, it will be deleted. - invalidateOnShutdown
-
Some applications want to ensure that all cached sessions are removed when the server shuts down. This option will ensure that all cached sessions are invalidated. The
AbstractSessionCache
does not implement this behaviour, a subclass must implement the SessionCache.shutdown() method. - flushOnResponseCommit
-
This forces a "dirty" session to be written to the
SessionDataStore
just before a response is returned to the client, rather than waiting until the request is finished. A "dirty" session is one whose attributes have changed, or it has been freshly created. Using this option ensures that all subsequent requests - either to the same or a different node - will see the latest changes to the session.
Jetty provides two SessionCache
implementations: the DefaultSessionCache and the NullSessionCache.
The DefaultSessionCache
The DefaultSessionCache retains Session
objects in memory in a ConcurrentHashMap
.
It is suitable for non-clustered and clustered deployments.
For clustered deployments, a sticky load balancer is strongly recommended, otherwise you risk indeterminate session state as the session bounces around multiple nodes.
It implements the SessionCache.shutdown() method.
It also provides some statistics on sessions, which are convenient to access either directly in code or remotely via jmx:
- current sessions
-
The DefaultSessionCache.getSessionsCurrent() reports the number of sessions in the cache at the time of the method call.
- max sessions
-
The DefaultSessionCache.getSessionsMax() reports the highest number of sessions in the cache at the time of the method call.
- total sessions
-
The DefaultSessionCache.getSessionsTotal() reports the cumulative total of the number of sessions in the cache at the time of the method call.
- reset
-
The DefaultSessionCache.resetStats() zeros out the statistics counters.
If you create a DefaultSessionFactory and register it as Server
bean, a SessionHandler
will be able to lazily create a DefaultSessionCache
.
The DefaultSessionCacheFactory
has all of the same configuration setters as a DefaultSessionCache
.
Alternatively, if you only have a single SessionHandler
, or you need to configure a DefaultSessionCache
differently for every SessionHandler
, then you could dispense with the DefaultSessionCacheFactory
and simply instantiate, configure and pass in the DefaultSessionCache
yourself.
Server server = new Server();
DefaultSessionCacheFactory cacheFactory = new DefaultSessionCacheFactory();
//EVICT_ON_INACTIVE: evict a session after 60sec inactivity
cacheFactory.setEvictionPolicy(60);
//Only useful with the EVICT_ON_INACTIVE policy
cacheFactory.setSaveOnInactiveEvict(true);
cacheFactory.setFlushOnResponseCommit(true);
cacheFactory.setInvalidateOnShutdown(false);
cacheFactory.setRemoveUnloadableSessions(true);
cacheFactory.setSaveOnCreate(true);
//Add the factory as a bean to the server, now whenever a
//SessionHandler starts it will consult the bean to create a new DefaultSessionCache
server.addBean(cacheFactory);
If you don’t configure any SessionCache or SessionCacheFactory , the SessionHandler will automatically create a DefaultSessionCache .
|
The NullSessionCache
The NullSessionCache does not actually cache any objects: each request uses a fresh Session
object.
It is suitable for clustered deployments without a sticky load balancer and non-clustered deployments when purely minimal support for sessions is needed.
As no sessions are actually cached, of course functions like invalidateOnShutdown
and all of the eviction strategies have no meaning for the NullSessionCache
.
There is a NullSessionCacheFactory which you can instantiate, configure and set as a Server
bean to enable the SessionHandler
to automatically create new NullCache
s as needed.
All of the same configuration options are available on the NullSessionCacheFactory
as the NullSessionCache
itself.
Alternatively, if you only have a single SessionHandler
, or you need to configure a NullSessionCache
differently for every SessionHandler
, then you could dispense with the NullSessionCacheFactory
and simply instantiate, configure and pass in the NullSessionCache
yourself.
Server server = new Server();
NullSessionCacheFactory cacheFactory = new NullSessionCacheFactory();
cacheFactory.setFlushOnResponseCommit(true);
cacheFactory.setRemoveUnloadableSessions(true);
cacheFactory.setSaveOnCreate(true);
//Add the factory as a bean to the server, now whenever a
//SessionHandler starts it will consult the bean to create a new NullSessionCache
server.addBean(cacheFactory);
Implementing a Custom SessionCache
As previously mentioned, we highly recommend that you extend the AbstractSessionCache.
Heterogeneous Caching
Using one of the SessionCacheFactory
s will ensure that every time a SessionHandler
starts it will create a new instance of the corresponding type of SessionCache
.
But, what if you deploy multiple webapps, and for one of them, you don’t want to use sessions?
Or alternatively, you don’t want to use sessions, but you have one webapp that now needs them?
In that case, you can configure the SessionCacheFactory
appropriate to the majority, and then specifically create the right type of SessionCache
for the others.
Here’s an example where we configure the DefaultSessionCacheFactory
to handle most webapps, but then specifically use a NullSessionCache
for another:
Server server = new Server();
DefaultSessionCacheFactory cacheFactory = new DefaultSessionCacheFactory();
//NEVER_EVICT
cacheFactory.setEvictionPolicy(SessionCache.NEVER_EVICT);
cacheFactory.setFlushOnResponseCommit(true);
cacheFactory.setInvalidateOnShutdown(false);
cacheFactory.setRemoveUnloadableSessions(true);
cacheFactory.setSaveOnCreate(true);
//Add the factory as a bean to the server, now whenever a
//SessionHandler starts it will consult the bean to create a new DefaultSessionCache
server.addBean(cacheFactory);
ContextHandlerCollection contexts = new ContextHandlerCollection();
server.setHandler(contexts);
//Add a webapp that will use a DefaultSessionCache via the DefaultSessionCacheFactory
WebAppContext app1 = new WebAppContext();
app1.setContextPath("/app1");
contexts.addHandler(app1);
//Add a webapp that uses an explicit NullSessionCache instead
WebAppContext app2 = new WebAppContext();
app2.setContextPath("/app2");
NullSessionCache nullSessionCache = new NullSessionCache(app2.getSessionHandler());
nullSessionCache.setFlushOnResponseCommit(true);
nullSessionCache.setRemoveUnloadableSessions(true);
nullSessionCache.setSaveOnCreate(true);
//If we pass an existing SessionCache instance to the SessionHandler, it must be
//fully configured: this means we must also provide SessionDataStore
nullSessionCache.setSessionDataStore(new NullSessionDataStore());
app2.getSessionHandler().setSessionCache(nullSessionCache);
The SessionDataStore
A SessionDataStore mediates the storage, retrieval and deletion of SessionData
.
There is one SessionDataStore
per SessionCache
.
The server libraries provide a number of alternative SessionDataStore
implementations.
The AbstractSessionDataStore provides most of the behaviour common to SessionDataStore
s:
- passivation
-
Supporting passivation means that session data is serialized. Some persistence mechanisms serialize, such as JDBC, GCloud Datastore etc. Others store an object in shared memory, e.g. Infinispan and thus don’t serialize session data. Whether or not a persistence technology entails passivation controls whether or not
HttpSessionActivationListener
s will be called. When implementing a customSessionDataStore
you need to decide whether or not passivation will be supported.
- savePeriod
-
This is an interval defined in seconds. It is used to reduce the frequency with which
SessionData
is written. Normally, whenever the last concurrent request leaves aSession
, theSessionData
for thatSession
is always persisted, even if the only thing that changed is thelastAccessTime
. If thesavePeriod
is non-zero, theSessionData
will not be persisted if no session attributes changed, unless the time since the last save exceeds thesavePeriod
. Setting a non-zero value can reduce the load on the persistence mechanism, but in a clustered environment runs the risk that other nodes will see the session as expired because it has not been persisted sufficiently recently. - gracePeriod
-
The
gracePeriod
is an interval defined in seconds. It is an attempt to deal with the non-transactional nature of sessions with regard to finding sessions that have expired. In a clustered configuration - even with a sticky load balancer - it is always possible that a session is "live" on a node but not yet updated in the persistent store. This means that it can be hard to determine at any given moment whether a clustered session has truly expired. Thus, we use thegracePeriod
to provide a bit of leeway around the moment of expiry during scavenge:-
on every scavenge cycle an
AbstractSessionDataStore
searches for sessions that belong to the context that expired at least onegracePeriod
ago -
infrequently the
AbstractSessionDataStore
searches for and summarily deletes sessions - from any context - that expired at least 10gracePeriod
s ago
-
The trivial NullSessionDataStore - which does not persist sessions - is the default used by the |
The FileSessionDataStore
The FileSessionDataStore
supports persistent storage of session data in a filesystem.
Persisting sessions to the local file system should never be used in a clustered environment. |
One file represents one session in one context.
File names follow this pattern:
[expiry]_[contextpath]_[virtualhost]_[id]
- expiry
-
This is the expiry time in milliseconds since the epoch.
- contextpath
-
This is the context path with any special characters, including
/
, replaced by theunderscore character. For example, a context path of
/catalog
would become_catalog
. A context path of simply/
becomes just_
. - virtualhost
-
This is the first virtual host associated with the context and has the form of 4 digits separated by
.
characters. If there are no virtual hosts associated with a context, then0.0.0.0
is used:[digit].[digit].[digit].[digit]
- id
-
This is the unique id of the session.
Putting all of the above together as an example, a session with an id of node0ek3vx7x2y1e7pmi3z00uqj1k0
for the context with path /test
with no virtual hosts and an expiry of 1599558193150
would have a file name of:
1599558193150__test_0.0.0.0_node0ek3vx7x2y1e7pmi3z00uqj1k0
Configuration
You can configure either a FileSessionDataStore individually, or a FileSessionDataStoreFactory
if you want multiple SessionHandler
s to use FileSessionDataStore
s that are identically configured.
The configuration methods are:
- storeDir
-
This is a File that defines the location for storage of session files. If the directory does not exist at startup, it will be created. If you use the same
storeDir
for multipleSessionHandlers
, then the sessions for all of those contexts are stored in the same directory. This is not a problem, as the name of the file is unique because it contains the context information. You must supply a value for this, otherwise startup of theFileSessionDataStore
will fail. - deleteUnrestorableFiles
-
Boolean, default
false
. If set totrue
, unreadable files will be deleted. This is useful to prevent repeated logging of the same error when the scavenger periodically (re-)attempts to load the corrupted information for a session in order to expire it. - savePeriod
-
This is an interval defined in seconds. It is used to reduce the frequency with which
SessionData
is written. Normally, whenever the last concurrent request leaves aSession
, theSessionData
for thatSession
is always persisted, even if the only thing that changed is thelastAccessTime
. If thesavePeriod
is non-zero, theSessionData
will not be persisted if no session attributes changed, unless the time since the last save exceeds thesavePeriod
. Setting a non-zero value can reduce the load on the persistence mechanism, but in a clustered environment runs the risk that other nodes will see the session as expired because it has not been persisted sufficiently recently. - gracePeriod
-
The
gracePeriod
is an interval defined in seconds. It is an attempt to deal with the non-transactional nature of sessions with regard to finding sessions that have expired. In a clustered configuration - even with a sticky load balancer - it is always possible that a session is "live" on a node but not yet updated in the persistent store. This means that it can be hard to determine at any given moment whether a clustered session has truly expired. Thus, we use thegracePeriod
to provide a bit of leeway around the moment of expiry during scavenge:-
on every scavenge cycle an
AbstractSessionDataStore
searches for sessions that belong to the context that expired at least onegracePeriod
ago -
infrequently the
AbstractSessionDataStore
searches for and summarily deletes sessions - from any context - that expired at least 10gracePeriod
s ago
-
Let’s look at an example of configuring a FileSessionDataStoreFactory
:
Server server = new Server();
//First lets configure a DefaultSessionCacheFactory
DefaultSessionCacheFactory cacheFactory = new DefaultSessionCacheFactory();
//NEVER_EVICT
cacheFactory.setEvictionPolicy(SessionCache.NEVER_EVICT);
cacheFactory.setFlushOnResponseCommit(true);
cacheFactory.setInvalidateOnShutdown(false);
cacheFactory.setRemoveUnloadableSessions(true);
cacheFactory.setSaveOnCreate(true);
//Add the factory as a bean to the server, now whenever a
//SessionHandler starts it will consult the bean to create a new DefaultSessionCache
server.addBean(cacheFactory);
//Now, lets configure a FileSessionDataStoreFactory
FileSessionDataStoreFactory storeFactory = new FileSessionDataStoreFactory();
storeFactory.setStoreDir(new File("/tmp/sessions"));
storeFactory.setGracePeriodSec(3600);
storeFactory.setSavePeriodSec(0);
//Add the factory as a bean on the server, now whenever a
//SessionHandler starts, it will consult the bean to create a new FileSessionDataStore
//for use by the DefaultSessionCache
server.addBean(storeFactory);
Here’s an alternate example, configuring a FileSessionDataStore
directly:
//create a context
WebAppContext app1 = new WebAppContext();
app1.setContextPath("/app1");
//First, we create a DefaultSessionCache
DefaultSessionCache cache = new DefaultSessionCache(app1.getSessionHandler());
cache.setEvictionPolicy(SessionCache.NEVER_EVICT);
cache.setFlushOnResponseCommit(true);
cache.setInvalidateOnShutdown(false);
cache.setRemoveUnloadableSessions(true);
cache.setSaveOnCreate(true);
//Now, we configure a FileSessionDataStore
FileSessionDataStore store = new FileSessionDataStore();
store.setStoreDir(new File("/tmp/sessions"));
store.setGracePeriodSec(3600);
store.setSavePeriodSec(0);
//Tell the cache to use the store
cache.setSessionDataStore(store);
//Tell the contex to use the cache/store combination
app1.getSessionHandler().setSessionCache(cache);
The JDBCSessionDataStore
The JDBCSessionDataStore
supports persistent storage of session data in a relational database.
To do that, it requires a DatabaseAdaptor
that handles the differences between databases (eg Oracle, Postgres etc), and a SessionTableSchema
that allows for the customization of table and column names.
SessionData
is stored in a table with one row per session.
This is the table, with the table name, column names and type keywords at their default settings:
sessionId | contextPath | virtualHost | lastNode | accessTime | lastAccessTime | createTime | cookieTime | lastSavedTime | expiryTime | maxInterval | map |
---|---|---|---|---|---|---|---|---|---|---|---|
120 varchar |
60 varchar |
60 varchar |
60 varchar |
long |
long |
long |
long |
long |
long |
long |
blob |
The name of the table and all columns can be configured using the SessionTableSchema
class described below.
Many databases use different keywords for the long
, blob
and varchar
types, so you can explicitly configure these if jetty cannot determine what they should be at runtime based on the metadata available from a db connection using the DatabaseAdaptor
class described below.
Configuration
The JDBCSessionDataStore and corresponding JDBCSessionDataStoreFactory supports the following configuration:
- savePeriod
-
This is an interval defined in seconds. It is used to reduce the frequency with which
SessionData
is written. Normally, whenever the last concurrent request leaves aSession
, theSessionData
for thatSession
is always persisted, even if the only thing that changed is thelastAccessTime
. If thesavePeriod
is non-zero, theSessionData
will not be persisted if no session attributes changed, unless the time since the last save exceeds thesavePeriod
. Setting a non-zero value can reduce the load on the persistence mechanism, but in a clustered environment runs the risk that other nodes will see the session as expired because it has not been persisted sufficiently recently. - gracePeriod
-
The
gracePeriod
is an interval defined in seconds. It is an attempt to deal with the non-transactional nature of sessions with regard to finding sessions that have expired. In a clustered configuration - even with a sticky load balancer - it is always possible that a session is "live" on a node but not yet updated in the persistent store. This means that it can be hard to determine at any given moment whether a clustered session has truly expired. Thus, we use thegracePeriod
to provide a bit of leeway around the moment of expiry during scavenge:-
on every scavenge cycle an
AbstractSessionDataStore
searches for sessions that belong to the context that expired at least onegracePeriod
ago -
infrequently the
AbstractSessionDataStore
searches for and summarily deletes sessions - from any context - that expired at least 10gracePeriod
s ago
-
- DatabaseAdaptor
-
The
DatabaseAdaptor
can connect to a database either via ajavax.sql.Datasource
or ajava.sql.Driver
. Additionally, a database-specific keyword can be configured for theblob
,varchar
andlong
types. Note that theDatabaseAdaptor
tries to automatically detect the type of the database from the first connection and select the appropriate type keywords, however you may need to explicitly configure them if you’re not usingPostgres
orOracle
.- datasource
-
This can either be a
Datasource
instance or the jndi name of aDatasource
to look up.
DatabaseAdaptor datasourceAdaptor = new DatabaseAdaptor();
datasourceAdaptor.setDatasourceName("/jdbc/myDS");
- driverInfo
-
This is the name or instance of a jdbc
Driver
class and a connection url.
DatabaseAdaptor driverAdaptor = new DatabaseAdaptor();
driverAdaptor.setDriverInfo("com.mysql.jdbc.Driver", "jdbc:mysql://127.0.0.1:3306/sessions?user=sessionsadmin");
- blobType
-
Default
blob
orbytea
for Postgres. - longType
-
Default
bigint
ornumber(20)
for Oracle. - stringType
-
Default
varchar
.- SessionTableSchema
- schemaName
- catalogName
-
The exact meaning of these two are dependent on your database vendor, but can broadly be described as further scoping for the session table name. See https://en.wikipedia.org/wiki/Database_schema and https://en.wikipedia.org/wiki/Database_catalog. These extra scoping names can come into play at startup time when Jetty determines if the session table already exists, or otherwise creates it on-the-fly. If you have employed either of these concepts when you pre-created the session table, or you want to ensure that Jetty uses them when it auto-creates the session table, then you have two options: either set them explicitly, or let Jetty infer them from a database connection. If you leave them unset, then no scoping will be done. If you use the special value
INFERRED
, Jetty will determine them from a database connection. - tableName
-
Default
JettySessions
. This is the name of the table in which session data is stored. - accessTimeColumn
-
Default
accessTime
. This is the name of the column that stores the time - in ms since the epoch - at which a session was last accessed - contextPathColumn
-
Default
contextPath
. This is the name of the column that stores thecontextPath
of a session. - cookieTimeColumn
-
Default
cookieTime
. This is the name of the column that stores the time - in ms since the epoch - that the cookie was last set for a session. - createTimeColumn
-
Default
createTime
. This is the name of the column that stores the time - in ms since the epoch - at which a session was created. - expiryTimeColumn
-
Default
expiryTime
. This is name of the column that stores - in ms since the epoch - the time at which a session will expire. - lastAccessTimeColumn
-
Default
lastAccessTime
. This is the name of the column that stores the time - in ms since the epoch - that a session was previously accessed. - lastSavedTimeColumn
-
Default
lastSavedTime
. This is the name of the column that stores the time - in ms since the epoch - at which a session was last written. - idColumn
-
Default
sessionId
. This is the name of the column that stores the id of a session. - lastNodeColumn
-
Default
lastNode
. This is the name of the column that stores theworkerName
of the last node to write a session. - virtualHostColumn
-
Default
virtualHost
. This is the name of the column that stores the first virtual host of the context of a session. - maxIntervalColumn
-
Default
maxInterval
. This is the name of the column that stores the interval - in ms - during which a session can be idle before being considered expired. - mapColumn
-
Default
map
. This is the name of the column that stores the serialized attributes of a session.
The MongoSessionDataStore
The MongoSessionDataStore
supports persistence of SessionData
in a nosql database.
The best description for the document model for session information is found in the javadoc for the MongoSessionDataStore. In overview, it can be represented thus:
The database contains a document collection for the sessions.
Each document represents a session id, and contains one nested document per context in which that session id is used.
For example, the session id abcd12345
might be used by two contexts, one with path /contextA
and one with path /contextB
.
In that case, the outermost document would refer to abcd12345
and it would have a nested document for /contextA
containing the session attributes for that context, and another nested document for /contextB
containing the session attributes for that context.
Remember, according to the Servlet Specification, a session id can be shared by many contexts, but the attributes must be unique per context.
The outermost document contains these fields:
- id
-
The session id.
- created
-
The time (in ms since the epoch) at which the session was first created in any context.
- maxIdle
-
The time (in ms) for which an idle session is regarded as valid. As maxIdle times can be different for
Session
s from different contexts, this is the shortest maxIdle time. - expiry
-
The time (in ms since the epoch) at which the session will expire. As the expiry time can be different for
Session
s from different contexts, this is the shortest expiry time.
Each nested context-specific document contains:
- attributes
-
The session attributes as a serialized map.
- lastSaved
-
The time (in ms since the epoch) at which the session in this context was saved.
- lastAccessed
-
The time (in ms since the epoch) at which the session in this context was previously accessed.
- accessed
-
The time (in ms since the epoch) at which this session was most recently accessed.
- lastNode
-
The workerName of the last server that saved the session data.
- version
-
An object that is updated every time a session is written out for a context.
Configuration
You can configure either a MongoSessionDataStore individually, or a MongoSessionDataStoreFactory if you want multiple SessionHandler
s to use MongoSessionDataStore
s that are identically configured.
The configuration methods for the MongoSessionDataStoreFactory
are:
- savePeriod
-
This is an interval defined in seconds. It is used to reduce the frequency with which
SessionData
is written. Normally, whenever the last concurrent request leaves aSession
, theSessionData
for thatSession
is always persisted, even if the only thing that changed is thelastAccessTime
. If thesavePeriod
is non-zero, theSessionData
will not be persisted if no session attributes changed, unless the time since the last save exceeds thesavePeriod
. Setting a non-zero value can reduce the load on the persistence mechanism, but in a clustered environment runs the risk that other nodes will see the session as expired because it has not been persisted sufficiently recently. - gracePeriod
-
The
gracePeriod
is an interval defined in seconds. It is an attempt to deal with the non-transactional nature of sessions with regard to finding sessions that have expired. In a clustered configuration - even with a sticky load balancer - it is always possible that a session is "live" on a node but not yet updated in the persistent store. This means that it can be hard to determine at any given moment whether a clustered session has truly expired. Thus, we use thegracePeriod
to provide a bit of leeway around the moment of expiry during scavenge:-
on every scavenge cycle an
AbstractSessionDataStore
searches for sessions that belong to the context that expired at least onegracePeriod
ago -
infrequently the
AbstractSessionDataStore
searches for and summarily deletes sessions - from any context - that expired at least 10gracePeriod
s ago
-
- dbName
-
This is the name of the database.
- collectionName
-
The name of the document collection.
There are two alternative ways to specify the connection to mongodb:
- connectionString
-
This is a mongodb url, eg
mongodb://localhost
- host
- port
-
This is the hostname and port number of the mongodb instance to contact.
Let’s look at an example of configuring a MongoSessionDataStoreFactory
:
Server server = new Server();
MongoSessionDataStoreFactory mongoSessionDataStoreFactory = new MongoSessionDataStoreFactory();
mongoSessionDataStoreFactory.setGracePeriodSec(3600);
mongoSessionDataStoreFactory.setSavePeriodSec(0);
mongoSessionDataStoreFactory.setDbName("HttpSessions");
mongoSessionDataStoreFactory.setCollectionName("JettySessions");
// Either set the connectionString
mongoSessionDataStoreFactory.setConnectionString("mongodb:://localhost:27017");
// or alternatively set the host and port.
mongoSessionDataStoreFactory.setHost("localhost");
mongoSessionDataStoreFactory.setPort(27017);
The CachingSessionDataStore
The CachingSessionDataStore is a special type of SessionDataStore
that checks an L2 cache for SessionData
before checking a delegate SessionDataStore
.
This can improve the performance of slow stores.
The L2 cache is an instance of a SessionDataMap.
Jetty provides one implementation of this L2 cache based on memcached
, MemcachedSessionDataMap.
Configuration
Here’s an example of how to programmatically configure CachingSessionDataStore
s, using a FileSessionDataStore as a delegate, and memcached
as the L2 cache:
Server server = new Server();
//Make a factory for memcached L2 caches for SessionData
MemcachedSessionDataMapFactory mapFactory = new MemcachedSessionDataMapFactory();
mapFactory.setExpirySec(0); //items in memcached don't expire
mapFactory.setHeartbeats(true); //tell memcached to use heartbeats
mapFactory.setAddresses(new InetSocketAddress("localhost", 11211)); //use a local memcached instance
mapFactory.setWeights(new int[] {100}); //set the weighting
//Make a FileSessionDataStoreFactory for creating FileSessionDataStores
//to persist the session data
FileSessionDataStoreFactory storeFactory = new FileSessionDataStoreFactory();
storeFactory.setStoreDir(new File("/tmp/sessions"));
storeFactory.setGracePeriodSec(3600);
storeFactory.setSavePeriodSec(0);
//Make a factory that plugs the L2 cache into the SessionDataStore
CachingSessionDataStoreFactory cachingSessionDataStoreFactory = new CachingSessionDataStoreFactory();
cachingSessionDataStoreFactory.setSessionDataMapFactory(mapFactory);
cachingSessionDataStoreFactory.setSessionStoreFactory(storeFactory);
//Register it as a bean so that all SessionHandlers will use it
//to make FileSessionDataStores that use memcached as an L2 SessionData cache.
server.addBean(cachingSessionDataStoreFactory);