// // ======================================================================== // Copyright (c) 1995-2017 Mort Bay Consulting Pty. Ltd. // ------------------------------------------------------------------------ // All rights reserved. This program and the accompanying materials // are made available under the terms of the Eclipse Public License v1.0 // and Apache License v2.0 which accompanies this distribution. // // The Eclipse Public License is available at // http://www.eclipse.org/legal/epl-v10.html // // The Apache License v2.0 is available at // http://www.opensource.org/licenses/apache2.0.php // // You may elect to redistribute this code under either of these licenses. // ======================================================================== // package org.eclipse.jetty.server.session; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNotEquals; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertTrue; import java.io.IOException; import javax.servlet.ServletException; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import javax.servlet.http.HttpSession; import org.eclipse.jetty.client.HttpClient; import org.eclipse.jetty.client.api.ContentResponse; import org.eclipse.jetty.client.api.Request; import org.eclipse.jetty.servlet.ServletContextHandler; import org.eclipse.jetty.servlet.ServletHolder; import org.eclipse.jetty.util.StringUtil; import org.eclipse.jetty.util.log.Log; import org.eclipse.jetty.util.log.StacklessLogging; import org.junit.Test; /** * SaveOptimizeTest * * Test session save optimization. */ public class SaveOptimizeTest { protected TestServlet _servlet; protected TestServer _server1 = null; /** * Create and then invalidate a session in the same request. * Use SessionCache.setSaveOnCreate(true) AND save optimization * and verify the session is actually saved. * @throws Exception */ @Test public void testSessionCreateAndInvalidateWithSave() throws Exception { String contextPath = ""; String servletMapping = "/server"; int inactivePeriod = 20; int scavengePeriod = 3; DefaultSessionCacheFactory cacheFactory = new DefaultSessionCacheFactory(); cacheFactory.setEvictionPolicy(SessionCache.NEVER_EVICT); cacheFactory.setSaveOnCreate(true); TestSessionDataStoreFactory storeFactory = new TestSessionDataStoreFactory(); storeFactory.setSavePeriodSec(10); _server1 = new TestServer(0, inactivePeriod, scavengePeriod, cacheFactory, storeFactory); _servlet = new TestServlet(); ServletHolder holder = new ServletHolder(_servlet); ServletContextHandler contextHandler = _server1.addContext(contextPath); contextHandler.addServlet(holder, servletMapping); _servlet.setStore(contextHandler.getSessionHandler().getSessionCache().getSessionDataStore()); _server1.start(); int port1 = _server1.getPort(); try (StacklessLogging stackless = new StacklessLogging(Log.getLogger("org.eclipse.jetty.server.session"))) { HttpClient client = new HttpClient(); try { client.start(); String url = "http://localhost:" + port1 + contextPath + servletMapping+"?action=create&check=true"; //make a request to set up a session on the server ContentResponse response = client.GET(url); assertEquals(HttpServletResponse.SC_OK,response.getStatus()); } finally { client.stop(); } } finally { _server1.stop(); } } /** * Test that repeated requests to a session where nothing changes does not do * saves. * @throws Exception */ @Test public void testCleanSessionWithinSavePeriod() throws Exception { String contextPath = ""; String servletMapping = "/server"; int inactivePeriod = 600; int scavengePeriod = 30; DefaultSessionCacheFactory cacheFactory = new DefaultSessionCacheFactory(); cacheFactory.setEvictionPolicy(SessionCache.NEVER_EVICT); cacheFactory.setSaveOnCreate(true); TestSessionDataStoreFactory storeFactory = new TestSessionDataStoreFactory(); storeFactory.setSavePeriodSec(300); _server1 = new TestServer(0, inactivePeriod, scavengePeriod, cacheFactory, storeFactory); _servlet = new TestServlet(); ServletHolder holder = new ServletHolder(_servlet); ServletContextHandler contextHandler = _server1.addContext(contextPath); contextHandler.addServlet(holder, servletMapping); _servlet.setStore(contextHandler.getSessionHandler().getSessionCache().getSessionDataStore()); _server1.start(); int port1 = _server1.getPort(); try (StacklessLogging stackless = new StacklessLogging(Log.getLogger("org.eclipse.jetty.server.session"))) { HttpClient client = new HttpClient(); try { client.start(); String url = "http://localhost:" + port1 + contextPath + servletMapping+"?action=create&check=true"; //make a request to set up a session on the server ContentResponse response = client.GET(url); assertEquals(HttpServletResponse.SC_OK,response.getStatus()); String sessionCookie = response.getHeaders().get("Set-Cookie"); assertTrue(sessionCookie != null); String sessionId = TestServer.extractSessionId(sessionCookie); SessionData data = contextHandler.getSessionHandler().getSessionCache().getSessionDataStore().load(sessionId); assertNotNull(data); long firstSaved = data.getLastSaved(); //make a few requests to access the session but not change it for (int i=0;i<5; i++) { sessionCookie = sessionCookie.replaceFirst("(\\W)(P|p)ath=", "$1\\$Path="); // Perform a request to contextB with the same session cookie Request request = client.newRequest("http://localhost:" + port1 + contextPath + servletMapping+"?action=noop"); request.header("Cookie", sessionCookie); response = request.send(); //check session is unchanged SessionData d = contextHandler.getSessionHandler().getSessionCache().getSessionDataStore().load(sessionId); assertNotNull(d); assertEquals(firstSaved, d.getLastSaved()); //slight pause between requests Thread.currentThread().sleep(500); } } finally { client.stop(); } } finally { _server1.stop(); } } /** * Test that a dirty session will always be saved regardless of * save optimisation. * * @throws Exception */ @Test public void testDirtySession() throws Exception { String contextPath = ""; String servletMapping = "/server"; int inactivePeriod = 600; int scavengePeriod = 30; int savePeriod = 5; DefaultSessionCacheFactory cacheFactory = new DefaultSessionCacheFactory(); cacheFactory.setEvictionPolicy(SessionCache.NEVER_EVICT); cacheFactory.setSaveOnCreate(true); TestSessionDataStoreFactory storeFactory = new TestSessionDataStoreFactory(); storeFactory.setSavePeriodSec(savePeriod); _server1 = new TestServer(0, inactivePeriod, scavengePeriod, cacheFactory, storeFactory); _servlet = new TestServlet(); ServletHolder holder = new ServletHolder(_servlet); ServletContextHandler contextHandler = _server1.addContext(contextPath); contextHandler.addServlet(holder, servletMapping); _servlet.setStore(contextHandler.getSessionHandler().getSessionCache().getSessionDataStore()); _server1.start(); int port1 = _server1.getPort(); try (StacklessLogging stackless = new StacklessLogging(Log.getLogger("org.eclipse.jetty.server.session"))) { HttpClient client = new HttpClient(); try { client.start(); String url = "http://localhost:" + port1 + contextPath + servletMapping+"?action=create&check=true"; //make a request to set up a session on the server ContentResponse response = client.GET(url); assertEquals(HttpServletResponse.SC_OK,response.getStatus()); String sessionCookie = response.getHeaders().get("Set-Cookie"); assertTrue(sessionCookie != null); String sessionId = TestServer.extractSessionId(sessionCookie); SessionData data = contextHandler.getSessionHandler().getSessionCache().getSessionDataStore().load(sessionId); assertNotNull(data); long lastSaved = data.getLastSaved(); // Perform a request to do nothing with the same session cookie sessionCookie = sessionCookie.replaceFirst("(\\W)(P|p)ath=", "$1\\$Path="); Request request = client.newRequest("http://localhost:" + port1 + contextPath + servletMapping+"?action=noop"); request.header("Cookie", sessionCookie); response = request.send(); //check session not saved SessionData d = contextHandler.getSessionHandler().getSessionCache().getSessionDataStore().load(sessionId); assertNotNull(d); assertEquals(lastSaved, d.getLastSaved()); // Perform a request to mutate the session request = client.newRequest("http://localhost:" + port1 + contextPath + servletMapping+"?action=mutate"); request.header("Cookie", sessionCookie); response = request.send(); //check session is saved d = contextHandler.getSessionHandler().getSessionCache().getSessionDataStore().load(sessionId); assertNotNull(d); assertTrue(d.getLastSaved() > lastSaved); } finally { client.stop(); } } finally { _server1.stop(); } } /** * Test that if the savePeriod is set, the session will only be saved * after the savePeriod expires (if not dirty). * @throws Exception */ @Test public void testCleanSessionAfterSavePeriod() throws Exception { String contextPath = ""; String servletMapping = "/server"; int inactivePeriod = 600; int scavengePeriod = 30; int savePeriod = 5; DefaultSessionCacheFactory cacheFactory = new DefaultSessionCacheFactory(); cacheFactory.setEvictionPolicy(SessionCache.NEVER_EVICT); cacheFactory.setSaveOnCreate(true); TestSessionDataStoreFactory storeFactory = new TestSessionDataStoreFactory(); storeFactory.setSavePeriodSec(savePeriod); _server1 = new TestServer(0, inactivePeriod, scavengePeriod, cacheFactory, storeFactory); _servlet = new TestServlet(); ServletHolder holder = new ServletHolder(_servlet); ServletContextHandler contextHandler = _server1.addContext(contextPath); contextHandler.addServlet(holder, servletMapping); _servlet.setStore(contextHandler.getSessionHandler().getSessionCache().getSessionDataStore()); _server1.start(); int port1 = _server1.getPort(); try (StacklessLogging stackless = new StacklessLogging(Log.getLogger("org.eclipse.jetty.server.session"))) { HttpClient client = new HttpClient(); try { client.start(); String url = "http://localhost:" + port1 + contextPath + servletMapping+"?action=create&check=true"; //make a request to set up a session on the server ContentResponse response = client.GET(url); assertEquals(HttpServletResponse.SC_OK,response.getStatus()); String sessionCookie = response.getHeaders().get("Set-Cookie"); assertTrue(sessionCookie != null); String sessionId = TestServer.extractSessionId(sessionCookie); SessionData data = contextHandler.getSessionHandler().getSessionCache().getSessionDataStore().load(sessionId); assertNotNull(data); long lastSaved = data.getLastSaved(); //make another request, session should not change sessionCookie = sessionCookie.replaceFirst("(\\W)(P|p)ath=", "$1\\$Path="); // Perform a request to do nothing with the same session cookie Request request = client.newRequest("http://localhost:" + port1 + contextPath + servletMapping+"?action=noop"); request.header("Cookie", sessionCookie); response = request.send(); //check session not saved SessionData d = contextHandler.getSessionHandler().getSessionCache().getSessionDataStore().load(sessionId); assertNotNull(d); assertEquals(lastSaved, d.getLastSaved()); //wait for the savePeriod to pass and then make another request, this should save the session Thread.currentThread().sleep(1000*savePeriod); // Perform a request to do nothing with the same session cookie request = client.newRequest("http://localhost:" + port1 + contextPath + servletMapping+"?action=noop"); request.header("Cookie", sessionCookie); response = request.send(); //check session is saved d = contextHandler.getSessionHandler().getSessionCache().getSessionDataStore().load(sessionId); assertNotNull(d); assertTrue(d.getLastSaved() > lastSaved); } finally { client.stop(); } } finally { _server1.stop(); } } /** * Test that if we turn off caching of the session, then if a savePeriod * is set, the session is still not saved unless the savePeriod expires. * @throws Exception */ @Test public void testNoCacheWithSaveOptimization() throws Exception { String contextPath = ""; String servletMapping = "/server"; int inactivePeriod = -1; int scavengePeriod = -1; int savePeriod = 10; //never cache sessions NullSessionCacheFactory cacheFactory = new NullSessionCacheFactory(); cacheFactory.setSaveOnCreate(true); TestSessionDataStoreFactory storeFactory = new TestSessionDataStoreFactory(); //optimize saves storeFactory.setSavePeriodSec(savePeriod); _server1 = new TestServer(0, inactivePeriod, scavengePeriod, cacheFactory, storeFactory); _servlet = new TestServlet(); ServletHolder holder = new ServletHolder(_servlet); ServletContextHandler contextHandler = _server1.addContext(contextPath); contextHandler.addServlet(holder, servletMapping); _servlet.setStore(contextHandler.getSessionHandler().getSessionCache().getSessionDataStore()); _server1.start(); int port1 = _server1.getPort(); try (StacklessLogging stackless = new StacklessLogging(Log.getLogger("org.eclipse.jetty.server.session"))) { HttpClient client = new HttpClient(); try { client.start(); String url = "http://localhost:" + port1 + contextPath + servletMapping+"?action=create&check=true"; //make a request to set up a session on the server ContentResponse response = client.GET(url); assertEquals(HttpServletResponse.SC_OK,response.getStatus()); String sessionCookie = response.getHeaders().get("Set-Cookie"); assertTrue(sessionCookie != null); String sessionId = TestServer.extractSessionId(sessionCookie); SessionData data = contextHandler.getSessionHandler().getSessionCache().getSessionDataStore().load(sessionId); assertNotNull(data); long lastSaved = data.getLastSaved(); assertTrue(lastSaved > 0); //check session created was saved sessionCookie = sessionCookie.replaceFirst("(\\W)(P|p)ath=", "$1\\$Path="); // Perform a request to do nothing with the same session cookie, check the session object is different Request request = client.newRequest("http://localhost:" + port1 + contextPath + servletMapping+"?action=noop&check=diff"); request.header("Cookie", sessionCookie); response = request.send(); //check session not saved SessionData d = contextHandler.getSessionHandler().getSessionCache().getSessionDataStore().load(sessionId); assertNotNull(d); assertEquals(lastSaved, d.getLastSaved()); } finally { client.stop(); } } finally { _server1.stop(); } } /** * Test changing the maxInactive on a session that is subject to save * optimizations, and check that the session is saved, even if it is * not otherwise dirty. * * @throws Exception */ @Test public void testChangeMaxInactiveWithSaveOptimisation () throws Exception { String contextPath = ""; String servletMapping = "/server"; int inactivePeriod = -1; int scavengePeriod = -1; int savePeriod = 40; //never cache sessions DefaultSessionCacheFactory cacheFactory = new DefaultSessionCacheFactory(); cacheFactory.setSaveOnCreate(true); TestSessionDataStoreFactory storeFactory = new TestSessionDataStoreFactory(); //optimize saves storeFactory.setSavePeriodSec(savePeriod); _server1 = new TestServer(0, inactivePeriod, scavengePeriod, cacheFactory, storeFactory); _servlet = new TestServlet(); ServletHolder holder = new ServletHolder(_servlet); ServletContextHandler contextHandler = _server1.addContext(contextPath); contextHandler.addServlet(holder, servletMapping); _servlet.setStore(contextHandler.getSessionHandler().getSessionCache().getSessionDataStore()); _server1.start(); int port1 = _server1.getPort(); try (StacklessLogging stackless = new StacklessLogging(Log.getLogger("org.eclipse.jetty.server.session"))) { HttpClient client = new HttpClient(); try { client.start(); String url = "http://localhost:" + port1 + contextPath + servletMapping+"?action=create&check=true"; //make a request to set up a session on the server ContentResponse response = client.GET(url); assertEquals(HttpServletResponse.SC_OK,response.getStatus()); String sessionCookie = response.getHeaders().get("Set-Cookie"); assertTrue(sessionCookie != null); String sessionId = TestServer.extractSessionId(sessionCookie); SessionData data = contextHandler.getSessionHandler().getSessionCache().getSessionDataStore().load(sessionId); assertNotNull(data); long lastSaved = data.getLastSaved(); assertTrue(lastSaved > 0); //check session created was saved sessionCookie = sessionCookie.replaceFirst("(\\W)(P|p)ath=", "$1\\$Path="); // Perform a request to change maxInactive on session Request request = client.newRequest("http://localhost:" + port1 + contextPath + servletMapping+"?action=max&value=60"); request.header("Cookie", sessionCookie); response = request.send(); //check session is saved, even though the save optimisation interval has not passed SessionData d = contextHandler.getSessionHandler().getSessionCache().getSessionDataStore().load(sessionId); assertNotNull(d); assertTrue(d.getLastSaved() > lastSaved); assertEquals(60000, d.getMaxInactiveMs()); } finally { client.stop(); } } finally { _server1.stop(); } } public static class TestServlet extends HttpServlet { public String _id = null; public SessionDataStore _store; public HttpSession _firstSession = null; public void setStore (SessionDataStore store) { _store = store; } @Override protected void doGet(HttpServletRequest request, HttpServletResponse httpServletResponse) throws ServletException, IOException { String action = request.getParameter("action"); if (action != null && action.startsWith("create")) { HttpSession session = request.getSession(true); _firstSession = session; _id = session.getId(); session.setAttribute("value", new Integer(1)); String check = request.getParameter("check"); if (!StringUtil.isBlank(check) && _store != null) { boolean exists; try { exists = _store.exists(_id); } catch (Exception e) { throw new ServletException (e); } if ("false".equalsIgnoreCase(check)) assertFalse(exists); else assertTrue(exists); } } else if ("mutate".equalsIgnoreCase(action)) { HttpSession session = request.getSession(false); assertNotNull(session); session.setAttribute("ttt", new Long(System.currentTimeMillis())); } else if ("max".equalsIgnoreCase(action)) { int interval = Integer.parseInt(request.getParameter("value")); HttpSession session = request.getSession(false); assertNotNull(session); session.setMaxInactiveInterval(interval); } else { //Don't change the session HttpSession session = request.getSession(false); assertNotNull(session); String check = request.getParameter("check"); if (!StringUtil.isBlank(check) && "same".equalsIgnoreCase(check)) { assertEquals(_firstSession, session); } else if (!StringUtil.isBlank(check) && "diff".equalsIgnoreCase(check)) { assertNotEquals(_firstSession, session); } } } } }