Allow persistent servlet sessions across restarts
Update Tomcat, Jetty and Undertow to serialize session data when the application is stopped and load it again when the application restarts. Persistent session are opt-in; either by setting `persistentSession` on the ConfigurableEmbeddedServletContainer or by using the property `server.session.persistent=true`. Fixes gh-2490pull/3531/head
parent
3e72300bd4
commit
08d1f6daf5
@ -0,0 +1,156 @@
|
||||
/*
|
||||
* Copyright 2012-2015 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.boot.context.embedded.undertow;
|
||||
|
||||
import io.undertow.servlet.UndertowServletLogger;
|
||||
import io.undertow.servlet.api.SessionPersistenceManager;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.ObjectInputStream;
|
||||
import java.io.ObjectOutputStream;
|
||||
import java.io.Serializable;
|
||||
import java.util.Date;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* {@link SessionPersistenceManager} that stores session information in a file.
|
||||
*
|
||||
* @author Phillip Webb
|
||||
* @since 1.3.0
|
||||
*/
|
||||
public class FileSessionPersistence implements SessionPersistenceManager {
|
||||
|
||||
private final File folder;
|
||||
|
||||
public FileSessionPersistence(File folder) {
|
||||
this.folder = folder;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void persistSessions(String deploymentName,
|
||||
Map<String, PersistentSession> sessionData) {
|
||||
try {
|
||||
save(sessionData, getSessionFile(deploymentName));
|
||||
}
|
||||
catch (Exception ex) {
|
||||
UndertowServletLogger.ROOT_LOGGER.failedToPersistSessions(ex);
|
||||
}
|
||||
}
|
||||
|
||||
private void save(Map<String, PersistentSession> sessionData, File file)
|
||||
throws IOException {
|
||||
ObjectOutputStream stream = new ObjectOutputStream(new FileOutputStream(file));
|
||||
try {
|
||||
save(sessionData, stream);
|
||||
}
|
||||
finally {
|
||||
stream.close();
|
||||
}
|
||||
}
|
||||
|
||||
private void save(Map<String, PersistentSession> sessionData,
|
||||
ObjectOutputStream stream) throws IOException {
|
||||
Map<String, Serializable> session = new LinkedHashMap<String, Serializable>();
|
||||
for (Map.Entry<String, PersistentSession> entry : sessionData.entrySet()) {
|
||||
session.put(entry.getKey(),
|
||||
new SerializablePersistentSession(entry.getValue()));
|
||||
}
|
||||
stream.writeObject(session);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<String, PersistentSession> loadSessionAttributes(String deploymentName,
|
||||
final ClassLoader classLoader) {
|
||||
try {
|
||||
File file = getSessionFile(deploymentName);
|
||||
if (file.exists()) {
|
||||
return load(file);
|
||||
}
|
||||
}
|
||||
catch (Exception ex) {
|
||||
UndertowServletLogger.ROOT_LOGGER.failedtoLoadPersistentSessions(ex);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private Map<String, PersistentSession> load(File file) throws IOException,
|
||||
ClassNotFoundException {
|
||||
ObjectInputStream stream = new ObjectInputStream(new FileInputStream(file));
|
||||
try {
|
||||
return load(stream);
|
||||
}
|
||||
finally {
|
||||
stream.close();
|
||||
}
|
||||
}
|
||||
|
||||
private Map<String, PersistentSession> load(ObjectInputStream stream)
|
||||
throws ClassNotFoundException, IOException {
|
||||
Map<String, SerializablePersistentSession> session = readSession(stream);
|
||||
long time = System.currentTimeMillis();
|
||||
Map<String, PersistentSession> result = new LinkedHashMap<String, PersistentSession>();
|
||||
for (Map.Entry<String, SerializablePersistentSession> entry : session.entrySet()) {
|
||||
PersistentSession entrySession = entry.getValue().getPersistentSession();
|
||||
if (entrySession.getExpiration().getTime() > time) {
|
||||
result.put(entry.getKey(), entrySession);
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
private Map<String, SerializablePersistentSession> readSession(
|
||||
ObjectInputStream stream) throws ClassNotFoundException, IOException {
|
||||
return ((Map<String, SerializablePersistentSession>) stream.readObject());
|
||||
}
|
||||
|
||||
private File getSessionFile(String deploymentName) {
|
||||
if (!this.folder.exists()) {
|
||||
this.folder.mkdirs();
|
||||
}
|
||||
return new File(this.folder, deploymentName + ".session");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void clear(String deploymentName) {
|
||||
getSessionFile(deploymentName).delete();
|
||||
}
|
||||
|
||||
static class SerializablePersistentSession implements Serializable {
|
||||
|
||||
private static final long serialVersionUID = 0L;
|
||||
|
||||
private final Date expiration;
|
||||
|
||||
private final Map<String, Object> sessionData;
|
||||
|
||||
public SerializablePersistentSession(PersistentSession session) {
|
||||
this.expiration = session.getExpiration();
|
||||
this.sessionData = new LinkedHashMap<String, Object>(session.getSessionData());
|
||||
}
|
||||
|
||||
public PersistentSession getPersistentSession() {
|
||||
return new PersistentSession(this.expiration, this.sessionData);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,109 @@
|
||||
/*
|
||||
* Copyright 2012-2015 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.boot.context.embedded.undertow;
|
||||
|
||||
import io.undertow.servlet.api.SessionPersistenceManager.PersistentSession;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.util.Date;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import org.junit.Before;
|
||||
import org.junit.Rule;
|
||||
import org.junit.Test;
|
||||
import org.junit.rules.TemporaryFolder;
|
||||
|
||||
import static org.hamcrest.Matchers.equalTo;
|
||||
import static org.hamcrest.Matchers.notNullValue;
|
||||
import static org.hamcrest.Matchers.nullValue;
|
||||
import static org.junit.Assert.assertThat;
|
||||
|
||||
/**
|
||||
* Tests for {@link FileSessionPersistence}.
|
||||
*
|
||||
* @author Phillip Webb
|
||||
*/
|
||||
public class FileSessionPersistenceTests {
|
||||
|
||||
@Rule
|
||||
public TemporaryFolder temp = new TemporaryFolder();
|
||||
|
||||
private File folder;
|
||||
|
||||
private FileSessionPersistence persistence;
|
||||
|
||||
private ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
|
||||
|
||||
private Date expiration = new Date(System.currentTimeMillis() + 10000);
|
||||
|
||||
@Before
|
||||
public void setup() throws IOException {
|
||||
this.folder = this.temp.newFolder();
|
||||
this.persistence = new FileSessionPersistence(this.folder);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void loadsNullForMissingFile() throws Exception {
|
||||
Map<String, PersistentSession> attributes = this.persistence
|
||||
.loadSessionAttributes("test", this.classLoader);
|
||||
assertThat(attributes, nullValue());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void presistAndLoad() throws Exception {
|
||||
Map<String, PersistentSession> sessionData = new LinkedHashMap<String, PersistentSession>();
|
||||
Map<String, Object> data = new LinkedHashMap<String, Object>();
|
||||
data.put("spring", "boot");
|
||||
PersistentSession session = new PersistentSession(this.expiration, data);
|
||||
sessionData.put("abc", session);
|
||||
this.persistence.persistSessions("test", sessionData);
|
||||
Map<String, PersistentSession> restored = this.persistence.loadSessionAttributes(
|
||||
"test", this.classLoader);
|
||||
assertThat(restored, notNullValue());
|
||||
assertThat(restored.get("abc").getExpiration(), equalTo(this.expiration));
|
||||
assertThat(restored.get("abc").getSessionData().get("spring"),
|
||||
equalTo((Object) "boot"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void dontRestoreExpired() throws Exception {
|
||||
Date expired = new Date(System.currentTimeMillis() - 1000);
|
||||
Map<String, PersistentSession> sessionData = new LinkedHashMap<String, PersistentSession>();
|
||||
Map<String, Object> data = new LinkedHashMap<String, Object>();
|
||||
data.put("spring", "boot");
|
||||
PersistentSession session = new PersistentSession(expired, data);
|
||||
sessionData.put("abc", session);
|
||||
this.persistence.persistSessions("test", sessionData);
|
||||
Map<String, PersistentSession> restored = this.persistence.loadSessionAttributes(
|
||||
"test", this.classLoader);
|
||||
assertThat(restored, notNullValue());
|
||||
assertThat(restored.containsKey("abc"), equalTo(false));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void deleteFileOnClear() throws Exception {
|
||||
File sessionFile = new File(this.folder, "test.session");
|
||||
Map<String, PersistentSession> sessionData = new LinkedHashMap<String, PersistentSession>();
|
||||
this.persistence.persistSessions("test", sessionData);
|
||||
assertThat(sessionFile.exists(), equalTo(true));
|
||||
this.persistence.clear("test");
|
||||
assertThat(sessionFile.exists(), equalTo(false));
|
||||
}
|
||||
|
||||
}
|
Loading…
Reference in New Issue