{"id":19502274,"url":"https://github.com/amadeusitgroup/httpsessionreplacer","last_synced_at":"2025-04-25T23:31:29.140Z","repository":{"id":56289378,"uuid":"74458764","full_name":"AmadeusITGroup/HttpSessionReplacer","owner":"AmadeusITGroup","description":"Store JEE Servlet HttpSessions in Redis","archived":false,"fork":false,"pushed_at":"2023-09-26T14:59:27.000Z","size":548,"stargazers_count":49,"open_issues_count":8,"forks_count":33,"subscribers_count":11,"default_branch":"master","last_synced_at":"2025-04-25T20:52:26.864Z","etag":null,"topics":["jee","redis","servlet","session"],"latest_commit_sha":null,"homepage":"","language":"Java","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/AmadeusITGroup.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2016-11-22T09:56:07.000Z","updated_at":"2025-03-27T14:08:30.000Z","dependencies_parsed_at":"2022-08-15T16:01:10.442Z","dependency_job_id":null,"html_url":"https://github.com/AmadeusITGroup/HttpSessionReplacer","commit_stats":null,"previous_names":[],"tags_count":20,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/AmadeusITGroup%2FHttpSessionReplacer","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/AmadeusITGroup%2FHttpSessionReplacer/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/AmadeusITGroup%2FHttpSessionReplacer/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/AmadeusITGroup%2FHttpSessionReplacer/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/AmadeusITGroup","download_url":"https://codeload.github.com/AmadeusITGroup/HttpSessionReplacer/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":250912660,"owners_count":21506865,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["jee","redis","servlet","session"],"created_at":"2024-11-10T22:15:48.537Z","updated_at":"2025-04-25T23:31:28.479Z","avatar_url":"https://github.com/AmadeusITGroup.png","language":"Java","funding_links":[],"categories":[],"sub_categories":[],"readme":"# HTTP Session Replacement\n\n[![Maven Central](https://img.shields.io/maven-central/v/com.amadeus/session.svg?style=flat-square)](https://search.maven.org/#search%7Cga%7C1%7Ccom.amadeus.session)\n[![Javadocs](http://www.javadoc.io/badge/com.amadeus/session-replacement.svg?style=flat-square)](http://www.javadoc.io/doc/com.amadeus/session-replacement)\n[![Travis](https://img.shields.io/travis/AmadeusITGroup/HttpSessionReplacer.svg?style=flat-square)](http://travis-ci.org/AmadeusITGroup/HttpSessionReplacer)\n[![license](https://img.shields.io/github/license/AmadeusITGroup/HttpSessionReplacer.svg?style=flat-square)](LICENSE)\n[![Dependency Status](https://www.versioneye.com/user/projects/583d2f19d2fd57003fdfbe76/badge.svg?style=flat-square)](https://www.versioneye.com/user/projects/583d2f19d2fd57003fdfbe76/)\n[![SonarQube Coverage](https://img.shields.io/sonar/http/sonarqube.com/com.amadeus:session/coverage.svg?style=flat-square)](https://sonarqube.com/dashboard?id=com.amadeus%3Asession)\n[![SonarQube Tech Debt](https://img.shields.io/sonar/http/sonarqube.com/com.amadeus:session/tech_debt.svg?style=flat-square)](https://sonarqube.com/dashboard?id=com.amadeus%3Asession)\n\nThis project provides session management including, possibly, distributed\nsession repository for JEE and other java containers. Default implementation\ncomes with in-memory and Redis based implementation.\n\nThe project is inspired by Spring Session project and reuses some of redis logic from it.\nIts objective is to avoid any dependency on Spring libraries, and, so, make it usable in applications that\ndon't use Spring, or that use an older version.\nThe implementation, however, uses the Jedis library directly.\nThis makes the algorithm easier to port to other languages.\n\nThe project aims to make session management transparent for existing webapps (zero code\nchange) and as compatible as possible with wide variety of the JEE containers, all the while\noffering full support for session API including different session listeners.\n\nRedis support includes both single instance, sentinel based and cluster modes.\nTwo session expiration strategies are available when using Redis. One is based\non Redis notifications, and antoher on sorted sets (ZRANGE).\n\nUseful links:\n\n* For details about installing the library see [docs/Usage.md](docs/Usage.md)\n* For information about building this project see [docs/BUILD.md](docs/BUILD.md).\n* For information about using session encryption see [docs/ENCRYPTION.md](docs/ENCRYPTION.md).\n\n## HTTP Servlet support\n\nThe primary usecase is the support for session management for `HttpSessions`.\nSupport includes the following:\n\n* Creation of sessions on demand\n* Storing of session attributes between requests\n* Invalidation of sessions\n* Session expiration management\n* Session propagation to clients via cookie and URL.\n* Full support for all non-deprecated `HttpSession` methods\n* Support for callbacks for values stored in session that implement\n  `javax.servlet.http.HttpSessionActivationListener` or\n  `javax.servlet.http.HttpSessionBindingListener`\n* When used with an agent, support for listener objects such as\n  `javax.servlet.http.HttpSessionListener` and\n  `javax.servlet.http.HttpSessionAttributeListener`\n* Support for Servlet 3.1 features such as session id switch\n* Compatibility with Servlet 2.5\n* Session stickiness - sessions can be sticked to node and expiration events will then be triggered on node owner of session\n* Support for non-distributable web applications\n\n### General Concepts\n\n#### Session Repository\n\nThe session information needs to be stored between server request.\nThis storage is called session repository.\nDifferent repository implementations are supported by the session replacement mechanism.\n\n#### Configuration\n\nMost of the configuration can be specified with `ServletContext`s initialization\nparameters, system properties and some paramaters can be provided via the agent.\nUnless otherwise specified the general rule for priority of configuration is as\nfollows in descending order:\n\n* `ServletContext`s initiate parameters (set through `web.xml` or programmatically since Servlet 3.x )\n* Agent configuration (when exists)\n* System properties\n* Default values\n\n#### Architecture\n\nHere is the block diagram of the architecture:\n\n```\n     +--------------------------------------------------+\n     |                                                  |\n     | +---+          +-------------------------------+ |\n     | |   |          |                               | |\n     | |   |          |                               | |\n     | | * |          |                               | |\n     | | F |          |                               | |\n     | | I | Wrapped  |        WEB APPLICATION        | |\n     | | L | request  |                               | |\nHTTP | | T |---------\u003e|                               | |\n----\u003e| | E |          |                               | |\nHTTPS| | R |          |                               | |\n     | | * |          |                               | |\n     | |   |          +-------------------------------+ |\n     | |   |                          ^                 |\n     | |   |      Session interaction |                 |\n     | |   |                          v                 |\n     | |   +------------------------------------------+ |\n     | |          *Session-management*                | |\n     | +----------------------------------------------+ |\n     |        Container (e.g. JBoss, jetty, tomcat)      |\n     +-------------------------------------+------------+\n     |            JVM                      |   *Agent*  |\n     +-------------------------------------+------------+\n```\n\nIn the above picture, *Agent*, *Filter* and *Session-management* are modules added to support\nsession storage in repository.\n\n* The *Filter* wraps all incoming requests to allow session retrieval and commit.\n  When using the agent, all filters have code to wrap incoming requests,\n  however, only the first one will wrap it.\n  The following filter in chain will return the request it received.\n  The canonical implementation of  a filter is\n  [com.amadeus.session.servlet.SessionFilter](session-replacement/src/main/java/com/amadeus/session/servlet/SessionFilter.java)\n\n* The *Session-management* is intercepting all interactions with sessions\n  and communicates with the session repository.\n  Normally, unless using container specific interface,\n  container should not be aware of the existence of the session.\n\n* The *Agent* performs instrumentation as described below.\n\n### Session management\n\nThe general algorithm for managing sessions is independent of the underlying storage.\nIt has the following characteristics:\n\n* Session retrieval from repository.\n\n* New session creation of session with cryptographically secure session id.\n  For details see Session id section.\n\n* Partial and full session updates allows updating all session attributes\n  or only those that were changed or touched during session request (if repository supports it).\n  For details see Optimized session updates section.\n\n* Support for atomic commit allows updating all attributes at once in one transaction or network exchange (if repository supports it).\n\n* Support of non-cacheable attributes, i.e. attributes that are stored or retrieved from repository on each access to session attribute.\n  For details see Non-sticky sessions and concurrent access.\n\n* Support for session encryption when storing sessions into repository.\n\n#### Optimized session updates\n\nThe session management keeps track of the attributes that have changed\n(including deletion) and only updates those.\nThis means if an attribute is written once and read many times we only need to\nwrite that attribute once.\n\nSession management can be configured to update all the attributes no matter\nwhat or to update all non-primitive wrappers\n\n#### Session id\n\nA session id is an UUID generated using type 4 algorithm, a random\nsequence of bytes encoded in modified base64 algorithm, or a random sequence\nof bytes encoded in modified base64 algorithm that doesn't allow substrigns that\nmatch Luhn checksum. \nIf a request is made for a session with an id that is expired,\nnot valid or not present in the repository, the id is invalidated and a new one is generated.\nThis prevents [simple session fixation attack scenario](https://en.wikipedia.org/wiki/Session_fixation).\n\n##### UUID based session id\n\nThe UUID based session id is activated by setting servlet or system property\n`com.amadeus.session.id` to `uuid`.\nUUID based session id is the default mechanism at the moment,\nbut this may change before a final release.\n\nIf the servlet parameter or system property\n`com.amadeus.session.noHyphensInId` is set to `true`,\nhyphens are removed from UUID.\n\n##### Random session id\n\nThe random session id is activated by setting servlet or system property\n`com.amadeus.session.id` to `random`.\nThis is default strategy.\n\nRandom session id length is specified in bytes using the servlet parameter or system property\n`com.amadeus.session.id.length`.\nThe length of the id as a string will be 4 characters for\neach 3 bytes of the id (with padding up to a number that divides by 4).\nE.g for 1, 2 or 3 bytes length there will be 4 characters in the id string,\nfor 4, 5 or 6 there will be 8, etc.\n\n##### Random session id without Luhn checksum matching substrings\n\nThe random session id without substrings matching Lunh checksum is activated by \nsetting servlet or system property `com.amadeus.session.id` to `no-luhn`. This\nis useful when there is logic that conceals credit card information (credit \ncard numbers are sequences of numbers that match Luhn checksum).\n\nThe session id length is specified in bytes using the servlet parameter or system property\n`com.amadeus.session.id.length`.\nThe length of the id as a string will be 4 characters for\neach 3 bytes of the id (with padding up to a number that divides by 4).\nE.g for 1, 2 or 3 bytes length there will be 4 characters in the id string,\nfor 4, 5 or 6 there will be 8, etc.\n\n##### Session format\n\nIt is possible to tweak the generated session id format using proper configuration parameter.\nParameter `com.amadeus.session.timestamp` can be used to enforce presence of '!xxxxx' at end of generated jsessionid\nxxxxx being the number of millis ellapsed since january 1970 and corresponding to UNIX timestamp.\n\n##### Session isolation\n\nSessions can be isolated per application.\nWhile this is repository dependent, it is expected that repositories support this.\nThis isolation is done using unique identifier called namespace and it should be part of the key or repository choice.\nThe namespace is not communicated to the clients and is only known by the server.\n\nBest practice is to have different namespaces for sessions in different applications or webapps.\nIf applications want to share sessions they can use the same namespace.\n\nIn case of webapps, default behavior is that either the namespace is defined using the servlet initialization\nparameter `com.amadeus.session.namespace`, or if not present, the context path of the webapp is used.\n\n**NOTE**: When the context path is used as namespace, two different application\nservers with different webapps,\nhaving the same context path will share the same session namespace if they use\nthe same repository.\n\nOutside servlet containers the default namespace name is `default`.\n\n##### Session id propagation between webapps\n\nWhen propagating a session id from one webapp to another (e.g. using `RequestDispatcher`),\nthe session id doesn't change.\nThe first webapp that got the request is the one that controls and sets the session id.\n\nNote however, that by default each webapp will store its sessions in a\ndifferent namespace even if they use the same session id.\n\n### Session propagation\n\nTwo builtin strategies are available for session propagation.\nThe first one is based on cookies and is the default one.\nThe second one is based on URL rewriting where the session is appended at the\nend of the path part of URL (preceding the query).\n\nThe session propagation can be configured using web.xml (standard Servlet approach)\n\n```xml\n\u003cweb-app\u003e\n...\n  \u003csession-config\u003e\n    \u003ctracking-mode\u003eURL\u003c/tracking-mode\u003e\n  \u003c/session-config\u003e\n\u003c/web-app\u003e\n```\nIt can also be configured using system property or servlet initialization parameter\n`com.amadeus.session.tracking`. Valid values are `COOKIE`, `URL` or `DEFAULT`\n(which is same as `COOKIE`).\n\nThe URL rewriting session propagation is not supported on Tomcat 6 based servlet\nengines (Tomcat 6.x, JBoss 6.x).\n\n#### Cookie Session Management\n\nThe session is stored as a UUID inside a cookie.\nThe cookie name is one of the following by descending order of priority:\n\n* Using the `com.amadeus.session.sessionName` initialization parameter of the ServletContext.\n* Using the `com.amadeus.session.sessionName` system property.\n* `JSESSIONID`.\n\nIn case of HTTPS requests, cookies can be marked as secure.\nThis can be configured by setting the `com.amadeus.session.cookie.secure`\ninitialization parameter or system property to `true`.  \nTo apply this behavior only when the request is over secured channel\n(i.e. HTTPS), set `com.amadeus.session.cookie.secure.on.secured.request`\nto `true`. In this case if request comes over insecure channel (i.e. HTTP),\nthe cookie will not be marked as secure. Reason for this behavior is that\napplication servers are often behind a load balancer or a TLS offloader \nwhich calls them via HTTP, so even though the exchange with client is over HTTPS, application server is not aware of it. In those cases we want to force cookie to\nbe marked as secure. When application server is either directly exposed\nto secured requests or are aware if request is secured via headers injected\nby TLS offloaders, this additional property allows using that capability to\nselect whether cookie should be marked or not.\n\nFor Servlet 3.x and later containers, cookies can be marked as HTTP only.\nThis can be configured by setting `com.amadeus.session.cookie.httpOnly`\ninitialization parameter or system property to `true`.\n\nCookies apply only on the context path of the web app.\nI.e. it is only sent for URLs that\nare prefixed by context path.\n\nThe cookie expiration is set only if the session has expired,\nand the value of expiration is 0 (i.e. immediately).\n\n### Session stickiness\n\nThere is no specific support for concurrent calls on the session.\nStandard approach to simplify concurrent access to session is to use session stickiness.\n\nSession stickiness implies that subsequent requests for the same session arrive\nto the same application server node.\nLoad-balancers and proxy web servers can provide such stickiness features.\nSome vendors call this feature \"affinity\".\nThe feature is usually based on HTTP cookies.\n\nIn general, the support for stickiness depends also on the repository\nimplementation.\nSee the details of the implementation for more information.\nThe default in-memory repository requires session stickiness.\nRedis repository can support session stickiness.\n\nThe cookie or URL representation of a session id doesn't carry any information\nabout the node that owns session, so it is entirely up to load-balancer or\nproxy web server to handle it.\n\nAs per Servlet 3.1 standard, sessions are considered sticky by default.\nThe support for stickiness can be disabled using the `com.amadeus.session.sticky`\nsystem property or servlet parameter.\n\n#### Non-sticky sessions and concurrent access\n\nWhen sessions are used with stickiness disabled,\nmultiple nodes can access and modify the session at the same time.\nThe last one modifying the session wins.\nThe concurrency in this case can be supported by declaring some or all\nattribute names as non-cacheable.\nWhen attribute names are declared as non-cacheable,\nany access to `HttpSession` retrieving, deleting or storing\nattributes will trigger retrieval, deletion or storing in remote repository.\nThis may have a negative impact on latency as each attribute is retrieved at\nthe access time and should be used with care.\nNon-cacheable attributes are specified as comma-separated list using\n`com.amadeus.session.non-cacheable` initialization parameter or system property.\n\n## Agent and instrumentation\n\nThe project comes with a java agent that is used to instrument the `ServletContext`\nimplementation of the JEE container as well as any `Filter` used by web applications.\nThe agent also allows the setting of global defaults for the session management.\n\n### Filter instrumentation\n\nAll classes implementing the `Filter` interface are instrumented to allow\nsession management.\nFollowing modifications are applied:\n\n* Existing `doFilter` method is renamed to `$$renamed_doFilter`.\n* A new method `doFilter` is created, that wraps http request and response\n  objects into custom wrappers that are responsible for creating the session.\n  The filter commits the session if needed at the end of processing.\n  The equivalent of the code is as follows:\n\n```java\n  public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {\n    ServletRequest oldRequest = request;\n    request = SessionHelpers.prepareRequest(oldRequest, response);\n    response = SessionHelpers.prepareResponse(request, response);\n    try {\n      doFiltеr(request, response, chain);\n    } finally {\n      SessionHelpers.commitRequest(request, oldRequest);\n    }\n  }\n```\n\n### ServletContext instrumentation\n\nThe agent will instrument `ServletContext` to allow registering and\nnotification of `HttpSessionListeners` and `HttpSessionAttributeListeners`.\nThis is done by injecting code that registers listeners associated to\n`ServletContext` into the `addListener` method.\n\n### Configuration\n\nTo use agent, add the following to the options passed to JVM: `-javagent:session-agent.jar=arg,arg,arg`\n\nAll agent arguments will be set into `com.amadeus.session.repository.conf`\nsystem property if it has not been set already. Few of the arguments will be\nused by agent itself:\n\n* `provider=factory` class for repository or name of the provider.\n\n* `log=debug` activates debug mode. Not active by default.\n\n* `timeout=` default maximum inactive interval\n\n* `distributable=true`: If set to `false` the session distributution is managed via `web.xml` and `\u003cdistrubtale/\u003e` tag.\n   Default value is `true` meaning that all sessions will be treated as distributable, even when it is not specified via `web.xml`.\n\n* `interceptListeners=true` is used to discover `HttpSessionListeners` and\n  `HttpSessionAttributeListeners` for applications servers where the\n  base instrumentation doesn't discover them.\n\nThe agent will set following system properties:\n\n* `com.amadeus.session.repository.conf` - specifies configuration for repository provider.\n  If not set, will be set to the value of the agent argument string.\n\n* `com.amadeus.session.repository.factory` - specifies factory class for repository provider.\n  Can be overridden by the agent parameter `provider`.\n\n* `com.amadeus.session.timeout` - default maximum inactive interval.\n  Can be overriden by agent parameter `timeout`.\n\n* `com.amadeus.session.distributable` - if set to `false` the session distributution is managed via `web.xml` and `\u003cdistrubtale/\u003e` tag.\n  Default value is `true` meaning that all sessions will be treated as distributable, even when it is not specified via `web.xml`.\n\n* `com.amadeus.session.intercept.listeners` - `true` if discovery of listeners\n  is done by creating intercepting calls to listeners by the applications\n  server.\n\n* `com.amadeus.session.debug` - `true` if agent debug level tracing is activated.\n\n\n## Session repository\n\nThe session repository is the storage were sessions are stored between requests.\n\n### In memory\n\nDefault implementation of repository is an `in-memory` repository.\nThis repository stores sessions in JVM's heap using a ConcurrentHashMap.\nIt doesn't support fail-over or high availability and is primarily meant for test and development.\nThere are no specific configuration parameters for this repository.\n\nThis repository is used by default for non-distributable web-apps.\nIt is controlled using the initialization parameter or\nsystem property `com.amadeus.session.distributable.force` which is set to `false` by default.\nIf this setting has the value `true`, all web-apps, including those without distributable marker,\nwill be stored in the default repository that supports distribution/replication (i.e. Redis repository).\nNote that the `com.amadeus.session.distributable` system property also impacts this behavior.\nFollowing table explains interaction between `web.xml` and these two configuration items:\n\n|com.amadeus.session.distributable.force|com.amadeus.session.distributable|web.xml distributale tag|repository used|\n| --------------------------------------- | --------------------------------- | ------------------------ | ---------------                    |\n| false                                   | false                             | absent                   | In memory                          |\n| false                                   | false                             | present                  | Distributable repository           |\n| false                                   | true                              | absent                   | In memory                          |\n| false                                   | true                              | present                  | Distributable repository           |\n| true                                    | false                             | absent                   | In memory                          |\n| true                                    | false                             | present                  | Distributable repository           |\n| true                                    | true                              | absent                   | Distributable repository + warning |\n| true                                    | true                              | present                  | Distributable repository           |\n\n### Redis repository\n\n**NOTE**: This explanation is adapted from Spring Session.\n\nSessions are stored using equivalent of the Redis [`HMSET` command](http://redis.io/commands/hmset):\n\n```redis\nHMSET com.amadeus.session:webapp-namespace:{33fdd1b6-b496-4b33-9f7d-df96679d32fe} #:creationTime 1404360000000 #:maxInactiveInterval 1800 #:lastAccessedTime 1404360000000 attrName someAttrValue attrName2 someAttrValue2\n```\n\nIn this example, the session following statements are true about the session:\n\n* The session id is 33fdd1b6-b496-4b33-9f7d-df96679d32fe.\n* The session was created at 1404360000000 in milliseconds since midnight of 1/1/1970 GMT.\n* The session expires in 1800 seconds (30 minutes).\n* The session was last accessed at 1404360000000 in milliseconds since midnight of 1/1/1970 GMT.\n* The session has two attributes. The first is \"attrName\" with the value of \"someAttrValue\". The second session attribute is named \"attrName2\" with the value of \"someAttrValue2\".\n* The session belongs to namespace called webapp-namespace.\n\nSession-management handles optimized writes, so if in a request we update\nonly the session attribute \"sessionAttr2\",\nthe following would be executed upon saving:\n\n```redis\nHMSET com.amadeus.session:webapp-namespace:{33fdd1b6-b496-4b33-9f7d-df96679d32fe} sessionAttr:attrName2 newValue\n```\n\nIn addition to the special attributes `#:creationTime`, `#:lastAccessedTime`, and `#:maxInactiveInterval`,\nan attribute named `#:invalidSession` is put into the set at the start of the delete or invalidation process.\n\nWhen session stickiness is used another optional special attribute called `#:ownerNode` is stored in session.\nIt contains the name of the application server node that was the last one to touch the session.\nThe default behaviour is to store host name of the node,\nbut it can also be specified using `com.amadeus.session.node` system property.\n\n#### Single instance mode\n\nIn single instance mode all containers connect to a single Redis instance.\n\nSingle instance mode is activated by specifying `mode=SINGLE` in the provider configuration string,\nor by specifying the system property `com.amadeus.session.redis.mode=SINGLE`.\n\n#### Sentinel mode\n\nIn sentinel instance mode all containers connect to sentinel nodes and obtain\nthe current Redis master node from them.\n\nSentinel mode is activated by specifying `mode=SENTINEL` in the provider\nconfiguration string, or by the specifying system property\n`com.amadeus.session.redis.mode=SENTINEL`.\nThe name of the master can be specified using `com.amadeus.session.redis.master`.\n\nNode addresses are configured by specifying either sentinel node\nnames or IP addresses.\nAny DNS name in the configuration is resolved to all mapped IP addresses.\nThis, for example, allows using Kubernetes services to get all sentinel nodes.\n\n#### Cluster mode\n\nIn cluster instance mode all containers connect to cluster nodes and obtain current the Redis nodes from them.\n\nCluster mode is activated by specifying `mode=CLUSTER` in provider\nconfiguration string, or by specifying the system property `com.amadeus.session.redis.mode=CLUSTER`.\n\nCluster mode is configured by specifying cluster node names or IP addresses.\nAny DNS name in the configuration is resolved to all mapped IP addresses.\nThis, for example, allows using Kubernetes services to get all cluster nodes.\n\nThe data for a single session is stored on a single Redis node using the hash\ntags in the key name (i.e. session is put in braces in key {33fdd1b6-b496-4b33-9f7d-df96679d32fe}).\n\nDue to characteristics of the Redis cluster, the update of data is not done in atomic mode.\n\n#### Redis Configuration\n\nThe redis repository can be configured using either a repository configuration\nstring which is specified using `com.amadeus.session.repository.conf` servlet initialization parameter,\nsystem property or through agent arguments or through the following system properties.\n\n* `com.amadeus.session.redis.host`: The ip address or DNS name of Redis server.\n   For Sentinel and Cluster modes it can be slash (`/`) separated list of servers.\n   If it is a DNS name, DNS resolution will retrieve all associated IP addresses.\n   A port can be provided as part of the address and is separated by commas.\n   Default value is `localhost`.\n\n* `com.amadeus.session.redis.port`: The port Redis server(s) listen to.\n  May be overridden in host configuration.\n  The default value is `6379`.\n\n* `com.amadeus.session.redis.master`: Name of Redis master in Sentinel mode.\n  Default is `com.amadeus.session`.\n\n* `com.amadeus.session.redis.mode` or `mode`: Specifies Redis mode.\n  Can be one of `SINGLE`, `SENTINEL` or `CLUSTER`. Default value is `SINGLE`.\n\n* Size of Jedis pool.\n  As redis connections are blocking, we create a pool of Redis connections to\n  allow multiple parallel connections to the Redis server.\n  By default set to `100`.\n  Defined using the `com.amadeus.session.redis.pool` system property or servlet\n  initialization parameter, or using the pool configuration parameter.\n\n* `com.amadeus.session.redis.expiration`: Specifies the expiration strategy.\n  Can be `NOTIF` or `ZRANGE`. See below for explanation. Default is `ZRANGE`.\n\n#### Session expiration\n\nAn expiration is associated with each session using the\n[`EXPIRE` command](http://redis.io/commands/expire) based upon the\n`maxInactiveInterval` plus 5 minutes.\nFor example:\n\n```redis\nEXPIRE com.amadeus.session:webapp-namespace:{33fdd1b6-b496-4b33-9f7d-df96679d32fe} 2100\n```\n\nThe expiration that is set is 5 minutes after the session actually expires.\nThis is necessary so that the value of the session can be accessed when the\nsession expires.\nAn expiration is set on the session itself five minutes after it actually\nexpires to ensure it is cleaned up, but only after we can perform\nany necessary processing.\nOn the other hand, putting expiration assures that data will be removed from\nthe store even if no clean-up has been performed.\n\n**NOTE:** The session management ensures that expired\nsessions are not returned to an application.\nThis means there is no need to check the expiration before using a session.\n\n**NOTE:** For safety reasons, many session management objects expire 5 minutes\nafter the time instant when the session expires.\nThis is a hardcoded value and is meant as a safety margin to complete all necessary processing.\n\n#### Notification expiration strategy\n\n__Doesn't work with Redis cluster__\n\nThis strategy relies on the expired\n[keyspace notifications](http://redis.io/topics/notifications)\nfrom Redis to clean-up and delete expired sessions.\n\nExpiration is not tracked directly on the session key itself since this would\nmean the session data would no longer be available.\nInstead, a special session expiration key is used. In our example the expiration key is:\n\n```redis\nSETEX com.amadeus.session:expire:webapp-namespace:{33fdd1b6-b496-4b33-9f7d-df96679d32fe} 1800 \"\"\n```\n\nWhen this key expires, a keyspace notification event triggers a\nlookup for the actual session and if it exists the session removal\nstarts.\n\nOne problem with relying on Redis expiration exclusively is that Redis makes\nno guarantee of when the expired event will be fired if the key has not been\naccessed. Specifically the background task that Redis uses to clean up\nexpired keys is a low priority task and may not trigger the key expiration.\nFor additional details see the [Timing of expired events](http://redis.io/topics/notifications)\nsection in the Redis documentation. \n\n##### Missed expire events\n\nTo circumvent the fact that expired events are not guaranteed to happen we\ncan ensure that each key is accessed when it is expected to expire. This\nmeans that if the TTL is expired on the key, Redis will remove the key and\nfire the expired event when we try to access the key.\n\nFor this reason, each session expiration is also tracked to the nearest\nminute. This allows a background task to access the potentially expired\nsessions to ensure that Redis expired events are fired in a more\ndeterministic fashion.\nFor example:\n\n```redis\nSADD com.amadeus.session:expirations:1439245070 33fdd1b6-b496-4b33-9f7d-df96679d32fe\nEXPIREAT com.amadeus.session:expirations1439245070 1439245370\n```\n\nThe expiration of this key is set 5 minutes after the minute it actually expires.\nThe background task will then perform following Redis operations:\n\n```redis\nSMEMBERS com.amadeus.session:expirations:1439245070\nDEL com.amadeus.session:expirations:1439245070\n```\n\nFrom Redis 3.2, a `SPOP` command can be used instead of the above block. When used\nin non-cluster mode, the block is wrapped in `MULTI/EXEC` sequence.\nIn this case, each node retrieves up to 1000 members until all are exhausted, and\nthen a `DEL` command is issued.\n\nThis `SMEMBERS` command returns the list of sessions for which to explicitly\nrequest session expires key (using `EXISTS`). By accessing the key, rather than deleting it,\nwe ensure that Redis deletes the key for us only if the TTL is expired.\n\n##### Session access\n\nOn each access to session, in this strategy we will remove session key from previous access\n`expirations` set using a `SREM` command and store it in a new `expirations` set. In our\nexample if the session is subsequently accessed and its minute of expirations is 1439245080,\nwe would send the equivalent of the following commands:\n\n```redis\nSREM com.amadeus.session:expirations:1439245070 33fdd1b6-b496-4b33-9f7d-df96679d32fe\nSADD com.amadeus.session:expirations:1439245080 33fdd1b6-b496-4b33-9f7d-df96679d32fe\nEXPIREAT com.amadeus.session:expirations:1439245080 1439245380000\n\nEXPIRE com.amadeus.session:webapp-namespace:{33fdd1b6-b496-4b33-9f7d-df96679d32fe} 2100\nSETEX com.amadeus.session:expire:webapp-namespace:{33fdd1b6-b496-4b33-9f7d-df96679d32fe} 1800 \"\"\n```\n\n##### Sequence diagram of the notification expiration strategy\n\n![Notification expiration strategy sequence diagram](https://www.websequencediagrams.com/cgi-bin/cdraw?lz=dGl0bGUgTm90aWZpY2F0aW9uIGV4cGlyAAUGc3RyYXRlZ3kKCkV2ZW50LT5KYXZhQ2xpZW50OiBDcmVhdGUgc2Vzc2lvbgoAEQotPgANB0lkABURYWN0aXYALAtJZAoKbm90ZSByaWdodCBvZiAAVwwKICBtYXhJbgAxBWVJbnRlcnZhbCA9IDE4MDBzICAgIAAMF0RlbGF5ID0AIQYrIDUgbWluID0gMjEwMCAKZW5kIG5vdGUKAIEcGEVYUElSRQCBGQoAMwUAIA4AgiAFZVMAgV4KU0VURVgAgjYGZToADwkAgSQFICIiAIFuCgAqDwCBXx9leHA9dW5peFRpbWUrAIF4Ez0xNDM5MjQ1MDcwACgHAIFzByBleHAgKyA1AIFyBgAfBzM3MCAAgWcYAINsCnMASgo6IFNBREQAhAcLczoAZgsAgzkKAIFQDgA2EABEJACCbQZBVABUGACBOAoKCgCEeRVBY2Nlc3MAhHkVAIFFFlNSRU0AgTgiZGVzdHJveQCBQhcAgiIgODA6AIIoGTgAghkoOABDJQCCJB44AII4CjgAhUgvAIU9OQoKCgphbHQAhHUIMDgwIHRpbWUgaGFzIGJlZW4gcmVhY2hlZAoJAId8BW92ZXIgAIhiBTogCgkJUmVkaXMAJwVub3QgZGV0ZWN0ZWQgdGgAhFkMCgkJV2Ugbm90aWZ5IGhpbSBtYW51YWxseQoJAIdbCgkAiSkTAIEEEACBAgkAgkYjU1BPUACDOxcKCQCDcBUtAIorDgCJcAsAYw0AiHUNSVNUUwCKGgsAghkLAIpSCwCCHwYAghcGcyB0aGF0AIIcBQCLDAcAgmUFAIkjBmQKCQCLCwkAizgOAIk8BwAgCACCRwYAjAQHCgplbHNlAFURAIJ0DgCJew8AMytlbgCLdQgAg3cFAIxPDWxlYW5pbmcgcHJvY2VzcwCLHQ4AgXcMREVMAIccEwCMUwsAixkdREVMAIsjEQCHZg4AiyALQ29udGFjdCBHaXRIdWIg\u0026s=modern-blue)\n\nThe diagram displays a case where redis expiration (done with\n  `SETEX webapp-namespace:expire:{sessionId} 1800`) was not triggered.\n\nIn this case a thread in our Java client periodically polls our expirations\nkeys mechanism in order to retrieve all the session ids that could have expired.\nWe then do a touch on these keys, which manually makes Redis aware whether these\nkeys have expired.\nIf they have, it will then push this notification to our Java client.\n\nPlease note that this diagram is without session stickiness.\nBesides, we removed the prefix `com.amadeus.session` for readability.\n\nFor diagram source code, see [docs/NotificationExpirationStrategy.md](docs/NotificationExpirationStrategy.md).\n\n#### Session stickiness\n\nRedis session repository doesn't require session sitckiness and it can be\ndisabled.\nNote however, that the standard behavior for Servlet containers is to have\nsession stickiness.\nIn particular if an  application handles concurrent\nrequests accessing same session attributes, or uses stateful session EJBs,\nstickiness is needed and implemented as follows.\n\n##### Node stickiness management\n\nIn a stickiness scenario only the node owning the session will delete session on\nexpire event.\n\nWhen using node stickiness, the `expire` key contains also the node identifier.\n\n```redis\nSETEX com.amadeus.session:expire:node123:webapp-namespace:{33fdd1b6-b496-4b33-9f7d-df96679d32fe} 1800 \"\"\n```\n\nWhen the listener running in the JVM receives the event that this key expires, it\nchecks if the key prefix matches the nodes one.\nIf it is the case, the session will be deleted.\n\nWe also need to handle the case where the owner node doesn't receive this notification (e.g.\nthe node is down, there were network issues, Redis servers are busy).\nFor this reason we add a `forced-expirations` key that is set one minute after the `expirations` key.\nIt has almost the same semantics and logic, with the only difference being that the key is different\nand it is set to expire one minute later.\nThe first JVM server receiving this message will\nforce expirations of all sessions present in this key.\n\nIn the above example, when the session was created, we would send also the following:\n\n```redis\nSADD com.amadeus.session:forced-expirations:1439245080 33fdd1b6-b496-4b33-9f7d-df96679d32fe\nEXPIREAT com.amadeus.session:forced-expirations1439245080 1439245380000\n```\n\nWhen the session is subsequently accessed, again, in the above example, the following sequence is sent.\n\n```redis\nSREM com.amadeus.session:forced-expirations:1439245080 33fdd1b6-b496-4b33-9f7d-df96679d32fe\nSADD com.amadeus.session:forced-expirations:1439245080000 33fdd1b6-b496-4b33-9f7d-df96679d32fe\nEXPIREAT com.amadeus.session:forced-expirations:1439245080 1439245390\n```\n\nWhen a node gets the request for a session, if it is not the owner of that session,\nit will additionally delete the old expire key and create new one with its own id.\nThis is expected to happen if a failover occurs at load-balancer in front of JEE servers.\nE.g. load-balancer detects that old node is not responding and decides to send session to new one.\n\nThe session will still be valid, but it will have a new owner node.\nIt would look as follows:\n\n```redis\nDEL com.amadeus.session:expire:OLDNODEID:webapp-namespace:{33fdd1b6-b496-4b33-9f7d-df96679d32fe}\nSETEX com.amadeus.session:expire:NEWNODEID:webapp-namespace:{33fdd1b6-b496-4b33-9f7d-df96679d32fe} 1800 \"\"\n```\n\n##### Sessions that do not expire\n\nIf the session never expires, there is no corresponding `expire` key and it is not stored in\n`expirations` or `forced-expirations` sets. The primary key of the session is marked as persisted\nusing [`PERSIST` command](http://redis.io/commands/persist):\n\n```redis\nPERSIST com.amadeus.session:{33fdd1b6-b496-4b33-9f7d-df96679d32fe}\n```\n\n##### Network traffic and JVM processing remarks\n\nEach session that expires will geneate message to every JVM running the web application\n(JVM node). Each node will check the incoming message, and in case of sticky\nsessions, only the nodes owner of the session reacts to this mesage. When using\nnon-sticky sessions, all JVM nodes will try to get information about the session,\nbut only the first JVM node that gets the message will process it.\n\nEach clean-up key (i.e. `expirations` or `forced-expirations`) will generate one\nmessage to each JVM node every minute when there are sessions that expire in\nthe previous minute.\nEach JVM node will try to process the message, but only the first\none will actually get the content and process it.\n\n#### ZRange expiration strategy\n\nThis strategy relies on the [Redis Sorted Set](http://redis.io/topics/data-types) aka [`ZRANGE`](http://redis.io/commands/ZRANGE).\nIn this case there is a single Sorted Set with a key `com.amadeus.session:all-sessions-set`. Each session id\nis stored in this sorted set using its expiration instant as the score.\n\nThe background task will then perform following Redis `ZRANGEBYSCORE` operation to retrieve all expired session and\ninitiate deletion. For example, at the instant 1439246090000, we would use\n\n```redis\nZRANGEBYSCORE com.amadeus.session:all-sessions-set 0 1439246090000\n```\n\n##### Session stickiness\n\nWhen using ZRANGE strategy with session stickiness, we store owner node of each session.\nThe background task will first retreive all sessions from last 5 minutes and expire only ones\nbelong to the node in question. \n\nAfter this, the background task will load all sessions that expired until 5 \nminutes before now. It is assumed that all of those sessions no longer have\nlegitimate owner, as otherwise, the owner node would have time to expire them. \nConsequently, they can be removed by any node, and first node that removes\ntheir key from sorted set will be the one that will exipre them.\n\n![Sorted set expiration strategy sequence diagram](https://www.websequencediagrams.com/cgi-bin/cdraw?lz=dGl0bGUgU29ydGVkIHNldCBleHBpcmF0aW9uIHN0cmF0ZWd5CgpFdmVudC0-SmF2YUNsaWVudDogQ3JlYXRlIHNlc3Npb24KABEKLT4ADQdJZAAVEWFjdGl2ACwLSWQKCm5vdGUgcmlnaHQgb2YgAFcMCiAgbWF4SW4AMQVlSW50ZXJ2YWwgPSAxODAwcwAIFkRlbGF5ID0AHQUgKyA1IG1pbiA9IDIxMDBzIAplbmQgbm90ZQoAgRgYRVhQSVJFAIEVCgA0BQAgDmFsbC0AgW8Hcy1zZXQ6IFpBREQgAAcQAIFZCjpub2RlIG5vdygpKwCBPRMAggsKAEoQCgoAgmUVQWNjZXNzAIJxCQA_XgCBei4KCmFsdCBldmVyeSA2MCBzCgoJAIQoE3J1bgCEYQZlIHRhc2sKCQCCQB9SQU5HRUJZU0NPUkUAglYSAIJUBS01bWluAIJeBgoJAIRNBW92ZXIAhEENSWYgAIMEBT09IHRoaXMACAUAWSJFTQCDPxsKAFEbADYFd2FzIHN1AIMrBWZ1bACBWw4AhkEMZGVsZQCGCQ0AgS0XUmV0cmlldmUgb2xkAIZzCHMAggE_MACCNwsAd4EZCmVuAIgjCACDSBEAgi0QIHByb2Nlc3MAh1cOAIcFCyBERUwAiHcLZGVzdHJveQCJCAwAiUIMAIUkBgCDayE\u0026s=modern-blue)\n\nFor diagram source code, see [docs/SortedSetExpirationStrategy.md](docs/SortedSetExpirationStrategy.md).\n\n## Session Encryption\n\nSee [docs/ENCRYPTION.md](docs/ENCRYPTION.md).\n\n## Thread pools\n\nThe session support system manages two thread pools:\n\n* one for executing long running or blocking tasks.\n* one for executing scheduled tasks (at a given moment in future).\n\nIf the JEE container supports it, threads are obtained using the managed thread factory using the default JNDI\nname `java:comp/DefaultManagedThreadFactory`). If the application is not running in container, or if the JEE container\ndoesn't support managed thread factories, threads are created using `Executors.defaultThreadFactory()`.\n\n## Logging and Monitoring\n\n### Logging\n\nThe logging is based on slf4j framework. Like other dependencies, this one is\nshaded and doesn't interfere with applications that use slf4j themselves.\n\nThe implementation behaves differently from standard slf4j framework, as it is\nable to delegate logging to any one of the following frameworks in order\nof precedence:\n\n* log4j (if `org.apache.log4j.Logger` class is present in class loader)\n* commons logging (if `org.apache.commons.logging.Log` class is present in class loader)\n* JDK4 logging\n* simple logging to `stderr`\n\nIn case of log4j logging, the logging mechanism also adds a field containing\nsession id to the Mapped Diagnostic Contex (MDC) implementation. The added field\ncan be added to the output of the logger. By default, the name of the field is\n`JSESSIONID`, but it can be configured using system properties:\n\n* `com.amadeus.session.logging.mdc.enabled` activates session id to MDC. Default value is true.\n* `com.amadeus.session.logging.mdc.name` specifies the name of MDC field. Default value is `JSESSIONID`.\n\n### Monitoring via JMX\n\nAll metrics are exposed as JMX beans under `metrics.session.NAMESPACE` where\nNAMESPACE is either the context path of the JEE web application or the namespace\nconfigured via `com.amadeus.session.namespace`.\n\n#### Session metrics\n\n* `com.amadeus.session.created` measures the total number of created sessions as well as rate of sessions created in last 1, 5 and 15 minutes.\n* `com.amadeus.session.deleted` measures the total number of deleted sessions as well as rate of sessions measures rate of sessions deleted in the last 1, 5 and 15 minutes.\n* `com.amadeus.session.missing` measures the total number of session which were not found in repository and also measures rate of such occurrences in last 1, 5 and 15 minutes.\n* `com.amadeus.session.retrieved` measures the total number of session retrievals as well as the rate of sessions retrieval from store in last 1, 5 and 15 minutes.\n* `com.amadeus.session.timer.commit` measures the histogram (distribution) of the  elapsed time during commit as well as the total number of commits and rate of commits over the last 1, 5 and 15 minutes.\n* `com.amadeus.session.timer.fetch` measures the histogram (distribution) of elapsed time during fetches of session data from the repository as well as the total number of fetch requests and rate of fetch requests over the last 1, 5 and 15 minutes.\n* `com.amadeus.session.serialized.bytes` measures the amount of data that was serialized to be sent.\n* `com.amadeus.session.serialized.distribution` measures the statistical information about data that was serialized in last 5 minutes.\n* `com.amadeus.session.deserialized.bytes` measures the amount of data that was deserialized when received.\n* `com.amadeus.session.deserialized.distribution` measures the statistical information about data that was deserialized in last 5 minutes.\n* `com.amadeus.session.invalidation.errors.expiry` measures the total number of invalidation errors.\n* `com.amadeus.session.invalidation.errors` measures the total number of invalidation on expiry errors.\n\nTotal number of active sessions is the total number of created sessions on all\nnodes minus total number of deleted sessions on all nodes.\n\n#### Timings\n\n* `com.amadeus.session.timers.delete-async` measures the histogram (distribution) of elapsed time during deletes of sessions as well as the total number of deletions and the rate of deletions over the last 1, 5 and 15 minutes.\n* `com.amadeus.session.timers.redis.expiration-cleanup` measures the histogram (distribution) of elapsed time during expiration cleanup of sessions stored in Redis as well as the total number of expiration cleanup invocations and the rate over the last 1, 5 and 15 minutes.\n* `com.amadeus.session.timers.redis.forced-cleanup` measures the histogram (distribution) of elapsed time during forced cleanup of sessions stored in redis as well as the total number of expiration cleanup invocations and the rate over the last 1, 5 and 15 minutes. Forced cleanup is used with session stickiness.\n* `com.amadeus.session.timers.in-memory-cleanup` measures the histogram (distribution) of elapsed time during expiration cleanup of sessions stored in memory as well as the total number of  expiration cleanup invocations and the rate over the last 1, 5 and 15 minutes.\n\n#### Thread pool monitoring\n\nFor thread pools of blocking/long running tasks the library exposes following metrics:\n\n* `com.amadeus.session.threads.active`: Number of running tasks.\n* `com.amadeus.session.threads.largest`: The largest recorded size of pool.\n* `com.amadeus.session.threads.pool`: Current size of pool.\n* `com.amadeus.session.threads.waiting`: Number of tasks waiting in queue.\n\nFor thread pools of scheduled tasks the library exposes following metrics:\n\n* `com.amadeus.session.scheduled-threads.active`: Number of running tasks.\n* `com.amadeus.session.scheduled-threads.largest`: The largest recorded size of pool.\n* `com.amadeus.session.scheduled-threads.pool`: Current size of pool.\n* `com.amadeus.session.scheduled-threads.waiting`: Number of tasks waiting in the queue.\n* `com.amadeus.session.scheduled-threads.tasks`: Approximate total number of tasks that have been scheduled.\n\n#### Redis monitoring\n\nIn the following metrics, HOST corresponds to Redis host.\n\n* `com.amadeus.session.redis.HOST.active`: Number of active Redis connections.\n* `com.amadeus.session.redis.HOST.idle`: Number of idle Redis connections.\n* `com.amadeus.session.redis.HOST.waiting`: Number of Redis requests waiting for connection.\n* `com.amadeus.session.redis.failover`: When using sticky sessions, number of failovers (session retrieval from different node) that occurred.\n\n## Classpath and dependency notes\n\nThe project depends on following external projects\n\n* [ASM](http://asm.ow2.org/)\n* [Jedis](https://github.com/xetorthio/jedis)\n* [Codahale Metrics](http://metrics.dropwizard.io)\n* [slf4j](http://www.slf4j.org)\n\nDuring build, the dependencies are 'shaded' using the maven shade plugin. The packages are moved\ninto `com.amadeus.session.shaded` hierarchy and as such are not visible under their default\npackages.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Famadeusitgroup%2Fhttpsessionreplacer","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Famadeusitgroup%2Fhttpsessionreplacer","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Famadeusitgroup%2Fhttpsessionreplacer/lists"}