Commit 922f8b6b authored by Phillip Webb's avatar Phillip Webb

Add `server.session.store-dir` support

Add support for a `server.session.store-dir` property which can be used
to specify where session data source be saved.

Fixes gh-4191
parent 1a764d9c
......@@ -162,6 +162,7 @@ public class ServerProperties
container.setSessionTimeout(getSession().getTimeout());
}
container.setPersistSession(getSession().isPersistent());
container.setSessionStoreDir(getSession().getStoreDir());
if (getSsl() != null) {
container.setSsl(getSsl());
}
......@@ -392,6 +393,11 @@ public class ServerProperties
*/
private boolean persistent;
/**
* The directory used to store session data.
*/
private File storeDir;
private Cookie cookie = new Cookie();
public Cookie getCookie() {
......@@ -422,6 +428,14 @@ public class ServerProperties
this.persistent = persistent;
}
public File getStoreDir() {
return this.storeDir;
}
public void setStoreDir(File storeDir) {
this.storeDir = storeDir;
}
public static class Cookie {
/**
......
......@@ -16,6 +16,7 @@
package org.springframework.boot.autoconfigure.web;
import java.io.File;
import java.net.InetAddress;
import java.nio.charset.Charset;
import java.util.Collections;
......@@ -384,6 +385,17 @@ public class ServerPropertiesTests {
verify(container).setUseForwardHeaders(true);
}
@Test
public void sessionStoreDir() throws Exception {
Map<String, String> map = new HashMap<String, String>();
map.put("server.session.store-dir", "myfolder");
bindProperties(map);
JettyEmbeddedServletContainerFactory container = spy(
new JettyEmbeddedServletContainerFactory());
this.properties.customize(container);
verify(container).setSessionStoreDir(new File("myfolder"));
}
private void bindProperties(Map<String, String> map) {
new RelaxedDataBinder(this.properties, "server")
.bind(new MutablePropertyValues(map));
......
......@@ -87,6 +87,7 @@ content into your application; rather pick only the properties that you need.
server.display-name= # the display name of the application
server.use-forward-headers= # if X-Forwarded-* headers should be used (default is off unless running in a known cloud)
server.session.persistent=false # true if session should be saved across restarts
server.session.store-dir= # dir used to save session data
server.session.timeout= # session timeout in seconds
server.session.tracking-modes= # tracking modes (one or more of "cookie" ,"url", "ssl")
server.session.cookie.name= # session cookie name
......
......@@ -3584,7 +3584,7 @@ Spring Session provides support for managing a user's session information. If yo
writing a web application and Spring Session and Spring Data Redis are both on the
classpath, Spring Boot will auto-configure Spring Session through its
`@EnableRedisHttpSession`. Session data will be stored in Redis and the session timeout
can be configured using the `server.session-timeout` property.
can be configured using the `server.session.timeout` property.
......
......@@ -66,6 +66,8 @@ public abstract class AbstractConfigurableEmbeddedServletContainer
private boolean persistSession;
private File sessionStoreDir;
private Ssl ssl;
private JspServlet jspServlet = new JspServlet();
......@@ -191,6 +193,15 @@ public abstract class AbstractConfigurableEmbeddedServletContainer
return this.persistSession;
}
@Override
public void setSessionStoreDir(File sessionStoreDir) {
this.sessionStoreDir = sessionStoreDir;
}
public File getSessionStoreDir() {
return this.sessionStoreDir;
}
@Override
public void setInitializers(List<? extends ServletContextInitializer> initializers) {
Assert.notNull(initializers, "Initializers must not be null");
......
......@@ -26,6 +26,9 @@ import java.util.Arrays;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.boot.ApplicationHome;
import org.springframework.boot.ApplicationTemp;
import org.springframework.util.Assert;
/**
* Abstract base class for {@link EmbeddedServletContainerFactory} implementations.
......@@ -140,4 +143,24 @@ public abstract class AbstractEmbeddedServletContainerFactory
}
}
protected final File getValidSessionStoreDir() {
return getValidSessionStoreDir(true);
}
protected final File getValidSessionStoreDir(boolean mkdirs) {
File dir = getSessionStoreDir();
if (dir == null) {
return new ApplicationTemp().getFolder("servlet-sessions");
}
if (!dir.isAbsolute()) {
dir = new File(new ApplicationHome().getDir(), dir.getPath());
}
if (!dir.exists() && mkdirs) {
dir.mkdirs();
}
Assert.state(!mkdirs || dir.exists(), "Session dir " + dir + " does not exist");
Assert.state(!dir.isFile(), "Session dir " + dir + " points to a file");
return dir;
}
}
......@@ -79,6 +79,12 @@ public interface ConfigurableEmbeddedServletContainer {
*/
void setPersistSession(boolean persistSession);
/**
* Set the directory used to store serialized session data.
* @param sessionStoreDir the directory or {@code null} to use a default location.
*/
void setSessionStoreDir(File sessionStoreDir);
/**
* Sets the specific network address that the server should bind to.
* @param address the address to set (defaults to {@code null})
......
......@@ -53,7 +53,6 @@ import org.eclipse.jetty.util.ssl.SslContextFactory;
import org.eclipse.jetty.webapp.AbstractConfiguration;
import org.eclipse.jetty.webapp.Configuration;
import org.eclipse.jetty.webapp.WebAppContext;
import org.springframework.boot.ApplicationTemp;
import org.springframework.boot.context.embedded.AbstractEmbeddedServletContainerFactory;
import org.springframework.boot.context.embedded.Compression;
import org.springframework.boot.context.embedded.EmbeddedServletContainer;
......@@ -304,8 +303,8 @@ public class JettyEmbeddedServletContainerFactory
private void configurePersistSession(SessionManager sessionManager) {
try {
File storeDirectory = new ApplicationTemp().getFolder("jetty-sessions");
((HashSessionManager) sessionManager).setStoreDirectory(storeDirectory);
((HashSessionManager) sessionManager)
.setStoreDirectory(getValidSessionStoreDir());
}
catch (IOException ex) {
throw new IllegalStateException(ex);
......
......@@ -52,7 +52,6 @@ import org.apache.coyote.ProtocolHandler;
import org.apache.coyote.http11.AbstractHttp11JsseProtocol;
import org.apache.coyote.http11.AbstractHttp11Protocol;
import org.springframework.beans.BeanUtils;
import org.springframework.boot.ApplicationTemp;
import org.springframework.boot.context.embedded.AbstractEmbeddedServletContainerFactory;
import org.springframework.boot.context.embedded.Compression;
import org.springframework.boot.context.embedded.EmbeddedServletContainer;
......@@ -411,7 +410,7 @@ public class TomcatEmbeddedServletContainerFactory
Assert.state(manager instanceof StandardManager,
"Unable to persist HTTP session state using manager type "
+ manager.getClass().getName());
File folder = new ApplicationTemp().getFolder("tomcat-sessions");
File folder = getValidSessionStoreDir();
File file = new File(folder, "SESSIONS.ser");
((StandardManager) manager).setPathname(file.getAbsolutePath());
}
......
......@@ -38,7 +38,6 @@ import javax.servlet.ServletContainerInitializer;
import javax.servlet.ServletContext;
import javax.servlet.ServletException;
import org.springframework.boot.ApplicationTemp;
import org.springframework.boot.context.embedded.AbstractEmbeddedServletContainerFactory;
import org.springframework.boot.context.embedded.EmbeddedServletContainer;
import org.springframework.boot.context.embedded.EmbeddedServletContainerFactory;
......@@ -360,7 +359,7 @@ public class UndertowEmbeddedServletContainerFactory
configureAccessLog(deployment);
}
if (isPersistSession()) {
File folder = new ApplicationTemp().getFolder("undertow-sessions");
File folder = getValidSessionStoreDir();
deployment.setSessionPersistenceManager(new FileSessionPersistence(folder));
}
DeploymentManager manager = Servlets.defaultContainer().addDeployment(deployment);
......
......@@ -19,6 +19,7 @@ package org.springframework.boot.context.embedded;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileWriter;
import java.io.FilenameFilter;
import java.io.IOException;
import java.io.InputStream;
import java.io.PrintWriter;
......@@ -58,6 +59,8 @@ import org.junit.Test;
import org.junit.rules.ExpectedException;
import org.junit.rules.TemporaryFolder;
import org.mockito.InOrder;
import org.springframework.boot.ApplicationHome;
import org.springframework.boot.ApplicationTemp;
import org.springframework.boot.context.embedded.Ssl.ClientAuth;
import org.springframework.http.HttpMethod;
import org.springframework.http.HttpStatus;
......@@ -72,6 +75,7 @@ import org.springframework.util.concurrent.ListenableFuture;
import static org.hamcrest.Matchers.containsString;
import static org.hamcrest.Matchers.equalTo;
import static org.hamcrest.Matchers.greaterThan;
import static org.hamcrest.Matchers.is;
import static org.hamcrest.Matchers.lessThan;
import static org.hamcrest.Matchers.notNullValue;
......@@ -564,6 +568,54 @@ public abstract class AbstractEmbeddedServletContainerFactoryTests {
assertThat(message, s3.split(":")[0], equalTo(s2.split(":")[1]));
}
@Test
public void persistSessionInSpecificSessionStoreDir() throws Exception {
AbstractEmbeddedServletContainerFactory factory = getFactory();
File sessionStoreDir = this.temporaryFolder.newFolder();
factory.setPersistSession(true);
factory.setSessionStoreDir(sessionStoreDir);
this.container = factory
.getEmbeddedServletContainer(sessionServletRegistration());
this.container.start();
getResponse(getLocalUrl("/session"));
this.container.stop();
File[] dirContents = sessionStoreDir.listFiles(new FilenameFilter() {
@Override
public boolean accept(File dir, String name) {
return !(".".equals(name) || "..".equals(name));
}
});
assertThat(dirContents.length, greaterThan(0));
}
@Test
public void getValidSessionStoreWhenSessionStoreNotSet() throws Exception {
AbstractEmbeddedServletContainerFactory factory = getFactory();
File dir = factory.getValidSessionStoreDir(false);
assertThat(dir.getName(), equalTo("servlet-sessions"));
assertThat(dir.getParentFile(), equalTo(new ApplicationTemp().getFolder()));
}
@Test
public void getValidSessionStoreWhenSessionStoreIsRelative() throws Exception {
AbstractEmbeddedServletContainerFactory factory = getFactory();
factory.setSessionStoreDir(new File("sessions"));
File dir = factory.getValidSessionStoreDir(false);
assertThat(dir.getName(), equalTo("sessions"));
assertThat(dir.getParentFile(), equalTo(new ApplicationHome().getDir()));
}
@Test
public void getValidSessionStoreWhenSessionStoreReferencesFile() throws Exception {
AbstractEmbeddedServletContainerFactory factory = getFactory();
factory.setSessionStoreDir(this.temporaryFolder.newFile());
this.thrown.expect(IllegalStateException.class);
this.thrown.expectMessage("points to a file");
factory.getValidSessionStoreDir(false);
}
@Test
public void compression() throws Exception {
assertTrue(doTestCompression(10000, null, null));
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment