Issue #10466 review session documentation. (#10497)

* Issue #10466 review session documentation.

Also fix session config context init param names and add missing code to
configure SessionHandler via context init params, and added test for
config.
This commit is contained in:
Jan Bartel 2023-09-22 08:18:29 +02:00 committed by GitHub
parent 57b953be67
commit 0e79cc8cb3
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
38 changed files with 1214 additions and 622 deletions

View File

@ -359,5 +359,13 @@
<type>zip</type>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.eclipse.jetty</groupId>
<artifactId>jetty-infinispan-embedded-query</artifactId>
</dependency>
<dependency>
<groupId>org.eclipse.jetty.gcloud</groupId>
<artifactId>jetty-gcloud-session-manager</artifactId>
</dependency>
</dependencies>
</project>

View File

@ -18,7 +18,7 @@ Jetty can store http session information into GCloud by enabling the `session-st
===== Preparation
You will first need to create a project and enable the Google Cloud api: link:https://cloud.google.com/docs/authentication#preparation[].
You will first need to create a project and enable the Google Cloud API: link:https://cloud.google.com/docs/authentication#preparation[].
Take note of the project id that you create in this step as you need to supply it in later steps.
===== Communicating with GCloudDataStore
@ -30,7 +30,7 @@ Before running Jetty, you will need to choose one of the following methods to se
1. Using the GCloud SDK:
* Ensure you have the GCloud SDK installed: link:https://cloud.google.com/sdk/?hl=en[]
* Use the GCloud tool to set up the project you created in the preparation step: `gcloud config set project PROJECT_ID`
* Use the GCloud tool to authenticate a google account associated with the project created in the preparation step: `gcloud auth login ACCOUNT`
* Use the GCloud tool to authenticate a Google account associated with the project created in the preparation step: `gcloud auth login ACCOUNT`
2. Using environment variables
* Define the environment variable `GCLOUD_PROJECT` with the project id you created in the preparation step.
@ -63,7 +63,7 @@ The `session-store-gcloud` module provides GCloud support for storing session da
Because the Google Cloud DataStore is not a technology provided by the Eclipse Foundation, when enabling the module you will be prompted to assent to the licenses of the external vendor.
As GCloud requires certain Java Commons Logging features to work correctly, Jetty routes these through SLF4J.
By default Jetty implements the SLF4J api, but you can choose a different logging implementation by following the instructions xref:og-server-logging[here]
By default, Jetty implements the SLF4J api, but you can choose a different logging implementation by following the instructions xref:og-server-logging[here]
IMPORTANT: If you want to use updated versions of the jar files automatically downloaded during the module enablement, you can place them in the associated `$JETTY_BASE/lib/` directory and use the `--skip-file-validation=<module name>` command line option to prevent errors when starting your server.

View File

@ -14,17 +14,37 @@
[[pg-server-session-architecture]]
==== 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`
Jetty session support has been architected to provide a core implementation that is independent of the Servlet specification.
This allows programmers who use core Jetty - without the Servlet API - to still have classic Servlet session-like support for their ``Request``s and ``Handler``s.
The session architecture can be represented like so:
These core classes are adapted to each of the various Servlet specification environments to deliver classic ``HttpSession``s for ``Servlet``s,`Filter``s, etc
Full support for the session lifecycle is supported, in addition to L1 and L2 caching, and a number of pluggable options for persisting session data.
Here are some of the most important concepts that will be referred to throughout the documentation:
SessionIdManager::
responsible for allocation of unique session ids.
HouseKeeper::
responsible for orchestrating the detection and removal of expired sessions.
SessionManager::
responsible for managing the lifecycle of sessions.
SessionHandler::
an implementation of `SessionManager` that adapts sessions to either the core or Servlet specification environment.
SessionCache::
an L1 cache of in-use `ManagedSession` objects
Session::
a session consisting of `SessionData` that can be associated with a `Request`
ManagedSession::
a `Session` that supports caching and lifecycle management
SessionData::
encapsulates the attributes and metadata associated with a `Session`
SessionDataStore::
responsible for creating, persisting and reading `SessionData`
CachingSessionDataStore::
an L2 cache of `SessionData`
Diagrammatically, these concepts can be represented as:
[plantuml]
----
@ -35,6 +55,8 @@ interface SessionIdManager
class HouseKeeper
interface SessionManager
class SessionHandler
interface SessionCache
@ -43,17 +65,24 @@ interface SessionDataStore
class CachingSessionDataStore
class Session
interface Session
class ManagedSession
class SessionData
class Request
Server "1" *-down- "1" SessionIdManager
SessionIdManager "1" *-left- "1" HouseKeeper
Server "1" *-down- "n" SessionHandler
SessionHandler "1" *-down- "1" SessionCache
Request "1" *-down- "0/1" Session
SessionManager "1" *-down- "1" SessionCache
SessionManager <|-- SessionHandler
SessionCache "1" *-down- "1" SessionDataStore
SessionCache o-down- Session
Session "1" *-- "1" SessionData
SessionCache o-down- ManagedSession
ManagedSession "1" *-- "1" SessionData
Session <|-- ManagedSession
SessionDataStore --> SessionData: CRUD
SessionDataStore <|-- CachingSessionDataStore
CachingSessionDataStore o- SessionData

View File

@ -25,15 +25,13 @@ CachingSessionDataStore "1" *-down- "1" SessionDataStore
SessionDataMap <|-- MemcachedSessionDataMap
----
The link:{javadoc-url}/org/eclipse/jetty/server/session/CachingSessionDataStore.html[CachingSessionDataStore] is a special type of `SessionDataStore` that checks an L2 cache for `SessionData` before checking a delegate `SessionDataStore`.
The link:{javadoc-url}/org/eclipse/jetty/session/CachingSessionDataStore.html[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 link:{javadoc-url}/org/eclipse/jetty/server/session/SessionDataMap.html[SessionDataMap].
The L2 cache is an instance of a link:{javadoc-url}/org/eclipse/jetty/session/SessionDataMap.html[SessionDataMap].
Jetty provides one implementation of this L2 cache based on `memcached`, link:{javadoc-url}/org/eclipse/jetty/memcached/session/MemcachedSessionDataMap.html[MemcachedSessionDataMap].
====== Configuration
Here's an example of how to programmatically configure ``CachingSessionDataStore``s, using a xref:pg-server-session-datastore-file[FileSessionDataStore] as a delegate, and `memcached` as the L2 cache:
This is an example of how to programmatically configure ``CachingSessionDataStore``s, using a xref:pg-server-session-datastore-file[FileSessionDataStore] as a delegate, and `memcached` as the L2 cache:
[source,java,indent=0]
----

View File

@ -14,30 +14,32 @@
[[pg-server-session-cache]]
==== 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 is one `SessionCache` per `SessionManager`, and thus one per context.
Its purpose is to provide an L1 cache of `ManagedSession` objects.
Having a working set of `ManagedSession` objects in memory allows multiple simultaneous requests for the same session (ie the _same_ session id in the _same_ context) to share the same `ManagedSession` object.
A `SessionCache` uses a `SessionDataStore` to create, read, store, and delete the `SessionData` associated with the `ManagedSession`.
There are two ways to create a `SessionCache` for a `SessionHandler`:
There are two ways to create a `SessionCache` for a `SessionManager`:
. allow the `SessionHandler` to create one lazily at startup.
The `SessionHandler` looks for a `SessionCacheFactory` bean on the server to produce the `SessionCache` instance.
It then looks for a `SessionDataStoreFactory` bean on the server to produce a `SessionDataStore` instance to use with the `SessionCache`.
. allow the `SessionManager` to create one lazily at startup.
The `SessionManager` looks for a `SessionCacheFactory` bean on the `Server` to produce the `SessionCache` instance.
It then looks for a `SessionDataStoreFactory` bean on the `Server` to produce a `SessionDataStore` instance to use with the `SessionCache`.
If no `SessionCacheFactory` is present, it defaults to creating a `DefaultSessionCache`.
If no `SessionDataStoreFactory` is present, it defaults to creating a `NullSessionDataStore`.
. pass a fully configured `SessionCache` instance to the `SessionHandler`.
. pass a fully configured `SessionCache` instance to the `SessionManager`.
You are responsible for configuring both the `SessionCache` instance and its `SessionDataStore`
More on ``SessionDataStore``s xref:pg-server-session-datastore[later], in this section we will concentrate on the `SessionCache` and `SessionCacheFactory`.
More on ``SessionDataStore``s xref:pg-server-session-datastore[later], this section concentrates on the `SessionCache` and `SessionCacheFactory`.
The link:{javadoc-url}/org/eclipse/jetty/server/session/AbstractSessionCache.html[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.
The link:{javadoc-url}/org/eclipse/jetty/session/AbstractSessionCache.html[AbstractSessionCache] provides most of the behaviour of ``SessionCache``s.
If you are implementing a custom `SessionCache` it is strongly recommended that you extend this class because it implements the numerous subtleties of the Servlet specification.
Some of the important behaviours of ``SessionCache``s are:
eviction::
By default, sessions remain in a cache until they are expired or invalidated.
By default, ``ManagedSession``s 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.
@ -52,7 +54,7 @@ The eviction strategies are:
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 the `SessionDataStore` whenever the last simultaneous request exits the session.
However, as `SessionDataStores` can be configured to xref:pg-server-session-datastore-skip[skip some writes], this option ensures that the session will be written out.
However, as ``SessionDataStore``s` can be configured to xref:pg-server-session-datastore-skip[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.
@ -68,61 +70,57 @@ To prevent his, enable this feature and the `SessionCache` will ensure that if a
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 link:{javadoc-url}/org/eclipse/jetty/server/session/SessionCache.html#shutdown()[SessionCache.shutdown()] method.
The `AbstractSessionCache` does not implement this behaviour, a subclass must implement the link:{javadoc-url}/org/eclipse/jetty/session/SessionCache.html#shutdown()[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 link:{javadoc-url}/org/eclipse/jetty/server/session/DefaultSessionCache.html[DefaultSessionCache] and the link:{javadoc-url}/org/eclipse/jetty/server/session/NullSessionCache.html[NullSessionCache].
Jetty provides two `SessionCache` implementations: the link:{javadoc-url}/org/eclipse/jetty/session/DefaultSessionCache.html[DefaultSessionCache] and the link:{javadoc-url}/org/eclipse/jetty/session/NullSessionCache.html[NullSessionCache].
[[pg-server-session-hash]]
===== The DefaultSessionCache
The link:{javadoc-url}/org/eclipse/jetty/server/session/DefaultSessionCache.html[DefaultSessionCache] retains `Session` objects in memory in a `ConcurrentHashMap`.
The link:{javadoc-url}/org/eclipse/jetty/session/DefaultSessionCache.html[DefaultSessionCache] retains `ManagedSession` 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 link:{javadoc-url}/org/eclipse/jetty/server/session/SessionCache.html#shutdown()[SessionCache.shutdown()] method.
It implements the link:{javadoc-url}/org/eclipse/jetty/session/SessionCache.html#shutdown()[SessionCache.shutdown()] method.
It also provides some statistics on sessions, which are convenient to access either directly in code or remotely via jmx:
It also provides some statistics on sessions, which are convenient to access either directly in code or remotely via JMX:
current sessions::
The link:{javadoc-url}/org/eclipse/jetty/server/session/DefaultSessionCache.html#getSessionsCurrent()[DefaultSessionCache.getSessionsCurrent()] reports the number of sessions in the cache at the time of the method call.
The link:{javadoc-url}/org/eclipse/jetty/session/DefaultSessionCache.html#getSessionsCurrent()[DefaultSessionCache.getSessionsCurrent()] method reports the number of sessions in the cache at the time of the method call.
max sessions::
The link:{javadoc-url}/org/eclipse/jetty/server/session/DefaultSessionCache.html#getSessionsCurrent()[DefaultSessionCache.getSessionsMax()] reports the highest number of sessions in the cache at the time of the method call.
The link:{javadoc-url}/org/eclipse/jetty/session/DefaultSessionCache.html#getSessionsCurrent()[DefaultSessionCache.getSessionsMax()] method reports the highest number of sessions in the cache at the time of the method call.
total sessions::
The link:{javadoc-url}/org/eclipse/jetty/server/session/DefaultSessionCache.html#getSessionsTotal()[DefaultSessionCache.getSessionsTotal()] reports the cumulative total of the number of sessions in the cache at the time of the method call.
The link:{javadoc-url}/org/eclipse/jetty/session/DefaultSessionCache.html#getSessionsTotal()[DefaultSessionCache.getSessionsTotal()] method reports the cumulative total of the number of sessions in the cache at the time of the method call.
reset::
The link:{javadoc-url}/org/eclipse/jetty/server/session/DefaultSessionCache.html#resetStats()[DefaultSessionCache.resetStats()] zeros out the statistics counters.
If you create a link:{javadoc-url}/org/eclipse/jetty/server/session/DefaultSessionCacheFactory.html[DefaultSessionFactory] and register it as `Server` bean, a `SessionHandler` will be able to lazily create a `DefaultSessionCache`.
If you create a link:{javadoc-url}/org/eclipse/jetty/session/DefaultSessionCacheFactory.html[DefaultSessionFactory] and register it as a `Server` bean, a `SessionManger` 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.
Alternatively, if you only have a single `SessionManager`, or you need to configure a `DefaultSessionCache` differently for every `SessionManager`, then you could dispense with the `DefaultSessionCacheFactory` and simply instantiate, configure, and pass in the `DefaultSessionCache` yourself.
[source,java,indent=0]
----
include::../../{doc_code}/org/eclipse/jetty/docs/programming/server/session/SessionDocs.java[tags=defaultsessioncache]
----
NOTE: If you don't configure any `SessionCache` or `SessionCacheFactory`, the `SessionHandler` will automatically create a `DefaultSessionCache`.
NOTE: If you don't configure any `SessionCache` or `SessionCacheFactory`, a `SessionManager` will automatically create its own `DefaultSessionCache`.
[[pg-server-session-null]]
===== The NullSessionCache
The link:{javadoc-url}/org/eclipse/jetty/server/session/NullSessionCache.html[NullSessionCache] does not actually cache any objects: each request uses a fresh `Session` object.
The link:{javadoc-url}/org/eclipse/jetty/session/NullSessionCache.html[NullSessionCache] does not actually cache any objects: each request uses a fresh `ManagedSession` 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 link:{javadoc-url}/org/eclipse/jetty/server/session/NullSessionCacheFactory.html[NullSessionCacheFactory] which you can instantiate, configure and set as a `Server` bean to enable the `SessionHandler` to automatically create new ``NullCache``s as needed.
There is a link:{javadoc-url}/org/eclipse/jetty/session/NullSessionCacheFactory.html[NullSessionCacheFactory] which you can instantiate, configure and set as a `Server` bean to enable a `SessionManager` to automatically create new ``NullSessionCache``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.
Alternatively, if you only have a single `SessionManager`, or you need to configure a `NullSessionCache` differently for every `SessionManager`, then you could dispense with the `NullSessionCacheFactory` and simply instantiate, configure, and pass in the `NullSessionCache` yourself.
[source,java,indent=0]
@ -131,13 +129,13 @@ include::../../{doc_code}/org/eclipse/jetty/docs/programming/server/session/Sess
----
[[pg-server-session-customcache]]
===== Implementing a Custom SessionCache
===== Implementing a custom SessionCache
As previously mentioned, we highly recommend that you extend the link:{javadoc-url}/org/eclipse/jetty/server/session/AbstractSessionCache.html[AbstractSessionCache].
As previously mentioned, it is strongly recommended that you extend the link:{javadoc-url}/org/eclipse/jetty/session/AbstractSessionCache.html[AbstractSessionCache].
===== Heterogeneous Caching
===== 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`.
Using one of the ``SessionCacheFactory``s will ensure that every time a `SessionManager` 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?

View File

@ -22,7 +22,7 @@ One file represents one session in one context.
File names follow this pattern:
+[expiry]_[contextpath]_[virtualhost]_[id]+
[expiry]_[contextpath]_[virtualhost]_[id]
expiry::
This is the expiry time in milliseconds since the epoch.
@ -31,6 +31,7 @@ contextpath::
This is the context path with any special characters, including `/`, replaced by the `_` underscore 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, then `0.0.0.0` is used:
@ -45,26 +46,24 @@ Putting all of the above together as an example, a session with an id of `node0e
`1599558193150__test_0.0.0.0_node0ek3vx7x2y1e7pmi3z00uqj1k0`
====== Configuration
You can configure either a link:{javadoc-url}/org/eclipse/jetty/server/session/FileSessionDataStore.html[FileSessionDataStore] individually, or a `FileSessionDataStoreFactory` if you want multiple ``SessionHandler``s to use ``FileSessionDataStore``s that are identically configured.
You can configure either a link:{javadoc-url}/org/eclipse/jetty/session/FileSessionDataStore.html[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.
setStoreDir(File) _[Default:null]_ ::
This is 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 multiple `SessionHandlers`, 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 the `FileSessionDataStore` will fail.
deleteUnrestorableFiles::
Boolean, default `false`.
deleteUnrestorableFiles(boolean) _[Default:false]_ ::
If set to `true`, unreadable files will be deleted.
This is useful to prevent repeated logging of the same error when the xref:pg-server-session-housekeeper[scavenger] periodically (re-)attempts to load the corrupted information for a session in order to expire it.
include::session-sessiondatastore.adoc[tag=common-datastore-config]
Let's look at an example of configuring a `FileSessionDataStoreFactory`:
Here's an example of configuring a `FileSessionDataStoreFactory`:
[source,java,indent=0]
----

View File

@ -0,0 +1,112 @@
//
// ========================================================================
// Copyright (c) 1995 Mort Bay Consulting Pty Ltd and others.
//
// This program and the accompanying materials are made available under the
// terms of the Eclipse Public License v. 2.0 which is available at
// https://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0
// which is available at https://www.apache.org/licenses/LICENSE-2.0.
//
// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0
// ========================================================================
//
[[pg-server-session-datastore-gcloud]]
===== The GCloudSessionDataStore
The `GCloudSessionDataStore` supports persistent storage of session data into https://cloud.google.com/datastore[Google Cloud DataStore].
[[pg-server-session-datastore-gcloud-prep]]
====== Preparation
You will first need to create a project and enable the Google Cloud API: link:https://cloud.google.com/docs/authentication#preparation[].
Take note of the `project id` that you create in this step as you need to supply it in later steps.
You can choose to use Jetty either inside or outside of Google infrastructure.
. Outside of Google infrastructure
+
Before running Jetty, you will need to choose one of the following methods to set up the local environment to enable remote GCloud DataStore communications:
.. Using the GCloud SDK
* Ensure you have the GCloud SDK installed: link:https://cloud.google.com/sdk/?hl=en[]
* Use the GCloud tool to set up the project you created in the preparation step: `gcloud config set project PROJECT_ID`
* Use the GCloud tool to authenticate a Google account associated with the project created in the preparation step: `gcloud auth login ACCOUNT`
.. Using environment variables
* Define the environment variable `GCLOUD_PROJECT` with the project id you created in the preparation step.
* Generate a JSON link:https://cloud.google.com/storage/docs/authentication?hl=en#service_accounts[service account key] and then define the environment variable `GOOGLE_APPLICATION_CREDENTIALS=/path/to/my/key.json`
. Inside of Google infrastructure
+
The Google deployment tools will automatically configure the project and authentication information for you.
Jetty GCloud session support provides some indexes as optimizations that can speed up session searches.
This will particularly benefit session scavenging, although it may make write operations slower.
By default, indexes will _not_ be used.
You will see a log `WARNING` message informing you about the absence of indexes:
WARN: Session indexes not uploaded, falling back to less efficient queries
In order to use them, you will need to manually upload the file to GCloud that defines the indexes.
This file is named `index.yaml` and you can find it in your distribution in `$JETTY_BASE/etc/sessions/gcloud/index.yaml`.
Follow the instructions link:https://cloud.google.com/datastore/docs/tools/#the_development_workflow_using_gcloud[here] to upload the pre-generated `index.yaml` file.
====== Configuration
The following configuration options apply to both the link:{javadoc-url}/org/eclipse/jetty/session/GCloudSessionDataStore.html[GCloudSessionDataStore] and the link:{javadoc-url}/org/eclipse/jetty/session/GCloudSessionDataStoreFactory.html[GCloudSessionDataStoreFactory].
Use the latter if you want multiple ``SessionHandler``s to use ``GCloudSessionDataStore``s that are identically configured.
include::session-sessiondatastore.adoc[tag=common-datastore-config]
setProjectId(String) _[Default: null]_ ::
Optional.
The `project id` of your project.
You don't need to set this if you carried out the instructions in the xref:pg-server-session-datastore-gcloud-prep[Preparation] section, but you might want to set this - along with the `host` and/or `namespace` parameters - if you want more explicit control over connecting to GCloud.
setHost(String) _[Default: null]_ ::
Optional.
This is the name of the host for the GCloud DataStore.
If you leave it unset, then the GCloud DataStore library will work out the host to contact.
You might want to use this - along with `projectId` and/or `namespace` parameters - if you want more explicit control over connecting to GCloud.
setNamespace(String) _[Default: null]_ ::
Optional.
If set, partitions the visibility of session data in multi-tenant deployments.
More information can be found link:https://cloud.google.com/datastore/docs/concepts/multitenancy[here.]
setMaxRetries(int) _[Default: 5]_ ::
This is the maximum number of retries to connect to GCloud DataStore in order to write a session.
This is used in conjunction with the `backoffMs` parameter to control the frequency with which Jetty will retry to contact GCloud to write out a session.
setBackoffMs(int) _[Default: 1000]_ ::
This is the interval that Jetty will wait in between retrying failed writes.
Each time a write fails, Jetty doubles the previous backoff.
Used in conjunction with the `maxRetries` parameter.
setEntityDataModel(EntityDataModel)::
The `EntityDataModel` encapsulates the type (called "kind" in GCloud DataStore) of stored session objects and the names of its fields.
If you do not set this parameter, `GCloudSessionDataStore` uses all default values, which should be sufficient for most needs.
Should you need to customize this, the methods and their defaults are:
* *setKind(String)* _[Default: "GCloudSession"]_ this is the type of the session object.
* *setId(String)* _[Default: "id"]_ this is the name of the field storing the session id.
* *setContextPath(String)* _[Default: "contextPath"]_ this is name of the field storing the canonicalized context path of the context to which the session belongs.
* *setVhost(String)* _[Default: "vhost"]_ this the name of the field storing the canonicalized virtual host of the context to which the session belongs.
* *setAccessed(String)* _[Default: "accessed"]_ this is the name of the field storing the current access time of the session.
* *setLastAccessed(String)* _[Default: "lastAccessed"]_ this is the name of the field storing the last access time of the session.
* *setCreateTime(String)* _[Default: "createTime"]_ this is the name of the field storing the time in ms since the epoch, at which the session was created.
* *setCookieSetTime(String)* _[Default: "cookieSetTime"]_ this is the name of the field storing time at which the session cookie was last set.
* *setLastNode(String)* _[Default: "lastNode"]_ this is the name of the field storing the `workerName` of the last node to manage the session.
* *setExpiry(String)* _[Default: "expiry"]_ this is the name of the field storing the time, in ms since the epoch, at which the session will expire.
* *setMaxInactive(String)* _[Default: "maxInactive"]_ this is the name of the field storing the session timeout in ms.
* *setAttributes(String)* _[Default: "attributes"]_ this is the name of the field storing the session attribute map.
Here's an example of configuring a `GCloudSessionDataStoreFactory`:
[source,java,indent=0]
----
include::../../{doc_code}/org/eclipse/jetty/docs/programming/server/session/SessionDocs.java[tags=gcloudsessiondatastorefactory]
----

View File

@ -0,0 +1,62 @@
//
// ========================================================================
// Copyright (c) 1995 Mort Bay Consulting Pty Ltd and others.
//
// This program and the accompanying materials are made available under the
// terms of the Eclipse Public License v. 2.0 which is available at
// https://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0
// which is available at https://www.apache.org/licenses/LICENSE-2.0.
//
// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0
// ========================================================================
//
[[pg-server-session-datastore-infinispan]]
===== The InfinispanSessionDataStore
The `InfinispanSessionDataStore` supports persistent storage of session data via the https://infinispan.org/[Infinispan] data grid.
You may use Infinispan in either _embedded mode_, where it runs in the same process as Jetty, or in _remote mode_ mode, where your Infinispan instance is on another node.
For more information on Infinispan, including some code examples, consult the https://infinispan.org/[Infinispan documentation].
See below for some code examples of configuring the link:{javadoc-url}/org/eclipse/jetty/session/infinispan/InfinispanSessionDataStore.html[InfinispanSessionDataStore] in Jetty.
Note that the configuration options are the same for both the `InfinispanSessionDataStore` and the link:{javadoc-url}/org/eclipse/jetty/session/infinispan/InfinispanSessionDataStoreFactory.html[InfinispanSessionDataStoreFactory].
Use the latter to apply the same configuration to multiple ``InfinispanSessionDataStore``s.
include::session-sessiondatastore.adoc[tag=common-datastore-config]
setCache(BasicCache<String, InfinispanSessionData> cache)::
Infinispan uses a cache API as the interface to the data grid and this method configures Jetty with the cache instance.
This cache can be either an _embedded_ cache - also called a "local" cache in Infinispan parlance - or a _remote_ cache.
setSerialization(boolean) _[Default: false]_ ::
When the `InfinispanSessionDataStore` starts, if it detects the Infinispan classes for remote caches on the classpath, it will automatically assume `serialization` is true, and thus that `SessionData` will be serialized over-the-wire to a remote cache.
You can use this parameter to override this.
If this parameter is `true`, the `InfinispanSessionDataStore` returns true for the `isPassivating()` method, but false otherwise.
setInfinispanIdleTimeoutSec(int) _[Default: 0]_ ::
This controls the Infinispan option whereby it can detect and delete entries that have not been referenced for a configurable amount of time.
A value of 0 disables it.
NOTE: If you use this option, expired sessions will be summarily deleted from Infinispan _without_ the normal session invalidation handling (eg calling of lifecycle listeners).
Only use this option if you do not have session lifecycle listeners that must be called when a session is invalidated.
setQueryManager(QueryManager)::
If this parameter is not set, the `InfinispanSessionDataStore` will be unable to scavenge for unused sessions.
In that case, you can use the `infinispanIdleTimeoutSec` option instead to prevent the accumulation of expired sessions.
When using Infinispan in _embedded_ mode, configure the link:{javadoc-url}/org/eclipse/jetty/session/infinispan/EmbeddedQueryManager.html[EmbeddedQueryManager] to enable Jetty to query for expired sessions so that they may be property invalidated and lifecycle listeners called.
When using Infinispan in _remote_ mode, configure the link:{javadoc-url}/org/eclipse/jetty/session/infinispan/RemoteQueryManager.html[RemoteQueryManager] instead.
Here is an example of configuring an `InfinispanSessionDataStore` in code using an _embedded_ cache:
[source,java,indent=0]
----
include::../../{doc_code}/org/eclipse/jetty/docs/programming/server/session/SessionDocs.java[tags=infinispanembed]
----
Here is an example of configuring an `InfinispanSessionDataStore` in code using a _remote_ cache:
[source,java,indent=0]
----
include::../../{doc_code}/org/eclipse/jetty/docs/programming/server/session/SessionDocs.java[tags=infinispanremote]
----

View File

@ -14,7 +14,7 @@
[[pg-server-session-datastore-jdbc]]
===== The JDBCSessionDataStore
The `JDBCSessionDataStore` supports persistent storage of session data in a relational database.
The link:{javadoc-url}/org/eclipse/jetty/session/JDBCSessionDataStore.html[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.
[plantuml]
@ -27,9 +27,57 @@ JDBCSessionDataStore "1" *-- "1" DatabaseAdaptor
JDBCSessionDataStore "1" *-- "1" SessionTableSchema
----
`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:
The link:{javadoc-url}/org/eclipse/jetty/session/JDBCSessionDataStore.html[JDBCSessionDataStore] and corresponding link:{javadoc-url}/org/eclipse/jetty/session/JDBCSessionDataStoreFactory.html[JDBCSessionDataStoreFactory] support the following configuration:
include::session-sessiondatastore.adoc[tag=common-datastore-config]
setDatabaseAdaptor(DatabaseAdaptor)::
A `JDBCSessionDataStore` requires a `DatabaseAdapter`, otherwise an `Exception` is thrown at start time.
setSessionTableSchema(SessionTableSchema)::
If a `SessionTableSchema` has not been explicitly set, one with all values defaulted is created at start time.
====== The DatabaseAdaptor
Many databases use different keywords for types such as `long`, `blob` and `varchar`.
Jetty will detect the type of the database at runtime by interrogating the metadata associated with a database connection.
Based on that metadata Jetty will try to select that database's preferred keywords.
However, you may need to instead explicitly configure these as described below.
setDatasource(String)::
setDatasource(Datasource)::
Either the JNDI name of a `Datasource` to look up, or the `Datasource` itself.
Alternatively you can set the *driverInfo*, see below.
[source,java,indent=0]
----
include::../../{doc_code}/org/eclipse/jetty/docs/programming/server/session/SessionDocs.java[tags=dbaDatasource]
----
setDriverInfo(String, String)::
setDriverInfo(Driver, String)::
This is the name or instance of a `Driver` class and a connection URL.
Alternatively you can set the *datasource*, see above.
[source,java,indent=0]
----
include::../../{doc_code}/org/eclipse/jetty/docs/programming/server/session/SessionDocs.java[tags=dbaDriver]
----
setBlobType(String) _[Default: "blob" or "bytea" for Postgres]_ ::
The type name used to represent "blobs" by the database.
setLongType(String) _[Default: "bigint" or "number(20)" for Oracle]_ ::
The type name used to represent large integers by the database.
setStringType(String) _[Default: "varchar"]_::
The type name used to represent character data by the database.
====== The SessionTableSchema
`SessionData` is stored in a table with one row per session.
This is the definition of the table with the table name, column names, and type keywords all at their default settings:
[caption="Table:"]
.JettySessions
@ -51,103 +99,52 @@ This is the table, with the table name, column names and type keywords at their
|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.
Use the `SessionTableSchema` class to customize these names.
====== Configuration
The link:{javadoc-url}/org/eclipse/jetty/server/session/JDBCSessionDataStore.html[JDBCSessionDataStore] and corresponding link:{javadoc-url}/org/eclipse/jetty/server/session/JDBCSessionDataStoreFactory.html[JDBCSessionDataStoreFactory] supports the following configuration:
include::session-sessiondatastore.adoc[tag=common-datastore-config]
DatabaseAdaptor::
The `DatabaseAdaptor` can connect to a database either via a `javax.sql.Datasource` or a `java.sql.Driver`.
Additionally, a database-specific keyword can be configured for the `blob`, `varchar` and `long` types.
Note that the `DatabaseAdaptor` 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 using `Postgres` or `Oracle`.
datasource:::
This can either be a `Datasource` instance or the jndi name of a `Datasource` to look up.
[source,java,indent=0]
----
include::../../{doc_code}/org/eclipse/jetty/docs/programming/server/session/SessionDocs.java[tags=dbaDatasource]
----
driverInfo:::
This is the name or instance of a jdbc `Driver` class and a connection url.
[source,java,indent=0]
----
include::../../{doc_code}/org/eclipse/jetty/docs/programming/server/session/SessionDocs.java[tags=dbaDriver]
----
blobType:::
Default `blob` or `bytea` for Postgres.
longType:::
Default `bigint` or `number(20)` for Oracle.
stringType:::
Default `varchar`.
SessionTableSchema::
schemaName:::
catalogName:::
setSchemaName(String), setCatalogName(String) _[Default: null]_ ::
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.
These extra scoping names come into play at startup time when Jetty determines if the session table already exists, or creates it on-the-fly.
If your database is not using schema or catalog name scoping, leave these unset.
If your database is configured with a schema or catalog name, use the special value "INFERRED" and Jetty will extract them from the database metadata.
Alternatively, set them explicitly using these methods.
tableName:::
Default `JettySessions`.
setTableName(String) _[Default:"JettySessions"]_ ::
This is the name of the table in which session data is stored.
accessTimeColumn:::
Default `accessTime`.
setAccessTimeColumn(String) _[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`.
setContextPathColumn(String) _[Default: "contextPath"]_ ::
This is the name of the column that stores the `contextPath` of a session.
cookieTimeColumn:::
Default `cookieTime`.
setCookieTimeColumn(String) _[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`.
setCreateTimeColumn(String) _[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`.
setExpiryTimeColumn(String) _[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`.
setLastAccessTimeColumn(String) _[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`.
setLastSavedTimeColumn(String) _[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`.
setIdColumn(String) _[Default: "sessionId"]_ ::
This is the name of the column that stores the id of a session.
lastNodeColumn:::
Default `lastNode`.
setLastNodeColumn(String) _[Default: "lastNode"]_ ::
This is the name of the column that stores the `workerName` of the last node to write a session.
virtualHostColumn:::
Default `virtualHost`.
setVirtualHostColumn(String) _[Default: "virtualHost"]_ ::
This is the name of the column that stores the first virtual host of the context of a session.
maxIntervalColumn:::
Default `maxInterval`.
setMaxIntervalColumn(String) _[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`.
setMapColumn(String) _[Default: "map"]_ ::
This is the name of the column that stores the serialized attributes of a session.

View File

@ -64,39 +64,29 @@ The time (in ms since the epoch) at which this session was most recently accesse
lastNode::
The xref:pg-server-session-workername[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.
An object that is updated every time a session is written for a context.
====== Configuration
You can configure either a link:{javadoc-url}/org/eclipse/jetty/nosql/mongodb/MongoSessionDataStore.html[MongoSessionDataStore] individually, or a link:{javadoc-url}/org/eclipse/jetty/nosql/mongodb/MongoSessionDataStore.html[MongoSessionDataStoreFactory] if you want multiple ``SessionHandler``s to use ``MongoSessionDataStore``s that are identically configured.
You can configure either a link:{javadoc-url}/org/eclipse/jetty/nosql/mongodb/MongoSessionDataStore.html[MongoSessionDataStore] individually, or a link:{javadoc-url}/org/eclipse/jetty/nosql/mongodb/MongoSessionDataStoreFactory.html[MongoSessionDataStoreFactory] if you want multiple ``SessionHandler``s to use ``MongoSessionDataStore``s that are identically configured.
The configuration methods for the `MongoSessionDataStoreFactory` are:
include::session-sessiondatastore.adoc[tag=common-datastore-config]
dbName::
setDbName(String)::
This is the name of the database.
collectionName::
setCollectionName(String)::
The name of the document collection.
There are two alternative ways to specify the connection to mongodb:
setConnectionString(String)::
a mongodb url, eg "mongodb://localhost".
Alternatively, you can specify the *host,port* combination instead, see below.
setHost(String)::
setPort(int)::
the hostname and port number of the mongodb instance to contact.
Alternatively, you can specify the *connectionString* instead, see above.
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`:
This is an example of configuring a `MongoSessionDataStoreFactory`:
[source,java,indent=0]
----
include::../../{doc_code}/org/eclipse/jetty/docs/programming/server/session/SessionDocs.java[tags=mongosdfactory]
----
// TODO: the code example is missing.
// Here's an alternate example, configuring a `MongoSessionDataStore` directly:
//[source,java,indent=0]
//----
//include::../../{doc_code}/org/eclipse/jetty/docs/programming/server/session/SessionDocs.java[tags=mongosessiondatastore]
//----

View File

@ -0,0 +1,24 @@
//
// ========================================================================
// Copyright (c) 1995 Mort Bay Consulting Pty Ltd and others.
//
// This program and the accompanying materials are made available under the
// terms of the Eclipse Public License v. 2.0 which is available at
// https://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0
// which is available at https://www.apache.org/licenses/LICENSE-2.0.
//
// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0
// ========================================================================
//
[[pg-server-session-datastore-null]]
===== The NullSessionDataStore
The `NullSessionDataStore` is a trivial implementation of `SessionDataStore` that does not persist `SessionData`.
Use it when you want your sessions to remain in memory _only_.
Be careful of your `SessionCache` when using the `NullSessionDataStore`:
* if using a `NullSessionCache` then your sessions are neither shared nor saved
* if using a `DefaultSessionCache` with eviction settings, your session will cease to exist when it is evicted from the cache
If you have not configured any other xref:pg-server-session-datastore[SessionDataStore], when a `SessionHandler` aka `AbstractSessionManager` starts up, it will instantiate a `NullSessionDataStore`.

View File

@ -14,9 +14,9 @@
[[pg-server-session-datastore]]
==== The SessionDataStore
A link:{javadoc-url}/org/eclipse/jetty/server/session/SessionDataStore.html[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.
A link:{javadoc-url}/org/eclipse/jetty/session/SessionDataStore.html[SessionDataStore] mediates the storage, retrieval and deletion of `SessionData`.
There is one `SessionDataStore` per `SessionCache` and thus one per context.
Jetty provides a number of alternative `SessionDataStore` implementations:
[plantuml]
----
@ -24,6 +24,7 @@ title SessionDataStores
interface SessionDataStore
class AbstractSessionDataStore
class NullSessionDataStore
class FileSessionDataStore
class GCloudSessionDataStore
class HazelcastSessionDataStore
@ -34,6 +35,7 @@ class CachingSessionDataStore
SessionDataStore <|-- AbstractSessionDataStore
AbstractSessionDataStore <|-- NullSessionDataStore
AbstractSessionDataStore <|-- FileSessionDataStore
AbstractSessionDataStore <|-- GCloudSessionDataStore
AbstractSessionDataStore <|-- HazelcastSessionDataStore
@ -43,36 +45,123 @@ AbstractSessionDataStore <|-- MongoSessionDataStore
SessionDataStore <|-- CachingSessionDataStore
----
The link:{javadoc-url}/org/eclipse/jetty/server/session/AbstractSessionDataStore.html[AbstractSessionDataStore] provides most of the behaviour common to ``SessionDataStore``s:
NullSessionDataStore::
Does not store `SessionData`, meaning that sessions will exist in-memory only.
See xref:pg-server-session-datastore-null[NullSessionDataStore]
passivation::
Supporting passivation means that session data is serialized.
FileSessionDataStore::
Uses the file system to persist `SessionData`.
See xref:pg-server-session-datastore-file[FileSessionDataStore] for more information.
GCloudSessionDataStore::
Uses GCloud Datastore for persisting `SessionData`.
See xref:pg-server-session-datastore-gcloud[GCloudSessionDataStore] for more information.
HazelcastSessionDataStore::
Uses Hazelcast for persisting `SessionData`.
InfinispanSessionDataStore::
Uses http://infinispan.org[Infinispan] for persisting `SessionData`.
See xref:pg-server-session-datastore-infinispan[InfinispanSessionDataStore] for more information.
JDBCSessionDataStore::
Uses a relational database via JDBC API to persist `SessionData`.
See xref:pg-server-session-datastore-jdbc[JDBCSessionDataStore] for more information.
MongoSessionDataStore::
Uses http://www.mongodb.com[MongoDB] document database to persist `SessionData`.
See xref:pg-server-session-datastore-mongo[MongoSessionDataStore] for more information.
CachingSessionDataStore::
Uses http://memcached.org[memcached] to provide an L2 cache of `SessionData` while delegating to another `SessionDataStore` for persistence of `SessionData`.
See xref:pg-server-session-cachingsessiondatastore[CachingSessionDataStore] for more information.
Most of the behaviour common to ``SessionDataStore``s is provided by the link:{javadoc-url}/org/eclipse/jetty/session/AbstractSessionDataStore.html[AbstractSessionDataStore] class.
You are strongly encouraged to use this as the base class for implementing your custom `SessionDataStore`.
Some important methods are:
isPassivating()::
Boolean. "True" 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 custom `SessionDataStore` you need to decide whether or not passivation will be supported.
Others can store an object in shared memory, e.g. Infinispan and thus don't serialize session data.
In Servlet environments, whether a `SessionDataStore` reports that it is capable of passivating controls whether ``HttpSessionActivationListener``s will be called.
When implementing a custom `SessionDataStore` you need to decide whether you will support passivation or not.
[[pg-server-session-datastore-skip]]
//tag::common-datastore-config[]
savePeriod::
setSavePeriodSec(int) _[Default:0]_ ::
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 a `Session`, the `SessionData` for that `Session` is always persisted, even if the only thing that changed is the `lastAccessTime`.
If the `savePeriod` is non-zero, the `SessionData` will not be persisted if no session attributes changed, _unless_ the time since the last save exceeds the `savePeriod`.
If the `savePeriodSec` is non-zero, the `SessionData` will not be persisted if no session attributes changed, _unless_ the time since the last save exceeds the `savePeriod`.
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::
setGracePeriodSec(int) _[Default:3600]_ ::
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 the `gracePeriod` to provide a bit of leeway around the moment of expiry during xref:pg-server-session-housekeeper[scavenge]:
Thus, we use the `gracePeriod` to provide a bit of leeway around the moment of expiry during xref:pg-server-session-housekeeper[scavenging]:
* on every xref:pg-server-session-housekeeper[scavenge] cycle an `AbstractSessionDataStore` searches for sessions that belong to the context that expired at least one `gracePeriod` ago
* infrequently the `AbstractSessionDataStore` searches for and summarily deletes sessions - from any context - that expired at least 10 ``gracePeriod``s ago
//end::common-datastore-config[]
[NOTE]
====
The trivial link:{javadoc-url}/org/eclipse/jetty/server/session/NullSessionDataStore.html[NullSessionDataStore] - which does not persist sessions - is the default used by the `SessionHandler`.
====
===== Custom SessionDataStores
When implementing a `SessionDataStore` for a particular persistence technology, you should base it off the `AbstractSessionDataStore` class.
Firstly, it is important to understand the components of a unique key for a session suitable for storing in a persistence mechanism.
Consider that although multiple contexts may share the _same_ session id (ie cross-context dispatch), the data in those sessions must be distinct.
Therefore, when storing session data in a persistence mechanism that is shared by many nodes in a cluster, the session must be identified by a combination of the id _and_ the context.
The ``SessionDataStore``s use the following information to synthesize a unique key for session data that is suitable to the particular persistence mechanism :
[[pg-server-session-key]]
id::
This is the id as generated by the `SessionIdManager`
context::
The path of the context associated with the session.
virtual host::
The first virtual host - if any - associated with the context.
The link:{javadoc-url}/org/eclipse/jetty/session/SessionContext.html[SessionContext] class, of which every `AbstractSessionDataStore` has an instance, will provide these components to you in a canonicalized form.
Then you will need to implement the following methods:
public boolean doExists(String id)::
Check if data for the given session exists in your persistence mechanism.
The id is always relative to the context, see xref:pg-server-session-key[above].
public void doStore(String id, SessionData data, long lastSaveTime)::
Store the session data into your persistence mechanism.
The id is always relative to the context, see xref:pg-server-session-key[above].
public SessionData doLoad(String id)::
Load the session from your persistent mechanism.
The id is always relative to the context, see xref:pg-server-session-key[above].
public Set<String> doCheckExpired(Set<String> candidates, long time)::
Verify which of the suggested session ids have expired since the time given, according to the data stored in your persistence mechanism.
This is used during scavenging to ensure that a session that is a candidate for expiry according to _this_ node is not in-use on _another_ node.
The sessions matching these ids will be loaded as ``ManagedSession``s and have their normal expiration lifecycle events invoked.
The id is always relative to the context, see xref:pg-server-session-key[above].
public Set<String> doGetExpired(long before)::
Find the ids of sessions that expired at or before the time given.
The sessions matching these ids will be loaded as ``ManagedSession``s and have their normal expiration lifecycle events invoked.
The id is always relative to the context, see xref:pg-server-session-key[above].
public void doCleanOrphans(long time)::
Find the ids of sessions that expired at or before the given time, _independent of the context they are in_.
The purpose is to find sessions that are no longer being managed by any node.
These sessions may even belong to contexts that no longer exist.
Thus, any such sessions must be summarily deleted from the persistence mechanism and cannot have their normal expiration lifecycle events invoked.
===== The SessionDataStoreFactory
Every `SessionDataStore` has a factory class that creates instances based on common configuration.
All `SessionDataStoreFactory` implementations support configuring:
setSavePeriodSec(int)::
setGracePeriodSec(int)::

View File

@ -14,137 +14,109 @@
[[pg-server-session-handler]]
==== 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.
A `SessionHandler` is a `Handler` that implements the `SessionManager`, and is thus responsible for the creation, maintenance and propagation of sessions.
There are `SessionHandlers` for both the core and the various Servlet environments.
===== Configuration
Note that in the Servlet environments, each `ServletContextHandler` or `WebAppContext` has at most a single `SessionHandler`.
The majority of configuration for the link:{javadoc-url}/org/eclipse/jetty/server/session/SessionHandler.html[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:
Both core and Servlet environment `SessionHandlers` can be configured programmatically.
Here are some of the most important methods that you may call to customize your session setup.
Note that in Servlet environments, some of these methods also have analogous Servlet API methods and/or analogous `web.xml` declarations and also equivalent context init params.
These alternatives are noted below.
checkingRemoteSessionIdEncoding::
Boolean, default `false`.
This controls whether or not the `javax.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.ee9.servlet.CheckingRemoteSessionIdEncoding` context init paramter
setCheckingRemoteSessionIdEncoding(boolean) _[Default:false]_ ::
This controls whether response urls will be encoded with the session id as a path parameter when the URL is destined for a remote node. +
_Servlet environment alternatives:_
* `org.eclipse.jetty.session.CheckingRemoteSessionIdEncoding` context init parameter
setMaxInactiveInterval::
Integer, seconds.
This is the amount of time after which an unused session may be scavenged.
This can also be configured by:
setMaxInactiveInterval(int) _[Default:-1]_ ::
This is the amount of time in seconds after which an unused session may be scavenged. +
_Servlet environment alternatives:_
* `<session-config><session-timeout/></session-config>` element in `web.xml` (NOTE! this element is specified in _minutes_ but this method uses _seconds_).
* `ServletContext.setSessionTimeout(int)` where the timeout is configured in _minutes_.
* defining the `<session-config><session-timeout/></session-config>` element in `web.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`.
If `true`, 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 in `web.xml`
setHttpOnly(boolean) _[Default:false]_ ::
If `true`, the session cookie will not be exposed to client-side scripting code. +
_Servlet environment alternatives:_
* `SessionCookieConfig.setHttpOnly(boolean)`
* `<session-config><cookie-config><http-only/></cookie-config></session-config>` element in `web.xml`
[[pg-server-session-handler-refreshcookie]]
refreshCookieAge::
Integer, seconds, default is `-1`.
This controls resetting the session cookie when `SessionCookieConfig.setMaxAge(int)` is non-zero.
setRefreshCookieAge(int) _[Default:-1]_ ::
Value in seconds that controls resetting the session cookie when `SessionCookieConfig.setMaxAge(int)` is non-zero.
See also xref:pg-server-session-handler-maxAge[setting the max session cookie age with an init parameter].
If the amount of time since the session cookie was last set exceeds this time, the session cookie is regenerated to keep the session cookie valid.
sameSite::
`HttpCookie.SameSite`, default `null`.
setSameSite(HttpCookie.SameSite) _[Default:null]_ ::
The values are `HttpCookie.SameSite.NONE`, `HttpCookie.SameSite.STRICT`, `HttpCookie.SameSite.LAX`.
secureRequestOnly::
Boolean, default `true`.
If `true` and the request is HTTPS, the set session cookie will be marked as `secure`, meaning the client will only send the session cookie to the server on subsequent requests over HTTPS.
This can also be configured by:
setSecureRequestOnly(boolean) _[Default:true]_::
If `true` and the request is HTTPS, the set session cookie will be marked as `secure`, meaning the client will only send the session cookie to the server on subsequent requests over HTTPS. +
_Servlet environment alternatives:_
* `SessionCookieConfig.setSecure(true)`, in which case the set session cookie will _always_ be marked as `secure`, even if the request triggering the creation of the cookie was not over HTTPS.
* `<session-config><cookie-config><secure/></cookie-config></session-config>` element in `web.xml`
* using the `javax.servlet.SessionCookieConfig.setSecure(true)` method, in which case the set session cookie will _always_ be marked as `secure`, even if the request triggering the creation of the cookie was not over HTTPS.
setSessionCookie(String) _[Default:"JSESSIONID"]_::
This is the name of the session cookie. +
_Servlet environment alternatives:_
* `SessionCookieConfig.setName(String)`
* `<session-config><cookie-config><name/></cookie-config></session-config>` element in `web.xml`
* `org.eclipse.jetty.session.SessionCookie` context init parameter.
sessionCookie::
String, default is `JSESSIONID`.
This is the name of the session cookie.
It can alternatively be configured by:
setSessionIdPathParameterName(String) _[Default:"jsessionid"]_::
This is the name of the path parameter used to transmit the session id on request URLs, and on encoded URLS in responses. +
_Servlet environment alternatives:_
* `org.eclipse.jetty.session.SessionIdPathParameterName` context init parameter
* using `javax.servlet.SessionCookieConfig.setName(String)` method
* setting the `org.eclipse.jetty.ee9.servlet.SessionCookie` context init parameter.
sessionIdPathParameterName::
String, default is `jsessionid`.
This is the name of the path parameter used to transmit the session id on request URLs, and on encoded URLS in responses.
It can alternatively be configured by:
* setting the `org.eclipse.jetty.ee9.servlet.SessionIdPathParameterName` context init parameter
sessionTrackingModes::
`Set<javax.servlet.SessionTrackingMode>`.
Default is `SessionTrackingMode.COOKIE`, `SessionTrackingMode.URL`.
This can also be configured by:
* using the `setSessionTrackingModes(Set<javax.servlet.SessionTrackingMode>)` method
* using the `javax.servlet.ServletContext.setSessionTrackingModes<Set<javax.servlet.SessionTrackingMode>)` method
setSessionTrackingModes(Set<SessionTrackingMode>) _[Default:{`SessionTrackingMode.COOKIE`, `SessionTrackingMode.URL`}]_::
_Servlet environment alternatives:_
* `ServletContext.setSessionTrackingModes<Set<SessionTrackingMode>)`
* defining up to three ``<tracking-mode>``s for the `<session-config>` element in `web.xml`
usingCookies::
Boolean, default `true`.
Determines whether or not the `SessionHandler` will look for session cookies on requests, and will set session cookies on responses.
setUsingCookies(boolean) _[Default:true]_ ::
Determines whether the `SessionHandler` will look for session cookies on requests, and will set session cookies on responses.
If `false` session ids must be transmitted as path params on URLs.
This can also be configured by:
* using the `setSessionTrackingModes(Set<javax.servlet.SessionTrackingMode>)` method
* using the `javax.servlet.ServletContext.setSessionTrackingModes<Set<javax.servlet.SessionTrackingMode>)` method
There are also a few session settings that do not have SessionHandler setters, but can be configured with context init parameters:
[[pg-server-session-handler-maxAge]]
org.eclipse.jetty.ee9.servlet.MaxAge::
setMaxAge(int) _[Default:-1]_::
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 xref:pg-server-session-handler-refreshcookie[refreshing the session cookie].
The value can also be configured by:
See also xref:pg-server-session-handler-refreshcookie[refreshing the session cookie]. +
_Servlet environment alternatives:_
* `ServletContext.getSessionCookieConfig().setMaxAge(int)`
* `org.eclipse.jetty.session.MaxAge` context init parameter
* calling the `SessionCookieConfig.setMaxAge(int)` method.
setSessionDomain(String) _[Default:null]_ ::
This is the domain of the session cookie. +
_Servlet environment alternatives:_
* `ServletContext.getSessionCookieConfig().setDomain(String)`
* `<session-config><cookie-config><domain/></cookie-config></session-config>` element in `web.xml`
* `org.eclipse.jetty.session.SessionDomain` context init parameter
org.eclipse.jetty.ee9.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 in `web.xml`
org.eclipse.jetty.ee9.servlet.SessionPath::
String, default `null`.
setSessionPath(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 in `web.xml`
If nothing is configured, the context path is used instead, defaulting to `/`. +
_Servlet environment alternatives:_
* `ServletContext.getSessionCookieConfig().setPath(String)`
* `<session-config><cookie-config><path/></cookie-config></session-config>` element in `web.xml`
* `org.eclipse.jetty.session.SessionPath` context init parameter
===== Statistics
Some statistics about the sessions for a context can be obtained from the `SessionHandler`, either by calling the methods directly or via `jmx`:
Some statistics about the sessions for a context can be obtained from the `SessionHandler`, either by calling the methods directly or via JMX:
sessionsCreated::
getSessionsCreated()::
This is the total number of sessions that have been created for this context since Jetty started.
sessionTimeMax::
getSessionTimeMax()::
The longest period of time a session was valid in this context before being invalidated.
sessionTimeMean::
getSessionTimeMean()::
The average period of time a session in this context was valid.
sessionTimeStdDev::
getSessionTimeStdDev()::
The standard deviation of the session validity times for this context.
sessionTimeTotal::
getSessionTimeTotal()::
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.

View File

@ -19,28 +19,25 @@ Its purpose is to generate fresh, unique session ids and to coordinate the re-us
The `SessionIdManager` is agnostic with respect to the type of clustering technology chosen.
Jetty provides a default implementation - the link:{javadoc-url}/org/eclipse/jetty/server/session/DefaultSessionIdManager.html[DefaultSessionIdManager] - which should meet the needs of most users.
NOTE: If you do not explicitly configure a `SessionIdManager`, then when the `SessionHandler` starts, it will use an instance of the `DefaultSessionIdManager`.
Jetty provides a default implementation - the link:{javadoc-url}/org/eclipse/jetty/session/DefaultSessionIdManager.html[DefaultSessionIdManager] - which should meet the needs of most users.
[[pg-server-session-defaultidmgr]]
===== The DefaultSessionIdManager
At startup, if no instance of the `HouseKeeper` has been explicitly set, the `DefaultSessionIdManager` will create one.
[[pg-server-session-workername]]
Also at startup, the `workerName` is determined.
The `workerName` must be unique per `Server`, and identifies the server in a cluster.
A single instance of the `DefaultSessionIdManager` should be created and registered as a bean on the `Server` instance so that all ``SessionHandler``'s share the same instance.
This is done by the Jetty `session` module, but can be done programmatically instead.
As a fallback, when an individual `SessionHandler` starts up, if it does not find the `SessionIdManager` already present for the `Server` it will create and register a bean for it.
That instance will be shared by the other ``SessionHandler``s.
The most important configuration parameter for the `DefaultSessionIdManager` is the `workerName`, which uniquely 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]+
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`.
It is _essential_ to change this default if you have more than one `Server`.
Here is an example of explicitly setting up a `DefaultSessionIdManager` with a `workerName` of `server3` in code:
@ -49,7 +46,28 @@ Here is an example of explicitly setting up a `DefaultSessionIdManager` with a `
include::../../{doc_code}/org/eclipse/jetty/docs/programming/server/session/SessionDocs.java[tags=default]
----
==== Implementing a Custom SessionIdManager
[[pg-server-session-housekeeper]]
===== The HouseKeeper
The `DefaultSessionIdManager` creates a link:{javadoc-url}/org/eclipse/jetty/session/HouseKeeper.html[HouseKeeper], which periodically scans for, and eliminates, expired sessions (referred to as "scavenging").
The period of the scan is controlled by the `setIntervalSec(int)` method, defaulting to 600secs.
Setting a negative or 0 value prevents scavenging occurring.
[IMPORTANT]
====
The `HouseKeeper` semi-randomly adds 10% to the configured `intervalSec`.
This is to help prevent sync-ing up of servers in a cluster that are all restarted at once, and slightly stagger their scavenge cycles to ensure any load on the persistent storage mechanism is spread out.
====
Here is an example of creating and configuring a `HouseKeeper` for the `DefaultSessionIdManager` in code:
[source,java,indent=0]
----
include::../../{doc_code}/org/eclipse/jetty/docs/programming/server/session/SessionDocs.java[tags=housekeeper]
----
===== Implementing a Custom SessionIdManager
If the `DefaultSessionIdManager` does not meet your needs, you can extend it, or implement the `SessionIdManager` interface directly.
@ -60,25 +78,3 @@ The `workerName` becomes important in clustering scenarios because sessions can
* 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.
[[pg-server-session-housekeeper]]
===== The HouseKeeper
There is a maximum of one link:{javadoc-url}/org/eclipse/jetty/server/session/HouseKeeper.html[HouseKeeper] per `SessionIdManager`.
Its purpose is to periodically poll the link:{javadoc-url}/org/eclipse/jetty/server/session/SessionHandler.html[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.
[IMPORTANT]
====
The HouseKeeper semi-randomly adds an additional `10%` of the configured `intervalSec`.
This is to help prevent sync-ing up of servers in a cluster that are all restarted at once, and slightly stagger their scavenge cycles to ensure any load on the persistent storage mechanism is spread out.
====
This code example shows how to configure a `HouseKeeper`, along with a `DefaultSessionIdManager`:
[source,java,indent=0]
----
include::../../{doc_code}/org/eclipse/jetty/docs/programming/server/session/SessionDocs.java[tags=housekeeper]
----

View File

@ -15,15 +15,16 @@
=== 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.
include::session-architecture.adoc[]
include::session-sessionidmgr.adoc[]
include::session-sessionhandler.adoc[]
include::session-sessioncache.adoc[]
include::session-sessiondatastore.adoc[]
include::session-sessiondatastore-null.adoc[]
include::session-sessiondatastore-file.adoc[]
include::session-sessiondatastore-jdbc.adoc[]
include::session-sessiondatastore-mongo.adoc[]
include::session-sessiondatastore-infinispan.adoc[]
include::session-sessiondatastore-gcloud.adoc[]
include::session-cachingsessiondatastore.adoc[]

View File

@ -14,14 +14,21 @@
package org.eclipse.jetty.docs.programming.server.session;
import java.io.File;
import java.io.FileInputStream;
import java.net.InetSocketAddress;
import java.util.Properties;
import org.eclipse.jetty.ee10.servlet.ServletContextHandler;
import org.eclipse.jetty.ee10.servlet.SessionHandler;
import org.eclipse.jetty.ee10.webapp.WebAppContext;
import org.eclipse.jetty.gcloud.session.GCloudSessionDataStoreFactory;
import org.eclipse.jetty.io.Content;
import org.eclipse.jetty.memcached.session.MemcachedSessionDataMapFactory;
import org.eclipse.jetty.nosql.mongodb.MongoSessionDataStoreFactory;
import org.eclipse.jetty.server.Handler;
import org.eclipse.jetty.server.Request;
import org.eclipse.jetty.server.Response;
import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.server.Session;
import org.eclipse.jetty.server.handler.ContextHandlerCollection;
import org.eclipse.jetty.session.CachingSessionDataStoreFactory;
import org.eclipse.jetty.session.DatabaseAdaptor;
@ -35,19 +42,99 @@ import org.eclipse.jetty.session.NullSessionCache;
import org.eclipse.jetty.session.NullSessionCacheFactory;
import org.eclipse.jetty.session.NullSessionDataStore;
import org.eclipse.jetty.session.SessionCache;
import org.eclipse.jetty.session.SessionHandler;
import org.eclipse.jetty.session.infinispan.InfinispanSessionData;
import org.eclipse.jetty.session.infinispan.InfinispanSessionDataStore;
import org.eclipse.jetty.util.Callback;
import org.infinispan.Cache;
import org.infinispan.client.hotrod.RemoteCache;
import org.infinispan.client.hotrod.RemoteCacheManager;
import org.infinispan.client.hotrod.configuration.ConfigurationBuilder;
import org.infinispan.commons.marshall.ProtoStreamMarshaller;
import org.infinispan.manager.DefaultCacheManager;
@SuppressWarnings("unused")
public class SessionDocs
{
public void minimumDefaultSessionIdManager()
public void cachingSessionDataStore()
{
//tag::default[]
//tag::cachingsds[]
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.addBean(idMgr, true);
//end::default[]
//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 SessionManagers will use it
//to make FileSessionDataStores that use memcached as an L2 SessionData cache.
server.addBean(cachingSessionDataStoreFactory);
//end::cachingsds[]
}
public void coreSessionHandler()
{
try
{
//tag:coresession[]
Server server = new Server();
org.eclipse.jetty.session.SessionHandler sessionHandler = new org.eclipse.jetty.session.SessionHandler();
sessionHandler.setSessionCookie("SIMPLE");
sessionHandler.setUsingCookies(true);
sessionHandler.setUsingURLs(false);
sessionHandler.setSessionPath("/");
server.setHandler(sessionHandler);
sessionHandler.setHandler(new Handler.Abstract()
{
@Override
public boolean handle(Request request, Response response, Callback callback)
{
Session session = request.getSession(false);
Content.Sink.write(response, true, "Session=" + session.getId(), callback);
return true;
}
});
//end::coresession[]
}
catch (Exception e)
{
e.printStackTrace();
}
}
public void defaultSessionCache()
{
//tag::defaultsessioncache[]
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.setSaveOnInactiveEviction(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
//SessionManager starts it will consult the bean to create a new DefaultSessionCache
server.addBean(cacheFactory);
//end::defaultsessioncache[]
}
public void defaultSessionIdManagerWithHouseKeeper()
@ -73,72 +160,90 @@ public class SessionDocs
}
}
public void servletContextWithSessionHandler()
public void fileSessionDataStore()
{
//tag:schsession[]
Server server = new Server();
//tag::filesessiondatastore[]
ServletContextHandler context = new ServletContextHandler("/foo", ServletContextHandler.SESSIONS);
SessionHandler sessions = context.getSessionHandler();
//make idle sessions valid for only 5mins
sessions.setMaxInactiveInterval(300);
//turn off use of cookies
sessions.setUsingCookies(false);
//create a context
WebAppContext app1 = new WebAppContext();
app1.setContextPath("/app1");
server.setHandler(context);
//end::schsession[]
//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 context to use the cache/store combination
app1.getSessionHandler().setSessionCache(cache);
//end::filesessiondatastore[]
}
public void webAppWithSessionHandler()
public void fileSessionDataStoreFactory()
{
//tag:wacsession[]
Server server = new Server();
WebAppContext context = new WebAppContext();
SessionHandler sessions = context.getSessionHandler();
//make idle sessions valid for only 5mins
sessions.setMaxInactiveInterval(300);
//turn off use of cookies
sessions.setUsingCookies(false);
server.setHandler(context);
//end::wacsession[]
}
public void defaultSessionCache()
{
//tag::defaultsessioncache[]
//tag::filesessiondatastorefactory[]
Server server = new Server();
//First lets configure a DefaultSessionCacheFactory
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.setSaveOnInactiveEviction(true);
//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
//SessionManager starts it will consult the bean to create a new DefaultSessionCache
server.addBean(cacheFactory);
//end::defaultsessioncache[]
//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
//SessionManager starts, it will consult the bean to create a new FileSessionDataStore
//for use by the DefaultSessionCache
server.addBean(storeFactory);
//end::filesessiondatastorefactory[]
}
public void nullSessionCache()
public void jdbcSessionDataStore()
{
//tag::nullsessioncache[]
Server server = new Server();
NullSessionCacheFactory cacheFactory = new NullSessionCacheFactory();
cacheFactory.setFlushOnResponseCommit(true);
cacheFactory.setRemoveUnloadableSessions(true);
cacheFactory.setSaveOnCreate(true);
//tag::dbaDatasource[]
DatabaseAdaptor datasourceAdaptor = new DatabaseAdaptor();
datasourceAdaptor.setDatasourceName("/jdbc/myDS");
//end::dbaDatasource[]
//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);
//end::nullsessioncache[]
//tag::dbaDriver[]
DatabaseAdaptor driverAdaptor = new DatabaseAdaptor();
driverAdaptor.setDriverInfo("com.mysql.jdbc.Driver", "jdbc:mysql://127.0.0.1:3306/sessions?user=sessionsadmin");
//end::dbaDriver[]
}
public void minimumDefaultSessionIdManager()
{
//tag::default[]
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.addBean(idMgr, true);
//end::default[]
}
public void mixedSessionCache()
@ -155,7 +260,7 @@ public class SessionDocs
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
//SessionManager starts it will consult the bean to create a new DefaultSessionCache
server.addBean(cacheFactory);
ContextHandlerCollection contexts = new ContextHandlerCollection();
@ -169,9 +274,7 @@ public class SessionDocs
//Add a webapp that uses an explicit NullSessionCache instead
WebAppContext app2 = new WebAppContext();
app2.setContextPath("/app2");
// TODO
// NullSessionCache nullSessionCache = new NullSessionCache(app2.getSessionHandler());
NullSessionCache nullSessionCache = null;
NullSessionCache nullSessionCache = new NullSessionCache(app2.getSessionHandler());
nullSessionCache.setFlushOnResponseCommit(true);
nullSessionCache.setRemoveUnloadableSessions(true);
nullSessionCache.setSaveOnCreate(true);
@ -182,114 +285,6 @@ public class SessionDocs
//end::mixedsessioncache[]
}
public void fileSessionDataStoreFactory()
{
//tag::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);
//end::filesessiondatastorefactory[]
}
public void fileSessionDataStore()
{
//tag::filesessiondatastore[]
//create a context
WebAppContext app1 = new WebAppContext();
app1.setContextPath("/app1");
//First, we create a DefaultSessionCache
// TODO
// DefaultSessionCache cache = new DefaultSessionCache(app1.getSessionHandler());
DefaultSessionCache cache = null;
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);
//end::filesessiondatastore[]
}
public void cachingSessionDataStore()
{
//tag::cachingsds[]
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);
//end::cachingsds[]
}
public void jdbcSessionDataStore()
{
//tag::dbaDatasource[]
DatabaseAdaptor datasourceAdaptor = new DatabaseAdaptor();
datasourceAdaptor.setDatasourceName("/jdbc/myDS");
//end::dbaDatasource[]
//tag::dbaDriver[]
DatabaseAdaptor driverAdaptor = new DatabaseAdaptor();
driverAdaptor.setDriverInfo("com.mysql.jdbc.Driver", "jdbc:mysql://127.0.0.1:3306/sessions?user=sessionsadmin");
//end::dbaDriver[]
}
public void mongoSessionDataStore()
{
//tag::mongosdfactory[]
@ -308,4 +303,163 @@ public class SessionDocs
mongoSessionDataStoreFactory.setPort(27017);
//end::mongosdfactory[]
}
public void infinispanEmbedded()
{
try
{
//tag::infinispanembed[]
/* Create a core SessionHandler
* Alternatively in a Servlet Environment do:
* WebAppContext webapp = new WebAppContext();
* SessionHandler sessionHandler = webapp.getSessionHandler();
*/
SessionHandler sessionHandler = new SessionHandler();
//Use an Infinispan local cache configured via an infinispan xml file
DefaultCacheManager defaultCacheManager = new DefaultCacheManager("path/to/infinispan.xml");
Cache<String, InfinispanSessionData> localCache = defaultCacheManager.getCache();
//Configure the Jetty session datastore with Infinispan
InfinispanSessionDataStore infinispanSessionDataStore = new InfinispanSessionDataStore();
infinispanSessionDataStore.setCache(localCache);
infinispanSessionDataStore.setSerialization(false); //local cache does not serialize session data
infinispanSessionDataStore.setInfinispanIdleTimeoutSec(0); //do not use infinispan auto delete of unused sessions
infinispanSessionDataStore.setQueryManager(new org.eclipse.jetty.session.infinispan.EmbeddedQueryManager(localCache)); //enable Jetty session scavenging
infinispanSessionDataStore.setGracePeriodSec(3600);
infinispanSessionDataStore.setSavePeriodSec(0);
//Configure a SessionHandler to use the local Infinispan cache as a store of SessionData
DefaultSessionCache sessionCache = new DefaultSessionCache(sessionHandler);
sessionCache.setSessionDataStore(infinispanSessionDataStore);
sessionHandler.setSessionCache(sessionCache);
//end::infinispanembed[]
}
catch (Exception e)
{
e.printStackTrace();
}
}
public void infinispanRemote()
{
try
{
//tag::infinispanremote[]
/* Create a core SessionHandler
* Alternatively in a Servlet Environment do:
* WebAppContext webapp = new WebAppContext();
* SessionHandler sessionHandler = webapp.getSessionHandler();
*/
SessionHandler sessionHandler = new SessionHandler();
//Configure Infinispan to provide a remote cache called "JettySessions"
Properties hotrodProperties = new Properties();
hotrodProperties.load(new FileInputStream("/path/to/hotrod-client.properties"));
org.infinispan.client.hotrod.configuration.ConfigurationBuilder configurationBuilder = new ConfigurationBuilder();
configurationBuilder.withProperties(hotrodProperties);
configurationBuilder.marshaller(new ProtoStreamMarshaller());
configurationBuilder.addContextInitializer(new org.eclipse.jetty.session.infinispan.InfinispanSerializationContextInitializer());
org.infinispan.client.hotrod.RemoteCacheManager remoteCacheManager = new RemoteCacheManager(configurationBuilder.build());
RemoteCache<String, InfinispanSessionData> remoteCache = remoteCacheManager.getCache("JettySessions");
//Configure the Jetty session datastore with Infinispan
InfinispanSessionDataStore infinispanSessionDataStore = new InfinispanSessionDataStore();
infinispanSessionDataStore.setCache(remoteCache);
infinispanSessionDataStore.setSerialization(true); //remote cache serializes session data
infinispanSessionDataStore.setInfinispanIdleTimeoutSec(0); //do not use infinispan auto delete of unused sessions
infinispanSessionDataStore.setQueryManager(new org.eclipse.jetty.session.infinispan.RemoteQueryManager(remoteCache)); //enable Jetty session scavenging
infinispanSessionDataStore.setGracePeriodSec(3600);
infinispanSessionDataStore.setSavePeriodSec(0);
//Configure a SessionHandler to use a remote Infinispan cache as a store of SessionData
DefaultSessionCache sessionCache = new DefaultSessionCache(sessionHandler);
sessionCache.setSessionDataStore(infinispanSessionDataStore);
sessionHandler.setSessionCache(sessionCache);
//end::infinispanremote[]
}
catch (Exception e)
{
e.printStackTrace();
}
}
public void gcloudSessionDataStore()
{
try
{
//tag::gcloudsessiondatastorefactory[]
Server server = new Server();
//Ensure there is a SessionCacheFactory
DefaultSessionCacheFactory cacheFactory = new DefaultSessionCacheFactory();
//Add the factory as a bean to the server, now whenever a
//SessionManager starts it will consult the bean to create a new DefaultSessionCache
server.addBean(cacheFactory);
//Configure the GCloudSessionDataStoreFactory
GCloudSessionDataStoreFactory storeFactory = new GCloudSessionDataStoreFactory();
storeFactory.setGracePeriodSec(3600);
storeFactory.setSavePeriodSec(0);
storeFactory.setBackoffMs(2000); //increase the time between retries of failed writes
storeFactory.setMaxRetries(10); //increase the number of retries of failed writes
//Add the factory as a bean on the server, now whenever a
//SessionManager starts, it will consult the bean to create a new GCloudSessionDataStore
//for use by the DefaultSessionCache
server.addBean(storeFactory);
//end::gcloudsessiondatastorefactory[]
}
catch (Exception e)
{
e.printStackTrace();
}
}
public void nullSessionCache()
{
//tag::nullsessioncache[]
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
//SessionManager starts it will consult the bean to create a new NullSessionCache
server.addBean(cacheFactory);
//end::nullsessioncache[]
}
public void servletContextWithSessionHandler()
{
//tag:schsession[]
Server server = new Server();
ServletContextHandler context = new ServletContextHandler("/foo", ServletContextHandler.SESSIONS);
//make idle sessions valid for only 5mins
context.getSessionHandler().setMaxInactiveInterval(300);
//turn off use of cookies
context.getSessionHandler().setUsingCookies(false);
server.setHandler(context);
//end::schsession[]
}
public void webAppWithSessionHandler()
{
//tag:wacsession[]
Server server = new Server();
WebAppContext context = new WebAppContext();
//make idle sessions valid for only 5mins
context.getSessionHandler().setMaxInactiveInterval(300);
//turn off use of cookies
context.getSessionHandler().setUsingCookies(false);
server.setHandler(context);
//end::wacsession[]
}
}

View File

@ -52,15 +52,15 @@
<!--
UNCOMMENT TO ACTIVATE
<context-param>
<param-name>org.eclipse.jetty.ee9.servlet.SessionDomain</param-name>
<param-name>org.eclipse.jetty.session.SessionDomain</param-name>
<param-value>127.0.0.1</param-value>
</context-param>
<context-param>
<param-name>org.eclipse.jetty.ee9.servlet.SessionPath</param-name>
<param-name>org.eclipse.jetty.session.SessionPath</param-name>
<param-value>/</param-value>
</context-param>
<context-param>
<param-name>org.eclipse.jetty.ee9.servlet.MaxAge</param-name>
<param-name>org.eclipse.jetty.session.MaxAge</param-name>
<param-value>-1</param-value>
</context-param>
-->

View File

@ -77,11 +77,11 @@ public abstract class AbstractSessionManager extends ContainerLifeCycle implemen
private SessionCache _sessionCache;
private Scheduler _scheduler;
private boolean _ownScheduler = false;
private String _sessionCookie = __DefaultSessionCookie;
private String _sessionIdPathParameterName = __DefaultSessionIdPathParameterName;
private String _sessionIdPathParameterNamePrefix = ";" + _sessionIdPathParameterName + "=";
private final Map<String, String> _sessionAttributes = new TreeMap<>(String.CASE_INSENSITIVE_ORDER);
private Map<String, String> _sessionAttributesSecure;
private String _sessionCookie;
private String _sessionIdPathParameterName;
private String _sessionIdPathParameterNamePrefix;
private final Map<String, String> _sessionCookieAttributes = new TreeMap<>(String.CASE_INSENSITIVE_ORDER);
private Map<String, String> _sessionCookieSecureAttributes;
private boolean _secureRequestOnly = true;
private int _refreshCookieAge;
private boolean _checkingRemoteSessionIdEncoding;
@ -243,6 +243,15 @@ public abstract class AbstractSessionManager extends ContainerLifeCycle implemen
_context = ContextHandler.getCurrentContext(server);
_loader = Thread.currentThread().getContextClassLoader();
//default the session cookie name
if (getSessionCookie() == null)
setSessionCookie(__DefaultSessionCookie);
//default the session id parameter name
if (getSessionIdPathParameterName() == null)
setSessionIdPathParameterName(__DefaultSessionIdPathParameterName);
// ensure a session path is set
String contextPath = _context == null ? "/" : _context.getContextPath();
if (getSessionPath() == null)
@ -327,14 +336,14 @@ public abstract class AbstractSessionManager extends ContainerLifeCycle implemen
@Override
public int getMaxCookieAge()
{
String mca = _sessionAttributes.get(HttpCookie.MAX_AGE_ATTRIBUTE);
String mca = _sessionCookieAttributes.get(HttpCookie.MAX_AGE_ATTRIBUTE);
return mca == null ? -1 : Integer.parseInt(mca);
}
@Override
public void setMaxCookieAge(int maxCookieAge)
{
_sessionAttributes.put(HttpCookie.MAX_AGE_ATTRIBUTE, Integer.toString(maxCookieAge));
_sessionCookieAttributes.put(HttpCookie.MAX_AGE_ATTRIBUTE, Integer.toString(maxCookieAge));
secureRequestOnlyAttributes();
}
@ -465,26 +474,26 @@ public abstract class AbstractSessionManager extends ContainerLifeCycle implemen
@Override
public String getSessionComment()
{
return _sessionAttributes.get(HttpCookie.COMMENT_ATTRIBUTE);
return _sessionCookieAttributes.get(HttpCookie.COMMENT_ATTRIBUTE);
}
@Override
public void setSessionComment(String sessionComment)
{
_sessionAttributes.put(HttpCookie.COMMENT_ATTRIBUTE, sessionComment);
_sessionCookieAttributes.put(HttpCookie.COMMENT_ATTRIBUTE, sessionComment);
secureRequestOnlyAttributes();
}
@Override
public HttpCookie.SameSite getSameSite()
{
return HttpCookie.SameSite.from(_sessionAttributes.get(HttpCookie.SAME_SITE_ATTRIBUTE));
return HttpCookie.SameSite.from(_sessionCookieAttributes.get(HttpCookie.SAME_SITE_ATTRIBUTE));
}
@Override
public void setSameSite(HttpCookie.SameSite sessionSameSite)
{
_sessionAttributes.put(HttpCookie.SAME_SITE_ATTRIBUTE, sessionSameSite.getAttributeValue());
_sessionCookieAttributes.put(HttpCookie.SAME_SITE_ATTRIBUTE, sessionSameSite.getAttributeValue());
secureRequestOnlyAttributes();
}
@ -511,34 +520,34 @@ public abstract class AbstractSessionManager extends ContainerLifeCycle implemen
@Override
public String getSessionDomain()
{
return _sessionAttributes.get(HttpCookie.DOMAIN_ATTRIBUTE);
return _sessionCookieAttributes.get(HttpCookie.DOMAIN_ATTRIBUTE);
}
@Override
public void setSessionDomain(String domain)
{
_sessionAttributes.put(HttpCookie.DOMAIN_ATTRIBUTE, domain);
_sessionCookieAttributes.put(HttpCookie.DOMAIN_ATTRIBUTE, domain);
secureRequestOnlyAttributes();
}
public void setSessionAttribute(String name, String value)
public void setSessionCookieAttribute(String name, String value)
{
_sessionAttributes.put(name, value);
_sessionCookieAttributes.put(name, value);
secureRequestOnlyAttributes();
}
public String getSessionAttribute(String name)
public String getSessionCookieAttribute(String name)
{
return _sessionAttributes.get(name);
return _sessionCookieAttributes.get(name);
}
/**
* @return all of the cookie config attributes EXCEPT for
* those that have explicit setter/getters
*/
public Map<String, String> getSessionAttributes()
public Map<String, String> getSessionCookieAttributes()
{
return Collections.unmodifiableMap(_sessionAttributes);
return Collections.unmodifiableMap(_sessionCookieAttributes);
}
@Override
@ -598,13 +607,13 @@ public abstract class AbstractSessionManager extends ContainerLifeCycle implemen
@Override
public String getSessionPath()
{
return _sessionAttributes.get(HttpCookie.PATH_ATTRIBUTE);
return _sessionCookieAttributes.get(HttpCookie.PATH_ATTRIBUTE);
}
@Override
public void setSessionPath(String sessionPath)
{
_sessionAttributes.put(HttpCookie.PATH_ATTRIBUTE, sessionPath);
_sessionCookieAttributes.put(HttpCookie.PATH_ATTRIBUTE, sessionPath);
secureRequestOnlyAttributes();
}
@ -830,7 +839,7 @@ public abstract class AbstractSessionManager extends ContainerLifeCycle implemen
@Override
public boolean isHttpOnly()
{
return Boolean.parseBoolean(_sessionAttributes.get(HttpCookie.HTTP_ONLY_ATTRIBUTE));
return Boolean.parseBoolean(_sessionCookieAttributes.get(HttpCookie.HTTP_ONLY_ATTRIBUTE));
}
/**
@ -842,7 +851,7 @@ public abstract class AbstractSessionManager extends ContainerLifeCycle implemen
@Override
public void setHttpOnly(boolean httpOnly)
{
_sessionAttributes.put(HttpCookie.HTTP_ONLY_ATTRIBUTE, Boolean.toString(httpOnly));
_sessionCookieAttributes.put(HttpCookie.HTTP_ONLY_ATTRIBUTE, Boolean.toString(httpOnly));
}
/**
@ -867,13 +876,13 @@ public abstract class AbstractSessionManager extends ContainerLifeCycle implemen
@Override
public boolean isSecureCookies()
{
return Boolean.parseBoolean(_sessionAttributes.get(HttpCookie.SECURE_ATTRIBUTE));
return Boolean.parseBoolean(_sessionCookieAttributes.get(HttpCookie.SECURE_ATTRIBUTE));
}
@Override
public void setSecureCookies(boolean secure)
{
_sessionAttributes.put(HttpCookie.SECURE_ATTRIBUTE, Boolean.toString(secure));
_sessionCookieAttributes.put(HttpCookie.SECURE_ATTRIBUTE, Boolean.toString(secure));
secureRequestOnlyAttributes();
}
@ -901,16 +910,16 @@ public abstract class AbstractSessionManager extends ContainerLifeCycle implemen
private void secureRequestOnlyAttributes()
{
if (isSecureRequestOnly() && !Boolean.parseBoolean(_sessionAttributes.get(HttpCookie.SECURE_ATTRIBUTE)))
if (isSecureRequestOnly() && !Boolean.parseBoolean(_sessionCookieAttributes.get(HttpCookie.SECURE_ATTRIBUTE)))
{
Map<String, String> attributes = new TreeMap<>(String.CASE_INSENSITIVE_ORDER);
attributes.putAll(_sessionAttributes);
attributes.putAll(_sessionCookieAttributes);
attributes.put(HttpCookie.SECURE_ATTRIBUTE, Boolean.TRUE.toString());
_sessionAttributesSecure = attributes;
_sessionCookieSecureAttributes = attributes;
}
else
{
_sessionAttributesSecure = _sessionAttributes;
_sessionCookieSecureAttributes = _sessionCookieAttributes;
}
}
@ -1377,12 +1386,12 @@ public abstract class AbstractSessionManager extends ContainerLifeCycle implemen
{
String name = getSessionCookie();
if (name == null)
name = _sessionAttributes.get("name");
name = _sessionCookieAttributes.get("name");
if (name == null)
name = __DefaultSessionCookie;
if (isSecureRequestOnly() && requestIsSecure && _sessionAttributesSecure != null && _sessionAttributes != _sessionAttributesSecure)
return session.generateSetCookie(name, _sessionAttributesSecure);
return session.generateSetCookie(name, _sessionAttributes);
if (isSecureRequestOnly() && requestIsSecure && _sessionCookieSecureAttributes != null && _sessionCookieAttributes != _sessionCookieSecureAttributes)
return session.generateSetCookie(name, _sessionCookieSecureAttributes);
return session.generateSetCookie(name, _sessionCookieAttributes);
}
return null;

View File

@ -20,6 +20,46 @@ import org.eclipse.jetty.util.annotation.ManagedAttribute;
// TODO the managed attribute descriptions need review
public interface SessionConfig
{
/**
* Session cookie name.
* Defaults to <code>JSESSIONID</code>, but can be set with the
* <code>org.eclipse.jetty.session.SessionCookie</code> context init parameter.
*/
String __SessionCookieProperty = "org.eclipse.jetty.session.SessionCookie";
String __DefaultSessionCookie = "JSESSIONID";
/**
* Session id path parameter name.
* Defaults to <code>jsessionid</code>, but can be set with the
* <code>org.eclipse.jetty.session.SessionIdPathParameterName</code> context init parameter.
* If context init param is "none", or setSessionIdPathParameterName is called with null or "none",
* no URL rewriting will be done.
*/
String __SessionIdPathParameterNameProperty = "org.eclipse.jetty.session.SessionIdPathParameterName";
String __DefaultSessionIdPathParameterName = "jsessionid";
String __CheckRemoteSessionEncodingProperty = "org.eclipse.jetty.session.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.
*/
String __SessionDomainProperty = "org.eclipse.jetty.session.SessionDomain";
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.
*/
String __SessionPathProperty = "org.eclipse.jetty.session.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.
*/
String __MaxAgeProperty = "org.eclipse.jetty.session.MaxAge";
@ManagedAttribute("if greater the zero, the time in seconds a session cookie will last for")
int getMaxCookieAge();

View File

@ -31,49 +31,6 @@ public interface SessionManager extends LifeCycle, SessionConfig
// - the configuration interface used to configure the manager
// - the contract between the request and the manager
// - maybe the contract with the ID managers?
/**
* Session cookie name.
* Defaults to <code>JSESSIONID</code>, but can be set with the
* <code>org.eclipse.jetty.servlet.SessionCookie</code> context init parameter.
*/
String __SessionCookieProperty = "org.eclipse.jetty.servlet.SessionCookie";
String __DefaultSessionCookie = "JSESSIONID";
/**
* Session id path parameter name.
* Defaults to <code>jsessionid</code>, but can be set with the
* <code>org.eclipse.jetty.servlet.SessionIdPathParameterName</code> context init parameter.
* If context init param is "none", or setSessionIdPathParameterName is called with null or "none",
* no URL rewriting will be done.
*/
String __SessionIdPathParameterNameProperty = "org.eclipse.jetty.servlet.SessionIdPathParameterName";
String __DefaultSessionIdPathParameterName = "jsessionid";
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.
*/
String __SessionDomainProperty = "org.eclipse.jetty.servlet.SessionDomain";
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.
*/
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.
*/
String __MaxAgeProperty = "org.eclipse.jetty.servlet.MaxAge";
ManagedSession getManagedSession(String id) throws Exception;

View File

@ -46,8 +46,8 @@ public class AbstractSessionManagerTest
//check cookie with all default cookie config settings
HttpCookie cookie = sessionManager.getSessionCookie(session, false);
assertNotNull(cookie);
assertEquals(SessionManager.__DefaultSessionCookie, cookie.getName());
assertEquals(SessionManager.__DefaultSessionDomain, cookie.getDomain());
assertEquals(SessionConfig.__DefaultSessionCookie, cookie.getName());
assertEquals(SessionConfig.__DefaultSessionDomain, cookie.getDomain());
assertEquals("/test", cookie.getPath());
assertFalse(cookie.isSecure());
assertFalse(cookie.isHttpOnly());
@ -58,16 +58,16 @@ public class AbstractSessionManagerTest
sessionManager.setSecureCookies(true);
cookie = sessionManager.getSessionCookie(session, true);
assertNotNull(cookie);
assertEquals(SessionManager.__DefaultSessionCookie, cookie.getName());
assertEquals(SessionManager.__DefaultSessionDomain, cookie.getDomain());
assertEquals(SessionConfig.__DefaultSessionCookie, cookie.getName());
assertEquals(SessionConfig.__DefaultSessionDomain, cookie.getDomain());
assertEquals("/test", cookie.getPath());
assertTrue(cookie.isHttpOnly());
assertTrue(cookie.isSecure());
//check cookie when cookie config is set
sessionManager.getCookieConfig().put(SessionManager.__SessionCookieProperty, "MYSESSIONID");
sessionManager.getCookieConfig().put(SessionManager.__SessionDomainProperty, "foo.bar");
sessionManager.getCookieConfig().put(SessionManager.__SessionPathProperty, "/special");
sessionManager.getCookieConfig().put(SessionConfig.__SessionCookieProperty, "MYSESSIONID");
sessionManager.getCookieConfig().put(SessionConfig.__SessionDomainProperty, "foo.bar");
sessionManager.getCookieConfig().put(SessionConfig.__SessionPathProperty, "/special");
sessionManager.configureCookies();
cookie = sessionManager.getSessionCookie(session, false);
assertNotNull(cookie);

View File

@ -183,7 +183,7 @@ public class TestableSessionManager extends AbstractSessionManager
if (StringUtil.isNotBlank(path))
setSessionPath(path);
String checkEncoding = _cookieConfig.get(__CheckRemoteSessionEncoding);
String checkEncoding = _cookieConfig.get(__CheckRemoteSessionEncodingProperty);
if (StringUtil.isNotBlank(checkEncoding))
setCheckingRemoteSessionIdEncoding(Boolean.parseBoolean(checkEncoding));
}

View File

@ -44,15 +44,15 @@
<!--
UNCOMMENT TO ACTIVATE
<context-param>
<param-name>org.eclipse.jetty.ee10.servlet.SessionDomain</param-name>
<param-name>org.eclipse.jetty.session.SessionDomain</param-name>
<param-value>127.0.0.1</param-value>
</context-param>
<context-param>
<param-name>org.eclipse.jetty.ee10.servlet.SessionPath</param-name>
<param-name>org.eclipse.jetty.session.SessionPath</param-name>
<param-value>/</param-value>
</context-param>
<context-param>
<param-name>org.eclipse.jetty.ee10.servlet.MaxAge</param-name>
<param-name>org.eclipse.jetty.session.MaxAge</param-name>
<param-value>-1</param-value>
</context-param>
-->

View File

@ -52,15 +52,15 @@
<!--
UNCOMMENT TO ACTIVATE
<context-param>
<param-name>org.eclipse.jetty.ee10.servlet.SessionDomain</param-name>
<param-name>org.eclipse.jetty.session.SessionDomain</param-name>
<param-value>127.0.0.1</param-value>
</context-param>
<context-param>
<param-name>org.eclipse.jetty.ee10.servlet.SessionPath</param-name>
<param-name>org.eclipse.jetty.session.SessionPath</param-name>
<param-value>/</param-value>
</context-param>
<context-param>
<param-name>org.eclipse.jetty.ee10.servlet.MaxAge</param-name>
<param-name>org.eclipse.jetty.session.MaxAge</param-name>
<param-value>-1</param-value>
</context-param>
-->

View File

@ -46,6 +46,7 @@ import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.server.Session;
import org.eclipse.jetty.session.AbstractSessionManager;
import org.eclipse.jetty.session.ManagedSession;
import org.eclipse.jetty.session.SessionConfig;
import org.eclipse.jetty.util.Callback;
public class SessionHandler extends AbstractSessionManager implements Handler.Singleton
@ -137,7 +138,7 @@ public class SessionHandler extends AbstractSessionManager implements Handler.Si
case "httponly" -> setHttpOnly(Boolean.parseBoolean(value));
case "secure" -> setSecure(Boolean.parseBoolean(value));
case "path" -> setPath(value);
default -> setSessionAttribute(name, value);
default -> setSessionCookieAttribute(name, value);
}
}
@ -154,7 +155,7 @@ public class SessionHandler extends AbstractSessionManager implements Handler.Si
case "httponly" -> String.valueOf(isHttpOnly());
case "secure" -> String.valueOf(isSecure());
case "path" -> getPath();
default -> getSessionAttribute(name);
default -> getSessionCookieAttribute(name);
};
}
@ -174,7 +175,7 @@ public class SessionHandler extends AbstractSessionManager implements Handler.Si
specials.put("httponly", getAttribute("httponly"));
specials.put("secure", getAttribute("secure"));
specials.put("path", getAttribute("path"));
specials.putAll(getSessionAttributes());
specials.putAll(getSessionCookieAttributes());
return Collections.unmodifiableMap(specials);
}
@ -234,10 +235,6 @@ public class SessionHandler extends AbstractSessionManager implements Handler.Si
public void setName(String name)
{
checkState();
if ("".equals(name))
throw new IllegalArgumentException("Blank cookie name");
if (name != null)
Syntax.requireValidRFC2616Token(name, "Bad Session cookie name");
SessionHandler.this.setSessionCookie(name);
}
@ -450,6 +447,39 @@ public class SessionHandler extends AbstractSessionManager implements Handler.Si
*/
protected void configureCookies()
{
ServletContextHandler contextHandler = ServletContextHandler.getCurrentServletContextHandler();
if (contextHandler == null)
return;
//configure the name of the session cookie set by an init param
String tmp = contextHandler.getInitParameter(SessionConfig.__SessionCookieProperty);
if (tmp != null)
setSessionCookie(tmp);
//configure the name of the session id path param set by an init param
tmp = contextHandler.getInitParameter(SessionConfig.__SessionIdPathParameterNameProperty);
if (tmp != null)
setSessionIdPathParameterName(tmp);
//configure checkRemoteSessionEncoding set by an init param
tmp = contextHandler.getInitParameter(SessionConfig.__CheckRemoteSessionEncodingProperty);
if (tmp != null)
setCheckingRemoteSessionIdEncoding(Boolean.parseBoolean(tmp));
//configure the domain of the session cookie set by an init param
tmp = contextHandler.getInitParameter(SessionConfig.__SessionDomainProperty);
if (tmp != null)
setSessionDomain(tmp);
//configure the path of the session cookie set by an init param
tmp = contextHandler.getInitParameter(SessionConfig.__SessionPathProperty);
if (tmp != null)
setSessionPath(tmp);
//configure the max age of the session cookie set by an init param
tmp = contextHandler.getInitParameter(SessionConfig.__MaxAgeProperty);
if (tmp != null)
setMaxCookieAge(Integer.parseInt(tmp.trim()));
}
public Session.API newSessionAPIWrapper(ManagedSession session)
@ -608,7 +638,7 @@ public class SessionHandler extends AbstractSessionManager implements Handler.Si
@Override
public HttpCookie.SameSite getSameSite()
{
String sameSite = getSessionAttribute("SameSite");
String sameSite = getSessionCookieAttribute("SameSite");
if (sameSite == null)
return null;
return SameSite.valueOf(sameSite.toUpperCase(Locale.ENGLISH));
@ -623,7 +653,7 @@ public class SessionHandler extends AbstractSessionManager implements Handler.Si
@Override
public void setSameSite(HttpCookie.SameSite sameSite)
{
setSessionAttribute("SameSite", sameSite.getAttributeValue());
setSessionCookieAttribute("SameSite", sameSite.getAttributeValue());
}
public void setSessionTrackingModes(Set<SessionTrackingMode> sessionTrackingModes)

View File

@ -54,6 +54,7 @@ import org.eclipse.jetty.session.ManagedSession;
import org.eclipse.jetty.session.NullSessionDataStore;
import org.eclipse.jetty.session.NullSessionDataStoreFactory;
import org.eclipse.jetty.session.SessionCache;
import org.eclipse.jetty.session.SessionConfig;
import org.eclipse.jetty.session.SessionData;
import org.eclipse.jetty.session.SessionDataStoreFactory;
import org.eclipse.jetty.toolchain.test.IO;
@ -492,7 +493,7 @@ public class SessionHandlerTest
assertThat(response.getContentAsString(), containsString("valid=false"));
//test with id for non-existent session
response = client.GET(url + ";" + SessionHandler.__DefaultSessionIdPathParameterName + "=" + "123456789");
response = client.GET(url + ";" + SessionConfig.__DefaultSessionIdPathParameterName + "=" + "123456789");
assertEquals(HttpServletResponse.SC_OK, response.getStatus());
String content = response.getContentAsString();
assertThat(content, containsString("requestedId=123456789"));
@ -507,7 +508,7 @@ public class SessionHandlerTest
sessionId = sessionId.trim();
//Check the requestedSessionId is valid
response = client.GET(url + ";" + SessionHandler.__DefaultSessionIdPathParameterName + "=" + sessionId);
response = client.GET(url + ";" + SessionConfig.__DefaultSessionIdPathParameterName + "=" + sessionId);
assertEquals(HttpServletResponse.SC_OK, response.getStatus());
content = response.getContentAsString();
assertThat(content, containsString("valid=true"));
@ -680,6 +681,50 @@ public class SessionHandlerTest
assertThat(cookieStr, containsString("; SameSite=Strict; ham=cheese"));
}
@Test
public void testSessionCookieConfigByInitParam() throws Exception
{
Server server = new Server();
ServletContextHandler contextHandler = new ServletContextHandler();
contextHandler.setContextPath("/test");
SessionHandler sessionHandler = new SessionHandler();
contextHandler.setHandler(sessionHandler);
server.setHandler(contextHandler);
server.start();
assertEquals(SessionConfig.__DefaultSessionCookie, sessionHandler.getSessionCookie());
assertEquals(null, sessionHandler.getSessionDomain());
assertEquals(SessionConfig.__DefaultSessionIdPathParameterName, sessionHandler.getSessionIdPathParameterName());
assertEquals("/test", sessionHandler.getSessionPath());
assertEquals(-1, sessionHandler.getMaxCookieAge());
assertEquals(false, sessionHandler.isCheckingRemoteSessionIdEncoding());
server.stop();
//make a new ContextHandler and SessionHandler that can be configured
contextHandler = new ServletContextHandler();
contextHandler.setContextPath("/test");
sessionHandler = new SessionHandler();
contextHandler.setHandler(sessionHandler);
server.setHandler(contextHandler);
contextHandler.setInitParameter(SessionConfig.__SessionCookieProperty, "TEST_SESSION_COOKIE");
contextHandler.setInitParameter(SessionConfig.__SessionDomainProperty, "TEST_DOMAIN");
contextHandler.setInitParameter(SessionConfig.__SessionIdPathParameterNameProperty, "TEST_SESSION_ID_PATH_PARAM");
contextHandler.setInitParameter(SessionConfig.__SessionPathProperty, "/mypath");
contextHandler.setInitParameter(SessionConfig.__MaxAgeProperty, "1000");
contextHandler.setInitParameter(SessionConfig.__CheckRemoteSessionEncodingProperty, "true");
server.start();
assertEquals("TEST_SESSION_COOKIE", sessionHandler.getSessionCookie());
assertEquals("TEST_DOMAIN", sessionHandler.getSessionDomain());
assertEquals("TEST_SESSION_ID_PATH_PARAM", sessionHandler.getSessionIdPathParameterName());
assertEquals("/mypath", sessionHandler.getSessionPath());
assertEquals(1000, sessionHandler.getMaxCookieAge());
assertEquals(true, sessionHandler.isCheckingRemoteSessionIdEncoding());
}
@Test
public void testSecureSessionCookie() throws Exception
{

View File

@ -44,15 +44,15 @@
<!--
UNCOMMENT TO ACTIVATE
<context-param>
<param-name>org.eclipse.jetty.ee10.servlet.SessionDomain</param-name>
<param-name>org.eclipse.jetty.session.SessionDomain</param-name>
<param-value>127.0.0.1</param-value>
</context-param>
<context-param>
<param-name>org.eclipse.jetty.ee10.servlet.SessionPath</param-name>
<param-name>org.eclipse.jetty.session.SessionPath</param-name>
<param-value>/</param-value>
</context-param>
<context-param>
<param-name>org.eclipse.jetty.ee10.servlet.MaxAge</param-name>
<param-name>org.eclipse.jetty.session.MaxAge</param-name>
<param-value>-1</param-value>
</context-param>
-->

View File

@ -36,6 +36,7 @@ import org.eclipse.jetty.session.ManagedSession;
import org.eclipse.jetty.session.NullSessionCacheFactory;
import org.eclipse.jetty.session.SessionCache;
import org.eclipse.jetty.session.SessionCacheFactory;
import org.eclipse.jetty.session.SessionConfig;
import org.eclipse.jetty.session.SessionData;
import org.eclipse.jetty.session.SessionDataStoreFactory;
import org.eclipse.jetty.session.SessionManager;
@ -175,7 +176,7 @@ public class SessionRenewTest
//make a request to change the sessionid
Request request = client.newRequest("http://localhost:" + port + contextPathA + servletMapping + "?action=renew");
request.cookie(HttpCookie.from(SessionManager.__DefaultSessionCookie, "1234"));
request.cookie(HttpCookie.from(SessionConfig.__DefaultSessionCookie, "1234"));
ContentResponse renewResponse = request.send();
assertEquals(HttpServletResponse.SC_OK, renewResponse.getStatus());
String newSessionCookie = renewResponse.getHeaders().get("Set-Cookie");

View File

@ -44,15 +44,15 @@
<!--
UNCOMMENT TO ACTIVATE
<context-param>
<param-name>org.eclipse.jetty.servlet.SessionDomain</param-name>
<param-name>org.eclipse.jetty.session.SessionDomain</param-name>
<param-value>127.0.0.1</param-value>
</context-param>
<context-param>
<param-name>org.eclipse.jetty.servlet.SessionPath</param-name>
<param-name>org.eclipse.jetty.session.SessionPath</param-name>
<param-value>/</param-value>
</context-param>
<context-param>
<param-name>org.eclipse.jetty.servlet.MaxAge</param-name>
<param-name>org.eclipse.jetty.session.MaxAge</param-name>
<param-value>-1</param-value>
</context-param>
-->

View File

@ -23,7 +23,6 @@ import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.toolchain.test.MavenTestingUtils;
import org.eclipse.jetty.toolchain.test.jupiter.WorkDir;
import org.eclipse.jetty.toolchain.test.jupiter.WorkDirExtension;
import org.eclipse.jetty.util.resource.FileSystemPool;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
@ -31,7 +30,6 @@ import org.junit.jupiter.api.extension.ExtendWith;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.containsInAnyOrder;
import static org.hamcrest.Matchers.empty;
import static org.hamcrest.Matchers.equalToIgnoringCase;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
@ -119,7 +117,7 @@ public class StandardDescriptorProcessorTest
equalToIgnoringCase("SameSite"))));
//test the attributes on SessionHandler do NOT contain the name
Map<String, String> sessionAttributes = wac.getSessionHandler().getSessionAttributes();
Map<String, String> sessionAttributes = wac.getSessionHandler().getSessionCookieAttributes();
sessionAttributes.keySet().forEach(System.err::println);
assertThat(sessionAttributes.keySet(),
containsInAnyOrder(Arrays.asList(

View File

@ -44,15 +44,15 @@
<!--
UNCOMMENT TO ACTIVATE
<context-param>
<param-name>org.eclipse.jetty.ee8.servlet.SessionDomain</param-name>
<param-name>org.eclipse.jetty.session.SessionDomain</param-name>
<param-value>127.0.0.1</param-value>
</context-param>
<context-param>
<param-name>org.eclipse.jetty.ee8.servlet.SessionPath</param-name>
<param-name>org.eclipse.jetty.session.SessionPath</param-name>
<param-value>/</param-value>
</context-param>
<context-param>
<param-name>org.eclipse.jetty.ee8.servlet.MaxAge</param-name>
<param-name>org.eclipse.jetty.session.MaxAge</param-name>
<param-value>-1</param-value>
</context-param>
-->

View File

@ -52,15 +52,15 @@
<!--
UNCOMMENT TO ACTIVATE
<context-param>
<param-name>org.eclipse.jetty.ee9.servlet.SessionDomain</param-name>
<param-name>org.eclipse.jetty.session.SessionDomain</param-name>
<param-value>127.0.0.1</param-value>
</context-param>
<context-param>
<param-name>org.eclipse.jetty.ee9.servlet.SessionPath</param-name>
<param-name>org.eclipse.jetty.session.SessionPath</param-name>
<param-value>/</param-value>
</context-param>
<context-param>
<param-name>org.eclipse.jetty.ee9.servlet.MaxAge</param-name>
<param-name>org.eclipse.jetty.session.MaxAge</param-name>
<param-value>-1</param-value>
</context-param>
-->

View File

@ -118,11 +118,48 @@ public class SessionHandler extends ScopedHandler implements SessionConfig.Mutab
return _sessionManager.getSessionIdManager();
}
protected void configureCookies()
{
if (_contextHandler == null)
return;
//configure the name of the session cookie set by an init param
String tmp = _contextHandler.getInitParameter(SessionConfig.__SessionCookieProperty);
if (tmp != null)
setSessionCookie(tmp);
//configure the name of the session id path param set by an init param
tmp = _contextHandler.getInitParameter(SessionConfig.__SessionIdPathParameterNameProperty);
if (tmp != null)
setSessionIdPathParameterName(tmp);
//configure checkRemoteSessionEncoding set by an init param
tmp = _contextHandler.getInitParameter(SessionConfig.__CheckRemoteSessionEncodingProperty);
if (tmp != null)
setCheckingRemoteSessionIdEncoding(Boolean.parseBoolean(tmp));
//configure the domain of the session cookie set by an init param
tmp = _contextHandler.getInitParameter(SessionConfig.__SessionDomainProperty);
if (tmp != null)
setSessionDomain(tmp);
//configure the path of the session cookie set by an init param
tmp = _contextHandler.getInitParameter(SessionConfig.__SessionPathProperty);
if (tmp != null)
setSessionPath(tmp);
//configure the max age of the session cookie set by an init param
tmp = _contextHandler.getInitParameter(SessionConfig.__MaxAgeProperty);
if (tmp != null)
setMaxCookieAge(Integer.parseInt(tmp.trim()));
}
@Override
protected void doStart() throws Exception
{
_contextHandler = ContextHandler.getCurrentContext().getContextHandler();
super.doStart();
configureCookies();
}
/**

View File

@ -39,6 +39,7 @@ import org.eclipse.jetty.session.AbstractSessionCache;
import org.eclipse.jetty.session.DefaultSessionIdManager;
import org.eclipse.jetty.session.ManagedSession;
import org.eclipse.jetty.session.NullSessionDataStore;
import org.eclipse.jetty.session.SessionConfig;
import org.eclipse.jetty.session.SessionData;
import org.eclipse.jetty.session.SessionManager;
import org.junit.jupiter.api.AfterEach;
@ -205,6 +206,50 @@ public class SessionHandlerTest
assertThat(cookieStr, containsString("; SameSite=Strict"));
}
@Test
public void testSessionCookieConfigByInitParam() throws Exception
{
Server server = new Server();
ContextHandler contextHandler = new ContextHandler();
contextHandler.setContextPath("/test");
SessionHandler sessionHandler = new SessionHandler();
contextHandler.setHandler(sessionHandler);
server.setHandler(contextHandler);
server.start();
assertEquals(SessionConfig.__DefaultSessionCookie, sessionHandler.getSessionCookie());
assertEquals(null, sessionHandler.getSessionDomain());
assertEquals(SessionConfig.__DefaultSessionIdPathParameterName, sessionHandler.getSessionIdPathParameterName());
assertEquals("/test", sessionHandler.getSessionPath());
assertEquals(-1, sessionHandler.getMaxCookieAge());
assertEquals(false, sessionHandler.isCheckingRemoteSessionIdEncoding());
server.stop();
//make a new ContextHandler and SessionHandler that can be configured
contextHandler = new ContextHandler();
contextHandler.setContextPath("/test");
sessionHandler = new SessionHandler();
contextHandler.setHandler(sessionHandler);
server.setHandler(contextHandler);
contextHandler.setInitParameter(SessionConfig.__SessionCookieProperty, "TEST_SESSION_COOKIE");
contextHandler.setInitParameter(SessionConfig.__SessionDomainProperty, "TEST_DOMAIN");
contextHandler.setInitParameter(SessionConfig.__SessionIdPathParameterNameProperty, "TEST_SESSION_ID_PATH_PARAM");
contextHandler.setInitParameter(SessionConfig.__SessionPathProperty, "/mypath");
contextHandler.setInitParameter(SessionConfig.__MaxAgeProperty, "1000");
contextHandler.setInitParameter(SessionConfig.__CheckRemoteSessionEncodingProperty, "true");
server.start();
assertEquals("TEST_SESSION_COOKIE", sessionHandler.getSessionCookie());
assertEquals("TEST_DOMAIN", sessionHandler.getSessionDomain());
assertEquals("TEST_SESSION_ID_PATH_PARAM", sessionHandler.getSessionIdPathParameterName());
assertEquals("/mypath", sessionHandler.getSessionPath());
assertEquals(1000, sessionHandler.getMaxCookieAge());
assertEquals(true, sessionHandler.isCheckingRemoteSessionIdEncoding());
}
@Test
public void testSessionTrackingMode()
{

View File

@ -52,15 +52,15 @@
<!--
UNCOMMENT TO ACTIVATE
<context-param>
<param-name>org.eclipse.jetty.ee9.servlet.SessionDomain</param-name>
<param-name>org.eclipse.jetty.session.SessionDomain</param-name>
<param-value>127.0.0.1</param-value>
</context-param>
<context-param>
<param-name>org.eclipse.jetty.ee9.servlet.SessionPath</param-name>
<param-name>org.eclipse.jetty.session.SessionPath</param-name>
<param-value>/</param-value>
</context-param>
<context-param>
<param-name>org.eclipse.jetty.ee9.servlet.MaxAge</param-name>
<param-name>org.eclipse.jetty.session.MaxAge</param-name>
<param-value>-1</param-value>
</context-param>
-->

View File

@ -52,15 +52,15 @@
<!--
UNCOMMENT TO ACTIVATE
<context-param>
<param-name>org.eclipse.jetty.ee9.servlet.SessionDomain</param-name>
<param-name>org.eclipse.jetty.session.SessionDomain</param-name>
<param-value>127.0.0.1</param-value>
</context-param>
<context-param>
<param-name>org.eclipse.jetty.ee9.servlet.SessionPath</param-name>
<param-name>org.eclipse.jetty.session.SessionPath</param-name>
<param-value>/</param-value>
</context-param>
<context-param>
<param-name>org.eclipse.jetty.ee9.servlet.MaxAge</param-name>
<param-name>org.eclipse.jetty.session.MaxAge</param-name>
<param-value>-1</param-value>
</context-param>
-->

View File

@ -36,6 +36,7 @@ import org.eclipse.jetty.session.ManagedSession;
import org.eclipse.jetty.session.NullSessionCacheFactory;
import org.eclipse.jetty.session.SessionCache;
import org.eclipse.jetty.session.SessionCacheFactory;
import org.eclipse.jetty.session.SessionConfig;
import org.eclipse.jetty.session.SessionData;
import org.eclipse.jetty.session.SessionDataStoreFactory;
import org.eclipse.jetty.session.SessionManager;
@ -176,7 +177,7 @@ public class SessionRenewTest
//make a request to change the sessionid
Request request = client.newRequest("http://localhost:" + port + contextPathA + servletMapping + "?action=renew");
request.cookie(HttpCookie.from(SessionManager.__DefaultSessionCookie, "1234"));
request.cookie(HttpCookie.from(SessionConfig.__DefaultSessionCookie, "1234"));
ContentResponse renewResponse = request.send();
assertEquals(HttpServletResponse.SC_OK, renewResponse.getStatus());
String newSessionCookie = renewResponse.getHeaders().get("Set-Cookie");

View File

@ -44,15 +44,15 @@
<!--
UNCOMMENT TO ACTIVATE
<context-param>
<param-name>org.eclipse.jetty.ee9.servlet.SessionDomain</param-name>
<param-name>org.eclipse.jetty.session.SessionDomain</param-name>
<param-value>127.0.0.1</param-value>
</context-param>
<context-param>
<param-name>org.eclipse.jetty.ee9.servlet.SessionPath</param-name>
<param-name>org.eclipse.jetty.session.SessionPath</param-name>
<param-value>/</param-value>
</context-param>
<context-param>
<param-name>org.eclipse.jetty.ee9.servlet.MaxAge</param-name>
<param-name>org.eclipse.jetty.session.MaxAge</param-name>
<param-value>-1</param-value>
</context-param>
-->