2.1. Configuring Remote Data Grid Mode
Before You Start
This chapter covers changing an application so that it stores sessions in the data grid instead of in WAS memory. Of the three topologies described in Part 1, the instructions assume the data grid only topology.
Application code is not modified. Code that uses HttpSession stays as it is; you insert a single servlet filter that changes only where sessions are read and written.
Prerequisites
| What | What to check |
|---|---|
| IMDG server | An In Memory Data Grid server for storing sessions is running and reachable on the HotRod port (11222 by default). Two or more servers with two instances each are recommended — see "How Many Data Grid Nodes to Deploy" in Part 1. It can be installed with OPENMARU Installer |
| Application | Runs on a WAS supporting Servlet 2.5 or later (WebLogic, JEUS, Tomcat, JBoss EAP, and others) |
| Objects placed in the session | Must be serializable (Serializable). See "Objects Placed in the Session Must Be Serializable" below |
| Existing filter list | Check the filters already registered in the application. This filter must be placed ahead of them — see step 3 |
Overall Procedure
| Step | What you do | What you edit |
|---|---|---|
| 1 | Add the libraries to the application | pom.xml |
| 2 | Tell it the address of the session store (the IMDG server) | hotrod.properties |
| 3 | Register the filter that intercepts sessions | web.xml or Java configuration |
| 4 | Confirm that sessions go to the store | Run the application |
Step 1. Add the Libraries
Add the libraries so that the application can use the session filter and the HotRod client.
OPENMARU Cluster is not published to a public repository. Your engineer will provide the library files. Depending on how you received them, use one of the two methods below.
Placing the Library Files Directly
Put the .jar files you received into the application's WEB-INF/lib directory.
myapp.war
└── WEB-INF/
├── lib/
│ ├── khan-session-core-5.1.0.jar ← file you received
│ ├── khan-session-hotrod-5.1.0.jar ← file you received
│ └── (the remaining files you received)
└── web.xml
Include every file you received, without omitting any. Along with the product libraries, the libraries they depend on are provided as well. If even one is missing, the application fails during startup with ClassNotFoundException or NoClassDefFoundError.
If you run several applications on the same WAS, you can either place the files in each application's WEB-INF/lib or put them once in the WAS's shared library directory. The location of the shared directory differs by WAS product, so check that product's documentation.
Using Maven or Gradle
If the libraries are registered in an internal repository (Nexus, Artifactory, and so on), you can declare a dependency instead. Ask your engineer whether they are registered.
<dependency>
<groupId>com.opennaru.khan</groupId>
<artifactId>khan-session-hotrod</artifactId>
<version>5.1.0</version>
</dependency>
Confirming They Were Added Correctly
Whichever method you used, the following class must be visible to the application. You will use this name in step 3.
com.opennaru.khan.session.filter.InfinispanHotRodSessionFilter
Step 2. Configure the Session Store Address
The filter needs to know which IMDG server to store sessions on. Create a hotrod.properties file and write the server IP and port in it. Place this file on the application classpath.
infinispan.client.hotrod.server_list = 192.168.0.11:11222
If there are several servers, list them separated by commas. The client distributes its connections across the servers in this list.
infinispan.client.hotrod.server_list = 192.168.0.11:11222,192.168.0.12:11222
The name of this file is passed to the filter through the configFile setting in step 3.
This file can also contain entries such as connection pool size and serialization method. In most environments, listing the servers is enough and the defaults cover the rest. If you need to adjust them, contact your engineer.
Installing and operating the IMDG server itself is outside the scope of this document. It can be installed automatically with OPENMARU Installer.
Step 3. Register the Filter
Register the filter that intercepts sessions in the application. This is also where you decide behavior such as the session store location, cookie name, and timeout. The meaning of each setting is in the "Settings" table below.
Read only the section that applies to you — XML configuration or Java configuration.
This Filter Must Run First
Of all the filters registered in the application, this one must be first.
This filter creates a new request object that wraps the incoming request and passes it to the next filter. That is what makes the following filters and the application receive the data grid session — not the WAS memory session — when they call request.getSession().
If another filter comes first, that filter sees the original, unwrapped request. If that filter touches the session, the WAS creates its own session first, and from then on sessions exist separately in two places. On the surface it works without errors, but restarting the WAS logs users out, and sessions do not carry over when the instance changes.
The following filters in particular touch the session and must be placed after this one.
- Authentication and authorization filters (Spring Security and similar)
- Character encoding filters that put values into the session
- Logging and audit filters that record the session ID or the logged-in user
- Custom login check filters
Using web.xml
Put the settings inside <filter> as <init-param> entries, and apply the filter to all requests (/*) with <filter-mapping>. To detect sessions being created and destroyed, register the <listener> as well.
The servlet specification runs filters in the order their <filter-mapping> entries appear. So this filter's <filter-mapping> must be placed above every other <filter-mapping> in web.xml. The position of the <filter> declaration does not affect ordering — <filter-mapping> is what determines it.
The one setting you must provide is configFile. It is the name of the store connection file you created in step 2; without it, startup fails with an error.
The rest use defaults if omitted. Check the values of these three, however.
| Setting | Why to check it |
|---|---|
sessionTimeout | The default is 10 minutes. If it differs from your existing application's session timeout, users are logged out sooner than expected |
excludeRegExp | There is no default. If you omit it, sessions are created for image and CSS requests as well, increasing load on the store |
allowDuplicateLogin | The default is false, so duplicate login prevention is on from the start. Set true if you need to allow it |
If infinispanCache and infinispanLoginCache are omitted, KHAN_SESSION and KHAN_SESSION_LOGIN are used respectively. The example below also shows how to specify different names in ${environment-variable:default} form.
The following example includes the settings that are used most often.
<?xml version="1.0" encoding="UTF-8"?>
{/*
~ Opennaru, Inc. http://www.opennaru.com/
~
~ Copyright (C) 2014 Opennaru, Inc. and/or its affiliates.
~ All rights reserved by Opennaru, Inc.
*/}
<web-app
xmlns="http://java.sun.com/xml/ns/javaee"
xmlns:web="http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd"
id="session1" version="2.5">
<display-name>Test</display-name>
<description>Test App</description>
{/* <distributable/> */}
<filter>
<filter-name>KhanSessionFilter</filter-name>
{/* Hotrod Mode */}
<init-param>
<param-name>configFile</param-name>
<param-value>${OPENMARU_CONFIG_FILE:hotrod.properties}</param-value>
</init-param>
<init-param>
<param-name>infinispanCache</param-name>
<param-value>${OPENMARU_INFINISPAN_CACHE:OPENMARU_SESSION}</param-value>
</init-param>
<init-param>
<param-name>infinispanLoginCache</param-name>
<param-value>${OPENMARU_INFINISPAN_LOGIN_CACHE:OPENMARU_SESSION_LOGIN}</param-value>
</init-param>
<init-param>
<param-name>sessionId</param-name>
<param-value>__KSMSID__</param-value>
</init-param>
<init-param>
<param-name>domain</param-name>
<param-value></param-value>
</init-param>
<init-param>
<param-name>path</param-name>
<param-value>/test1</param-value> {/* set to '/' to share sessions between different web applications */}
</init-param>
<init-param>
<param-name>secure</param-name>
<param-value>false</param-value>
</init-param>
<init-param>
<param-name>httpOnly</param-name>
<param-value>true</param-value>
</init-param>
<init-param>
<param-name>sessionTimeout</param-name>
<param-value>30</param-value>
</init-param>
<init-param>
<param-name>excludeRegExp</param-name>
<param-value>/.+\.(html|jpg|jpeg|png|gif|js|css|swf)</param-value>
</init-param>
<init-param>
<param-name>allowDuplicateLogin</param-name> {/* set to true to allow duplicate logins */}
<param-value>false</param-value>
</init-param>
<init-param>
<param-name>duplicateLoginPolicy</param-name> {/* duplicate login policy: none, legacy, custom */}
<param-value>legacy</param-value>
</init-param>
<init-param>
<param-name>invalidateDuplicateLogin</param-name> {/* set to false to skip the internal invalidate call (check for duplicates through the API, then call the invalidate API yourself) */}
<param-value>false</param-value>
</init-param>
<init-param>
<param-name>logoutUrl</param-name> {/* logout URL used on duplicate login */}
<param-value>/logout.jsp</param-value>
</init-param>
<init-param>
<param-name>enableImmediateSave</param-name>
<param-value>true</param-value>
</init-param>
</filter>
<filter-mapping>
<filter-name>KhanSessionFilter</filter-name>
<url-pattern>/*</url-pattern>
<dispatcher>ERROR</dispatcher>
<dispatcher>INCLUDE</dispatcher>
<dispatcher>FORWARD</dispatcher>
<dispatcher>REQUEST</dispatcher>
</filter-mapping>
<listener>
<listener-class>com.opennaru.khan.session.listener.SessionListener</listener-class>
</listener>
Using Java Configuration, as in Spring Boot
In environments that do not use web.xml, apply the same settings in Java code. Register the filter with FilterRegistrationBean and pass each setting through addInitParameter.
Be sure to include setOrder(Integer.MIN_VALUE). This is how the "must run first" rule described above is guaranteed in Java configuration. Lower values run earlier, so give it the lowest value.
Take particular care when using Spring Security. Its filter chain has a default order of -100, so unless you specify an order it runs before this filter.
@Configuration
public class OpenmaruFilterConfiguration implements WebMvcConfigurer {
@Bean
public FilterRegistrationBean getFilterRegistrationBean() {
FilterRegistrationBean registrationBean = new FilterRegistrationBean(new InfinispanHotRodSessionFilter());
registrationBean.setOrder(Integer.MIN_VALUE);
registrationBean.addInitParameter(Constants.INFINISPAN_CONFIGFILE_KEY, "hotrod.properties");
registrationBean.addInitParameter(Constants.INFINISPAN_CACHE_KEY, "KHAN_SESSION");
registrationBean.addInitParameter(Constants.INFINISPAN_LOGIN_CACHE_KEY, "KHAN_SESSION_LOGIN");
registrationBean.addInitParameter(Constants.SESSION_ID, "__KSMSID__");
registrationBean.addInitParameter(Constants.DOMAIN, "");
registrationBean.addInitParameter(Constants.PATH, "/");
registrationBean.addInitParameter(Constants.SECURE, "false");
registrationBean.addInitParameter(Constants.HTTP_ONLY, "false");
registrationBean.addInitParameter(Constants.SESSION_TIMEOUT, "30"); // minute
registrationBean.addInitParameter(Constants.SESSION_SAVE_DELAY, "5");
registrationBean.addInitParameter(Constants.EXCLUDE_REG_EXP, "/.+\\.(html|jpg|jpeg|png|gif|js|css|swf)");
registrationBean.addInitParameter(Constants.ALLOW_DUPLICATE_LOGIN, "true");
registrationBean.addInitParameter(Constants.DUPLICATE_LOGIN_POLICY, "legacy"); // none, legacy, custom
registrationBean.addInitParameter(Constants.DUPLICATE_LOGIN_EXCLUSTION_TYPE, "");
registrationBean.addInitParameter(Constants.INVALIDATE_DUPLICATE_LOGIN, "true");
registrationBean.addInitParameter(Constants.LOGOUT_URL, "");
registrationBean.addInitParameter(Constants.ENABLE_IMMEDIATED_SAVE, "true");
registrationBean.addInitParameter(Constants.ENABLE_STATISTICS, "true");
registrationBean.addInitParameter(Constants.ENABLE_MEMORY_STATISTICS, "false");
registrationBean.addInitParameter(Constants.LICENSE_KEY,
"#### LICENSE KEY ###\n" +
"> REQUEST sales@openmaru.io"
);
registrationBean.setUrlPatterns(Arrays.asList("/*"));
registrationBean.setDispatcherTypes(DispatcherType.ERROR, DispatcherType.INCLUDE, DispatcherType.FORWARD, DispatcherType.REQUEST);
return registrationBean;
}
@Bean
public HttpSessionListener httpSessionListener() {
SessionListener sessionListener = new SessionListener();
System.out.println("SessionListener started.");
return sessionListener;
}
}
Step 4. Verify That It Works
Once the three steps are done, start the application and check that sessions really go to the store.
1. Look at the cookies in the browser. After logging in, the cookie list in the developer tools must show both JSESSIONID and __KSMSID__.
JSESSIONID is the cookie the WAS issues on its own, so it remains. The filter does not remove it; it issues one additional cookie, __KSMSID__. __KSMSID__ is what is used to find the session in the data grid.
In other words, the criterion is whether __KSMSID__ appears in addition. If only JSESSIONID is present and __KSMSID__ is not, the filter did not take effect. The cookie name can be changed with the sessionId setting.
2. Restart the WAS. While logged in, restart the WAS instance and refresh the page. If you are still logged in, the session is stored outside the WAS. If you are sent back to the login screen after the restart, either the filter was not applied or it did not connect to the store.
3. Run two or more instances. After logging in on one instance, the login state must be preserved when a request goes to another instance.
If it does not work, check the following.
| Symptom | What to check |
|---|---|
The __KSMSID__ cookie is not present, only JSESSIONID | The filter was not registered, or it did not match the request. Check that url-pattern in <filter-mapping> is /*, or, for Java configuration, that the filter order is first |
| Store connection error during startup | Check the path of hotrod.properties from step 2 and the IP and port in server_list. That file must be on the classpath |
| Error when putting a value into the session | Check whether the object you placed implements Serializable — see below |
| Login is lost after a restart | The filter works but nothing is being saved to the store. Check the store connection and the cache name (infinispanCache) |
__KSMSID__ is issued but login is lost after a restart | Another filter is most likely running first. For web.xml, check that this filter's <filter-mapping> is at the top; for Java configuration, check that setOrder(Integer.MIN_VALUE) is present |