Merge branch 'jetty-9.4.x' of github.com:eclipse/jetty.project into jetty-9.4.x

This commit is contained in:
Greg Wilkins 2018-11-23 17:01:39 +01:00
commit 97cb063a57
7 changed files with 652 additions and 318 deletions

View File

@ -601,6 +601,7 @@
<excludes> <excludes>
<exclude>**/TestJettyOSGiBootHTTP2</exclude> <exclude>**/TestJettyOSGiBootHTTP2</exclude>
</excludes> </excludes>
<argLine>-Dconscrypt-version=${conscrypt.version}</argLine>
</configuration> </configuration>
</plugin> </plugin>
</plugins> </plugins>

View File

@ -100,9 +100,9 @@ public class TestJettyOSGiBootHTTP2Conscrypt
res.add(CoreOptions.systemProperty("jetty.alpn.protocols").value("h2,http/1.1")); res.add(CoreOptions.systemProperty("jetty.alpn.protocols").value("h2,http/1.1"));
res.add(CoreOptions.systemProperty("jetty.sslContext.provider").value("Conscrypt")); res.add(CoreOptions.systemProperty("jetty.sslContext.provider").value("Conscrypt"));
res.add(wrappedBundle(mavenBundle().groupId("org.conscrypt").artifactId("conscrypt-openjdk-uber").version("1.1.4")) res.add(wrappedBundle(mavenBundle().groupId("org.conscrypt").artifactId("conscrypt-openjdk-uber").versionAsInProject())
.imports("javax.net.ssl,*") .imports("javax.net.ssl,*")
.exports("org.conscrypt;version=1.1.4") .exports("org.conscrypt;version="+System.getProperty("conscrypt-version"))
.instructions("Bundle-NativeCode=META-INF/native/libconscrypt_openjdk_jni-linux-x86_64.so") .instructions("Bundle-NativeCode=META-INF/native/libconscrypt_openjdk_jni-linux-x86_64.so")
.start()); .start());
res.add(mavenBundle().groupId("org.eclipse.jetty.osgi").artifactId("jetty-osgi-alpn").versionAsInProject().noStart()); res.add(mavenBundle().groupId("org.eclipse.jetty.osgi").artifactId("jetty-osgi-alpn").versionAsInProject().noStart());

View File

@ -151,8 +151,9 @@ public abstract class AbstractSessionCache extends ContainerLifeCycle implements
/** /**
* Remove the session with this identity from the store * Remove the session with this identity from the store
*
* @param id the id * @param id the id
* @return true if removed false otherwise * @return Session that was removed or null
*/ */
public abstract Session doDelete (String id); public abstract Session doDelete (String id);
@ -727,12 +728,8 @@ public abstract class AbstractSessionCache extends ContainerLifeCycle implements
/**
* @see org.eclipse.jetty.server.session.SessionCache#renewSessionId(java.lang.String, java.lang.String)
*/
@Override @Override
public Session renewSessionId (String oldId, String newId) public Session renewSessionId (String oldId, String newId, String oldExtendedId, String newExtendedId)
throws Exception throws Exception
{ {
if (StringUtil.isBlank(oldId)) if (StringUtil.isBlank(oldId))
@ -741,17 +738,40 @@ public abstract class AbstractSessionCache extends ContainerLifeCycle implements
throw new IllegalArgumentException ("New session id is null"); throw new IllegalArgumentException ("New session id is null");
Session session = get(oldId); Session session = get(oldId);
if (session == null) renewSessionId(session, newId, newExtendedId);
return null;
return session;
}
/**
* Swap the id on a session.
*
* @param session the session for which to do the swap
* @param newId the new id
* @param newExtendedId the full id plus node id
*
* @throws Exception if there was a failure saving the change
*/
protected void renewSessionId (Session session, String newId, String newExtendedId)
throws Exception
{
if (session == null)
return;
try (Lock lock = session.lock()) try (Lock lock = session.lock())
{ {
String oldId = session.getId();
session.checkValidForWrite(); //can't change id on invalid session session.checkValidForWrite(); //can't change id on invalid session
session.getSessionData().setId(newId); session.getSessionData().setId(newId);
session.getSessionData().setLastSaved(0); //pretend that the session has never been saved before to get a full save session.getSessionData().setLastSaved(0); //pretend that the session has never been saved before to get a full save
session.getSessionData().setDirty(true); //ensure we will try to write the session out session.getSessionData().setDirty(true); //ensure we will try to write the session out
session.setExtendedId(newExtendedId); //remember the new extended id
session.setIdChanged(true); //session id changed
doPutIfAbsent(newId, session); //put the new id into our map doPutIfAbsent(newId, session); //put the new id into our map
doDelete (oldId); //take old out of map doDelete (oldId); //take old out of map
if (_sessionDataStore != null) if (_sessionDataStore != null)
{ {
_sessionDataStore.delete(oldId); //delete the session data with the old id _sessionDataStore.delete(oldId); //delete the session data with the old id
@ -759,7 +779,6 @@ public abstract class AbstractSessionCache extends ContainerLifeCycle implements
} }
if (LOG.isDebugEnabled()) if (LOG.isDebugEnabled())
LOG.debug ("Session id {} swapped for new id {}", oldId, newId); LOG.debug ("Session id {} swapped for new id {}", oldId, newId);
return session;
} }
} }

View File

@ -91,16 +91,41 @@ public interface SessionCache extends LifeCycle
*/ */
Session newSession (SessionData data); Session newSession (SessionData data);
/**
* Change the id of a session.
*
* This method has been superceded by the 4 arg renewSessionId method and
* should no longer be called.
*
* @param oldId the old id
* @param newId the new id
* @return the changed Session
* @throws Exception if anything went wrong
* @deprecated use
* {@link #renewSessionId(String oldId, String newId, String oldExtendedId, String newExtendedId)}
*/
@Deprecated
default Session renewSessionId(String oldId, String newId) throws Exception
{
return null;
}
/** /**
* Change the id of a Session. * Change the id of a Session.
* *
* @param oldId the current session id * @param oldId the current session id
* @param newId the new session id * @param newId the new session id
* @param oldExtendedId the current extended session id
* @param newExtendedId the new extended session id
* @return the Session after changing its id * @return the Session after changing its id
* @throws Exception if any error occurred * @throws Exception if any error occurred
*/ */
Session renewSessionId (String oldId, String newId) throws Exception; default Session renewSessionId(String oldId, String newId, String oldExtendedId, String newExtendedId) throws Exception
{
return renewSessionId(oldId, newId);
}
/** /**

View File

@ -18,6 +18,8 @@
package org.eclipse.jetty.server.session; package org.eclipse.jetty.server.session;
import static java.lang.Math.round;
import java.io.IOException; import java.io.IOException;
import java.util.Arrays; import java.util.Arrays;
import java.util.Collections; import java.util.Collections;
@ -65,8 +67,6 @@ import org.eclipse.jetty.util.thread.Locker.Lock;
import org.eclipse.jetty.util.thread.ScheduledExecutorScheduler; import org.eclipse.jetty.util.thread.ScheduledExecutorScheduler;
import org.eclipse.jetty.util.thread.Scheduler; import org.eclipse.jetty.util.thread.Scheduler;
import static java.lang.Math.round;
/* ------------------------------------------------------------ */ /* ------------------------------------------------------------ */
/** /**
* SessionHandler. * SessionHandler.
@ -343,6 +343,60 @@ public class SessionHandler extends ScopedHandler
_sessionListeners.clear(); _sessionListeners.clear();
_sessionIdListeners.clear(); _sessionIdListeners.clear();
} }
/**
* Call the session lifecycle listeners
* @param session the session on which to call the lifecycle listeners
*/
protected void callSessionDestroyedListeners (Session session)
{
if (session == null)
return;
if (_sessionListeners!=null)
{
HttpSessionEvent event=new HttpSessionEvent(session);
for (int i = _sessionListeners.size()-1; i>=0; i--)
{
_sessionListeners.get(i).sessionDestroyed(event);
}
}
}
/**
* Call the session lifecycle listeners
* @param session the session on which to call the lifecycle listeners
*/
protected void callSessionCreatedListeners (Session session)
{
if (session == null)
return;
if (_sessionListeners!=null)
{
HttpSessionEvent event=new HttpSessionEvent(session);
for (int i = _sessionListeners.size()-1; i>=0; i--)
{
_sessionListeners.get(i).sessionCreated(event);
}
}
}
protected void callSessionIdListeners (Session session, String oldId)
{
//inform the listeners
if (!_sessionIdListeners.isEmpty())
{
HttpSessionEvent event = new HttpSessionEvent(session);
for (HttpSessionIdListener l:_sessionIdListeners)
{
l.sessionIdChanged(event, oldId);
}
}
}
/* ------------------------------------------------------------ */ /* ------------------------------------------------------------ */
/** /**
@ -793,15 +847,10 @@ public class SessionHandler extends ScopedHandler
_sessionCache.put(id, session); _sessionCache.put(id, session);
_sessionsCreatedStats.increment(); _sessionsCreatedStats.increment();
if (request.isSecure()) if (request!=null && request.isSecure())
session.setAttribute(Session.SESSION_CREATED_SECURE, Boolean.TRUE); session.setAttribute(Session.SESSION_CREATED_SECURE, Boolean.TRUE);
if (_sessionListeners!=null) callSessionCreatedListeners(session);
{
HttpSessionEvent event=new HttpSessionEvent(session);
for (HttpSessionListener listener : _sessionListeners)
listener.sessionCreated(event);
}
return session; return session;
} }
@ -1183,24 +1232,15 @@ public class SessionHandler extends ScopedHandler
{ {
try try
{ {
Session session = _sessionCache.renewSessionId (oldId, newId); //swap the id over Session session = _sessionCache.renewSessionId (oldId, newId, oldExtendedId, newExtendedId); //swap the id over
if (session == null) if (session == null)
{ {
//session doesn't exist on this context //session doesn't exist on this context
return; return;
} }
session.setExtendedId(newExtendedId); //remember the extended id
//inform the listeners //inform the listeners
if (!_sessionIdListeners.isEmpty()) callSessionIdListeners(session, oldId);
{
HttpSessionEvent event = new HttpSessionEvent(session);
for (HttpSessionIdListener l:_sessionIdListeners)
{
l.sessionIdChanged(event, oldId);
}
}
} }
catch (Exception e) catch (Exception e)
{ {
@ -1208,28 +1248,62 @@ public class SessionHandler extends ScopedHandler
} }
} }
/**
* Record length of time session has been active. Called when the
* session is about to be invalidated.
*
* @param session the session whose time to record
*/
protected void recordSessionTime (Session session)
{
_sessionTimeStats.record(round((System.currentTimeMillis() - session.getSessionData().getCreated())/1000.0));
}
/* ------------------------------------------------------------ */ /* ------------------------------------------------------------ */
/** /**
* Called when a session has expired. * Called by SessionIdManager to remove a session that has been invalidated,
* either by this context or another context. Also called by
* SessionIdManager when a session has expired in either this context or
* another context.
* *
* @param id the id to invalidate * @param id the session id to invalidate
*/ */
public void invalidate (String id) public void invalidate (String id)
{ {
if (StringUtil.isBlank(id)) if (StringUtil.isBlank(id))
return; return;
try try
{ {
//remove the session and call the destroy listeners // Remove the Session object from the session cache and any backing
Session session = removeSession(id, true); // data store
Session session = _sessionCache.delete(id);
if (session != null) if (session != null)
{ {
_sessionTimeStats.record(round((System.currentTimeMillis() - session.getSessionData().getCreated())/1000.0)); //start invalidating if it is not already begun, and call the listeners
session.finishInvalidate(); try
{
if (session.beginInvalidate())
{
try
{
callSessionDestroyedListeners(session);
}
catch (Exception e)
{
LOG.warn("Session listener threw exception", e);
}
//call the attribute removed listeners and finally mark it as invalid
session.finishInvalidate();
}
}
catch (IllegalStateException e)
{
if (LOG.isDebugEnabled()) LOG.debug("Session {} already invalid", session);
LOG.ignore(e);
}
} }
} }
catch (Exception e) catch (Exception e)

View File

@ -34,7 +34,9 @@ import java.util.concurrent.TimeUnit;
import javax.servlet.http.HttpSessionActivationListener; import javax.servlet.http.HttpSessionActivationListener;
import javax.servlet.http.HttpSessionEvent; import javax.servlet.http.HttpSessionEvent;
import org.eclipse.jetty.server.Request;
import org.eclipse.jetty.server.Server; import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.server.SessionIdManager;
import org.eclipse.jetty.servlet.ServletContextHandler; import org.eclipse.jetty.servlet.ServletContextHandler;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
@ -65,6 +67,100 @@ public class DefaultSessionCacheTest
} }
@Test
public void testRenewIdMultipleRequests() throws Exception
{
//Test that invalidation happens on ALL copies of the session that are in-use by requests
Server server = new Server();
SessionIdManager sessionIdManager = new DefaultSessionIdManager(server);
server.setSessionIdManager(sessionIdManager);
ServletContextHandler context = new ServletContextHandler(ServletContextHandler.SESSIONS);
context.setContextPath("/test");
context.setServer(server);
context.getSessionHandler().setMaxInactiveInterval((int)TimeUnit.DAYS.toSeconds(1));
context.getSessionHandler().setSessionIdManager(sessionIdManager);
DefaultSessionCacheFactory cacheFactory = new DefaultSessionCacheFactory();
cacheFactory.setSaveOnCreate(true); //ensures that a session is persisted as soon as it is created
DefaultSessionCache cache = (DefaultSessionCache)cacheFactory.getSessionCache(context.getSessionHandler());
TestSessionDataStore store = new TestSessionDataStore();
cache.setSessionDataStore(store);
context.getSessionHandler().setSessionCache(cache);
TestHttpSessionListener listener = new TestHttpSessionListener();
context.getSessionHandler().addEventListener(listener);
server.setHandler(context);
try
{
server.start();
//create a new session
Session s = (Session)context.getSessionHandler().newHttpSession(null);
String id = s.getId();
context.getSessionHandler().access(s, false); //simulate accessing the request
context.getSessionHandler().complete(s); //simulate completing the request
//make 1st request
final Session session = context.getSessionHandler().getSession(id); //get the session again
assertNotNull(session);
context.getSessionHandler().access(session, false); //simulate accessing the request
//make 2nd request
final Session session2 = context.getSessionHandler().getSession(id); //get the session again
context.getSessionHandler().access(session2, false); //simulate accessing the request
assertNotNull(session2);
assertTrue(session == session2);
Thread t2 = new Thread(new Runnable()
{
@Override
public void run()
{
System.err.println("Starting session id renewal");
session2.renewId(new Request(null,null));
System.err.println("Finished session id renewal");
}
});
t2.start();
Thread t = new Thread(new Runnable()
{
@Override
public void run()
{
System.err.println("Starting invalidation");
try{Thread.sleep(1000L);}catch (Exception e) {e.printStackTrace();}
session.invalidate();
System.err.println("Finished invalidation");
}
}
);
t.start();
t.join();
t2.join();
}
finally
{
server.stop();
}
}
/** /**
* Test sessions are saved when shutdown with a store. * Test sessions are saved when shutdown with a store.
@ -180,7 +276,7 @@ public class DefaultSessionCacheTest
cache.put("1234", session); cache.put("1234", session);
assertTrue(cache.contains("1234")); assertTrue(cache.contains("1234"));
cache.renewSessionId("1234", "5678"); cache.renewSessionId("1234", "5678", "1234.foo", "5678.foo");
assertTrue(cache.contains("5678")); assertTrue(cache.contains("5678"));
assertFalse(cache.contains("1234")); assertFalse(cache.contains("1234"));