| /* |
| * Licensed to the Apache Software Foundation (ASF) under one or more |
| * contributor license agreements. See the NOTICE file distributed with |
| * this work for additional information regarding copyright ownership. |
| * The ASF licenses this file to You 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.apache.ignite.internal.websession; |
| |
| import java.io.BufferedReader; |
| import java.io.Externalizable; |
| import java.io.File; |
| import java.io.FileWriter; |
| import java.io.IOException; |
| import java.io.InputStreamReader; |
| import java.io.ObjectInput; |
| import java.io.ObjectOutput; |
| import java.io.Serializable; |
| import java.net.HttpURLConnection; |
| import java.net.URL; |
| import java.net.URLConnection; |
| import java.util.Iterator; |
| import java.util.List; |
| import java.util.Map; |
| import java.util.Random; |
| import java.util.Set; |
| import java.util.concurrent.Callable; |
| import java.util.concurrent.CountDownLatch; |
| import java.util.concurrent.TimeUnit; |
| import java.util.concurrent.atomic.AtomicBoolean; |
| import java.util.concurrent.atomic.AtomicReference; |
| import java.util.concurrent.atomic.AtomicReferenceArray; |
| 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.apache.ignite.Ignite; |
| import org.apache.ignite.IgniteCache; |
| import org.apache.ignite.IgniteCheckedException; |
| import org.apache.ignite.Ignition; |
| import org.apache.ignite.events.Event; |
| import org.apache.ignite.internal.IgniteInternalFuture; |
| import org.apache.ignite.internal.util.typedef.G; |
| import org.apache.ignite.internal.util.typedef.X; |
| import org.apache.ignite.internal.util.typedef.internal.U; |
| import org.apache.ignite.lang.IgnitePredicate; |
| import org.apache.ignite.marshaller.Marshaller; |
| import org.apache.ignite.testframework.GridTestUtils; |
| import org.apache.ignite.testframework.junits.common.GridCommonAbstractTest; |
| import org.eclipse.jetty.security.HashLoginService; |
| import org.eclipse.jetty.server.Server; |
| import org.eclipse.jetty.servlet.ServletHolder; |
| import org.eclipse.jetty.webapp.WebAppContext; |
| import org.jetbrains.annotations.Nullable; |
| import org.junit.Ignore; |
| import org.junit.Test; |
| |
| import static org.apache.ignite.events.EventType.EVT_CACHE_OBJECT_PUT; |
| |
| /** |
| * Tests the correctness of web sessions caching functionality. |
| */ |
| public class WebSessionSelfTest extends GridCommonAbstractTest { |
| /** Port for test Jetty server. */ |
| private static final int TEST_JETTY_PORT = 49090; |
| |
| /** Servers count in load test. */ |
| private static final int SRV_CNT = 3; |
| |
| /** Session invalidated marker. Added to HTTP response to indicate that session invalidation was successful. */ |
| public static final String INVALIDATED = "invalidated"; |
| |
| /** Session invalidated failed marker fot HTTP reponse. */ |
| public static final String FAILED = "failed"; |
| |
| /** |
| * @return Name of the cache for this test. |
| */ |
| protected String getCacheName() { |
| return "partitioned"; |
| } |
| |
| /** |
| * @return Keep binary flag. |
| */ |
| protected boolean keepBinary() { |
| return true; |
| } |
| |
| /** |
| * @throws Exception If failed. |
| */ |
| @Test |
| public void testSingleRequest() throws Exception { |
| testSingleRequest("/modules/core/src/test/config/websession/example-cache.xml"); |
| } |
| |
| /** |
| * @throws Exception If failed. |
| */ |
| @Ignore("https://issues.apache.org/jira/browse/IGNITE-3663") |
| @Test |
| public void testSessionRenewalDuringLogin() throws Exception { |
| testSessionRenewalDuringLogin("/modules/core/src/test/config/websession/example-cache.xml"); |
| } |
| |
| /** |
| * @throws Exception If failed. |
| */ |
| @Test |
| public void testSingleRequestMetaInf() throws Exception { |
| testSingleRequest("ignite-webapp-config.xml"); |
| } |
| |
| /** |
| * @throws Exception If failed. |
| */ |
| @Test |
| public void testImplicitlyAttributeModification() throws Exception { |
| testImplicitlyModification("ignite-webapp-config.xml"); |
| } |
| |
| /** */ |
| @Test |
| public void testSessionCookie() throws Exception { |
| testSessionCookie("/modules/core/src/test/config/websession/example-cache.xml"); |
| } |
| |
| /** |
| * @throws Exception If failed. |
| */ |
| @Test |
| public void testClientReconnectRequest() throws Exception { |
| testClientReconnectRequest("/modules/core/src/test/config/websession/example-cache.xml", |
| "/modules/core/src/test/config/websession/example-cache2.xml", |
| "/modules/core/src/test/config/websession/example-cache-client.xml"); |
| } |
| |
| /** |
| * Tests single request to a server. Checks the presence of session in cache. |
| * |
| * @param srvCfg Server configuration. |
| * @param clientCfg Client configuration. |
| * @throws Exception If failed. |
| */ |
| private void testClientReconnectRequest(String srvCfg, String srvCfg2, String clientCfg) throws Exception { |
| Server srv = null; |
| |
| Ignite ignite = Ignition.start(srvCfg); |
| |
| try { |
| srv = startServer(TEST_JETTY_PORT, clientCfg, "client", new SessionCreateServlet(keepBinary())); |
| |
| URL url = new URL("http://localhost:" + TEST_JETTY_PORT + "/ignitetest/test"); |
| |
| URLConnection conn = url.openConnection(); |
| |
| conn.connect(); |
| |
| try (BufferedReader rdr = new BufferedReader(new InputStreamReader(conn.getInputStream()))) { |
| String sesId = rdr.readLine(); |
| |
| assertNotNull(sesId); |
| } |
| |
| stopGrid(ignite.name()); |
| |
| ignite = Ignition.start(srvCfg); |
| |
| conn = url.openConnection(); |
| |
| conn.connect(); |
| |
| try (BufferedReader rdr = new BufferedReader(new InputStreamReader(conn.getInputStream()))) { |
| String sesId = rdr.readLine(); |
| |
| assertNotNull(sesId); |
| } |
| |
| Ignition.start(srvCfg2); |
| |
| stopGrid(ignite.name()); |
| |
| conn = url.openConnection(); |
| |
| conn.connect(); |
| |
| try (BufferedReader rdr = new BufferedReader(new InputStreamReader(conn.getInputStream()))) { |
| String sesId = rdr.readLine(); |
| |
| assertNotNull(sesId); |
| } |
| } |
| finally { |
| stopServer(srv); |
| |
| stopAllGrids(); |
| } |
| } |
| |
| /** |
| * Tests implicitly modification attribute in session. Checks the presence of session in cache. |
| * |
| * @param cfg Configuration. |
| * @throws Exception If failed. |
| */ |
| private void testImplicitlyModification(String cfg) throws Exception { |
| Server srv = null; |
| |
| try { |
| srv = startServer(TEST_JETTY_PORT, cfg, null, new SessionCreateServlet(keepBinary())); |
| |
| String sesId = sendRequestAndCheckMarker("marker1", null); |
| sendRequestAndCheckMarker("test_string", sesId); |
| sendRequestAndCheckMarker("ignite_test_attribute", sesId); |
| } |
| finally { |
| stopServer(srv); |
| } |
| } |
| |
| /** |
| * @param reqMarker Request marker. |
| * @param sesId Session id. |
| */ |
| private String sendRequestAndCheckMarker(String reqMarker, String sesId) throws IOException, IgniteCheckedException { |
| URLConnection conn = new URL("http://localhost:" + TEST_JETTY_PORT + |
| "/ignitetest/test?marker=" + reqMarker).openConnection(); |
| conn.addRequestProperty("Cookie", "JSESSIONID=" + sesId); |
| |
| conn.connect(); |
| |
| try (BufferedReader rdr = new BufferedReader(new InputStreamReader(conn.getInputStream()))) { |
| sesId = rdr.readLine(); |
| |
| if (!keepBinary()) { |
| IgniteCache<String, HttpSession> cache = G.ignite().cache(getCacheName()); |
| |
| assertNotNull(cache); |
| |
| HttpSession ses = cache.get(sesId); |
| |
| assertNotNull(ses); |
| assertEquals(reqMarker, ((Profile) ses.getAttribute("profile")).getMarker()); |
| } |
| else { |
| IgniteCache<String, WebSessionEntity> cache = G.ignite().cache(getCacheName()); |
| |
| assertNotNull(cache); |
| |
| WebSessionEntity ses = cache.get(sesId); |
| |
| assertNotNull(ses); |
| |
| final byte[] data = ses.attributes().get("profile"); |
| |
| assertNotNull(data); |
| |
| final Marshaller marshaller = G.ignite().configuration().getMarshaller(); |
| |
| assertEquals(reqMarker, marshaller.<Profile>unmarshal(data, getClass().getClassLoader()).getMarker()); |
| } |
| } |
| return sesId; |
| } |
| |
| /** |
| * Tests single request to a server. Checks modification attribute in cache. |
| * |
| * @param cfg Configuration. |
| * @throws Exception If failed. |
| */ |
| private void testSingleRequest(String cfg) throws Exception { |
| Server srv = null; |
| |
| try { |
| srv = startServer(TEST_JETTY_PORT, cfg, null, new SessionCreateServlet(keepBinary())); |
| |
| URLConnection conn = new URL("http://localhost:" + TEST_JETTY_PORT + "/ignitetest/test").openConnection(); |
| |
| conn.connect(); |
| |
| try (BufferedReader rdr = new BufferedReader(new InputStreamReader(conn.getInputStream()))) { |
| String sesId = rdr.readLine(); |
| |
| if (!keepBinary()) { |
| IgniteCache<String, HttpSession> cache = G.ignite().cache(getCacheName()); |
| |
| assertNotNull(cache); |
| |
| HttpSession ses = cache.get(sesId); |
| |
| assertNotNull(ses); |
| |
| assertEquals("val1", ses.getAttribute("key1")); |
| } |
| else { |
| final IgniteCache<String, WebSessionEntity> cache = G.ignite().cache(getCacheName()); |
| |
| assertNotNull(cache); |
| |
| final WebSessionEntity entity = cache.get(sesId); |
| |
| assertNotNull(entity); |
| |
| final byte[] data = entity.attributes().get("key1"); |
| |
| assertNotNull(data); |
| |
| final Marshaller marshaller = G.ignite().configuration().getMarshaller(); |
| |
| final String val = marshaller.unmarshal(data, getClass().getClassLoader()); |
| |
| assertEquals("val1", val); |
| } |
| } |
| } |
| finally { |
| stopServer(srv); |
| } |
| } |
| |
| /** |
| * Tests session renewal during login. Checks modification attribute in cache. |
| * |
| * @param cfg Configuration. |
| * @throws Exception If failed. |
| */ |
| private void testSessionRenewalDuringLogin(String cfg) throws Exception { |
| Server srv = null; |
| String sesId; |
| try { |
| srv = startServerWithLoginService(TEST_JETTY_PORT, cfg, null, new SessionLoginServlet()); |
| |
| URLConnection conn = new URL("http://localhost:" + TEST_JETTY_PORT + "/ignitetest/test").openConnection(); |
| |
| conn.connect(); |
| |
| String sesIdCookie1 = getSessionIdFromCookie(conn); |
| |
| X.println(">>>", "Initial session Cookie: " + sesIdCookie1, ">>>"); |
| |
| try (BufferedReader rdr = new BufferedReader(new InputStreamReader(conn.getInputStream()))) { |
| sesId = rdr.readLine(); |
| |
| if (!keepBinary()) { |
| IgniteCache<String, HttpSession> cache = G.ignite().cache(getCacheName()); |
| |
| assertNotNull(cache); |
| |
| HttpSession ses = cache.get(sesId); |
| |
| assertNotNull(ses); |
| |
| assertEquals("val1", ses.getAttribute("key1")); |
| } |
| else { |
| final IgniteCache<String, WebSessionEntity> cache = G.ignite().cache(getCacheName()); |
| |
| assertNotNull(cache); |
| |
| final WebSessionEntity entity = cache.get(sesId); |
| |
| assertNotNull(entity); |
| |
| final byte[] data = entity.attributes().get("key1"); |
| |
| assertNotNull(data); |
| |
| final Marshaller marshaller = G.ignite().configuration().getMarshaller(); |
| |
| final String val = marshaller.unmarshal(data, getClass().getClassLoader()); |
| |
| assertEquals("val1", val); |
| } |
| } |
| |
| URLConnection conn2 = new URL("http://localhost:" + TEST_JETTY_PORT + "/ignitetest/login").openConnection(); |
| |
| HttpURLConnection con = (HttpURLConnection) conn2; |
| |
| con.addRequestProperty("Cookie", "JSESSIONID=" + sesIdCookie1); |
| |
| con.setRequestMethod("POST"); |
| |
| con.setDoOutput(true); |
| |
| String sesIdCookie2 = getSessionIdFromCookie(con); |
| |
| X.println(">>>", "Logged In session Cookie: " + sesIdCookie2, ">>>"); |
| |
| try (BufferedReader rdr = new BufferedReader(new InputStreamReader(con.getInputStream()))) { |
| String sesId2 = rdr.readLine(); |
| |
| if (!keepBinary()) { |
| IgniteCache<String, HttpSession> cache = G.ignite().cache(getCacheName()); |
| |
| assertNotNull(cache); |
| |
| HttpSession ses = cache.get(sesId2); |
| |
| assertNotNull(ses); |
| |
| assertEquals("val1", ses.getAttribute("key1")); |
| |
| } |
| else { |
| final IgniteCache<String, WebSessionEntity> cache = G.ignite().cache(getCacheName()); |
| |
| assertNotNull(cache); |
| |
| final WebSessionEntity entity = cache.get(sesId2); |
| |
| assertNotNull(entity); |
| |
| final byte[] data = entity.attributes().get("key1"); |
| |
| assertNotNull(data); |
| |
| final Marshaller marshaller = G.ignite().configuration().getMarshaller(); |
| |
| final String val = marshaller.unmarshal(data, getClass().getClassLoader()); |
| |
| assertEquals("val1", val); |
| |
| } |
| |
| } |
| |
| URLConnection conn3 = new URL("http://localhost:" + TEST_JETTY_PORT + "/ignitetest/simple").openConnection(); |
| |
| conn3.addRequestProperty("Cookie", "JSESSIONID=" + sesIdCookie2); |
| |
| conn3.connect(); |
| |
| String sesIdCookie3 = getSessionIdFromCookie(conn3); |
| |
| X.println(">>>", "Post Logged In session Cookie: " + sesIdCookie3, ">>>"); |
| |
| assertEquals(sesIdCookie2, sesIdCookie3); |
| |
| try (BufferedReader rdr = new BufferedReader(new InputStreamReader(conn3.getInputStream()))) { |
| String sesId3 = rdr.readLine(); |
| |
| if (!keepBinary()) { |
| IgniteCache<String, HttpSession> cache = G.ignite().cache(getCacheName()); |
| |
| HttpSession session = cache.get(sesId3); |
| |
| assertNotNull(session); |
| |
| assertNotNull(cache); |
| |
| HttpSession ses = cache.get(sesId3); |
| |
| assertNotNull(ses); |
| |
| assertEquals("val1", ses.getAttribute("key1")); |
| } |
| else { |
| final IgniteCache<String, WebSessionEntity> cache = G.ignite().cache(getCacheName()); |
| |
| assertNotNull(cache); |
| |
| final WebSessionEntity entity = cache.get(sesId3); |
| |
| assertNotNull(entity); |
| |
| assertNotNull(cache.get(sesId3)); |
| |
| final byte[] data = entity.attributes().get("key1"); |
| |
| assertNotNull(data); |
| |
| final Marshaller marshaller = G.ignite().configuration().getMarshaller(); |
| |
| final String val = marshaller.unmarshal(data, getClass().getClassLoader()); |
| |
| assertEquals("val1", val); |
| } |
| } |
| } |
| finally { |
| stopServerWithLoginService(srv); |
| } |
| } |
| |
| /** |
| * Tests session cookie. |
| * |
| * @param cfg Configuration. |
| * @throws Exception If failed. |
| */ |
| private void testSessionCookie(String cfg) throws Exception { |
| Server srv = null; |
| String sesId; |
| |
| try { |
| srv = startServerWithLoginService(TEST_JETTY_PORT, cfg, null, new SessionLoginServlet()); |
| |
| URLConnection conn = new URL("http://localhost:" + TEST_JETTY_PORT + "/ignitetest/test").openConnection(); |
| |
| conn.connect(); |
| |
| String sesIdCookie1 = getSessionIdFromCookie(conn); |
| |
| X.println(">>>", "Initial session Cookie: " + sesIdCookie1, ">>>"); |
| |
| assertTrue(sesIdCookie1.contains(".node0")); |
| |
| try (BufferedReader rdr = new BufferedReader(new InputStreamReader(conn.getInputStream()))) { |
| sesId = rdr.readLine(); |
| |
| if (!keepBinary()) { |
| IgniteCache<String, HttpSession> cache = G.ignite().cache(getCacheName()); |
| |
| assertNotNull(cache); |
| |
| HttpSession ses = cache.get(sesId); |
| |
| assertNotNull(ses); |
| |
| assertEquals("val1", ses.getAttribute("key1")); |
| } |
| else { |
| final IgniteCache<String, WebSessionEntity> cache = G.ignite().cache(getCacheName()); |
| |
| assertNotNull(cache); |
| |
| final WebSessionEntity entity = cache.get(sesId); |
| |
| assertNotNull(entity); |
| |
| final byte[] data = entity.attributes().get("key1"); |
| |
| assertNotNull(data); |
| |
| final Marshaller marshaller = G.ignite().configuration().getMarshaller(); |
| |
| final String val = marshaller.unmarshal(data, getClass().getClassLoader()); |
| |
| assertEquals("val1", val); |
| } |
| } |
| |
| URLConnection conn2 = new URL("http://localhost:" + TEST_JETTY_PORT + "/ignitetest/login").openConnection(); |
| |
| HttpURLConnection con = (HttpURLConnection) conn2; |
| |
| con.addRequestProperty("Cookie", "JSESSIONID=" + sesIdCookie1); |
| |
| con.setRequestMethod("POST"); |
| |
| con.setDoOutput(true); |
| |
| String sesIdCookie2 = getSessionIdFromCookie(con); |
| |
| X.println(">>>", "Logged In session Cookie: " + sesIdCookie2, ">>>"); |
| |
| assertTrue(sesIdCookie2.contains(".node0")); |
| |
| try (BufferedReader rdr = new BufferedReader(new InputStreamReader(con.getInputStream()))) { |
| String sesId2 = rdr.readLine(); |
| |
| if (!keepBinary()) { |
| IgniteCache<String, HttpSession> cache = G.ignite().cache(getCacheName()); |
| |
| assertNotNull(cache); |
| |
| HttpSession ses = cache.get(sesId2); |
| |
| assertNotNull(ses); |
| |
| assertEquals("val1", ses.getAttribute("key1")); |
| |
| } |
| else { |
| final IgniteCache<String, WebSessionEntity> cache = G.ignite().cache(getCacheName()); |
| |
| assertNotNull(cache); |
| |
| final WebSessionEntity entity = cache.get(sesId2); |
| |
| assertNotNull(entity); |
| |
| final byte[] data = entity.attributes().get("key1"); |
| |
| assertNotNull(data); |
| |
| final Marshaller marshaller = G.ignite().configuration().getMarshaller(); |
| |
| final String val = marshaller.unmarshal(data, getClass().getClassLoader()); |
| |
| assertEquals("val1", val); |
| |
| } |
| } |
| } |
| finally { |
| stopServerWithLoginService(srv); |
| } |
| } |
| |
| /** |
| * Tests invalidated sessions. |
| * |
| * @throws Exception Exception If failed. |
| */ |
| @Test |
| public void testInvalidatedSession() throws Exception { |
| String invalidatedSesId; |
| Server srv = null; |
| |
| try { |
| srv = startServer(TEST_JETTY_PORT, "/modules/core/src/test/config/websession/example-cache.xml", |
| null, new InvalidatedSessionServlet()); |
| |
| Ignite ignite = G.ignite(); |
| |
| URLConnection conn = new URL("http://localhost:" + TEST_JETTY_PORT + "/ignitetest/invalidated").openConnection(); |
| |
| conn.connect(); |
| |
| try (BufferedReader rdr = new BufferedReader(new InputStreamReader(conn.getInputStream()))) { |
| |
| // checks if the old session object is invalidated. |
| invalidatedSesId = rdr.readLine(); |
| |
| assertNotNull(invalidatedSesId); |
| |
| if (!keepBinary()) { |
| IgniteCache<String, HttpSession> cache = ignite.cache(getCacheName()); |
| |
| assertNotNull(cache); |
| |
| HttpSession invalidatedSes = cache.get(invalidatedSesId); |
| |
| assertNull(invalidatedSes); |
| |
| // requests to subsequent getSession() returns null. |
| String ses = rdr.readLine(); |
| |
| assertEquals("null", ses); |
| } |
| else { |
| IgniteCache<String, WebSessionEntity> cache = ignite.cache(getCacheName()); |
| |
| assertNotNull(cache); |
| |
| WebSessionEntity invalidatedSes = cache.get(invalidatedSesId); |
| |
| assertNull(invalidatedSes); |
| |
| // requests to subsequent getSession() returns null. |
| String ses = rdr.readLine(); |
| |
| assertEquals("null", ses); |
| } |
| } |
| |
| // put and update. |
| final CountDownLatch latch = new CountDownLatch(2); |
| |
| final IgnitePredicate<Event> putLsnr = new IgnitePredicate<Event>() { |
| @Override public boolean apply(Event evt) { |
| assert evt != null; |
| |
| latch.countDown(); |
| |
| return true; |
| } |
| }; |
| |
| ignite.events().localListen(putLsnr, EVT_CACHE_OBJECT_PUT); |
| |
| // new request that creates a new session. |
| conn = new URL("http://localhost:" + TEST_JETTY_PORT + "/ignitetest/valid").openConnection(); |
| |
| conn.addRequestProperty("Cookie", "JSESSIONID=" + invalidatedSesId); |
| |
| conn.connect(); |
| |
| try (BufferedReader rdr = new BufferedReader(new InputStreamReader(conn.getInputStream()))) { |
| String sesId = rdr.readLine(); |
| |
| assertFalse(sesId.equals("null")); |
| |
| assertTrue(latch.await(10, TimeUnit.SECONDS)); |
| |
| if (!keepBinary()) { |
| IgniteCache<String, HttpSession> cache = ignite.cache(getCacheName()); |
| |
| assertNotNull(cache); |
| |
| HttpSession ses = cache.get(sesId); |
| |
| assertNotNull(ses); |
| |
| assertEquals("val10", ses.getAttribute("key10")); |
| } |
| else { |
| IgniteCache<String, WebSessionEntity> cache = ignite.cache(getCacheName()); |
| |
| assertNotNull(cache); |
| |
| WebSessionEntity entity = cache.get(sesId); |
| |
| assertNotNull(entity); |
| |
| final Marshaller marshaller = ignite.configuration().getMarshaller(); |
| |
| assertEquals("val10", |
| marshaller.unmarshal(entity.attributes().get("key10"), getClass().getClassLoader())); |
| } |
| } |
| } |
| finally { |
| stopServer(srv); |
| } |
| } |
| |
| /** |
| * Tests session id change. |
| * |
| * @throws Exception Exception If failed. |
| */ |
| @Test |
| public void testChangeSessionId() throws Exception { |
| String newWebSesId; |
| Server srv = null; |
| |
| try { |
| srv = startServer(TEST_JETTY_PORT, "/modules/core/src/test/config/websession/example-cache.xml", |
| null, new SessionIdChangeServlet()); |
| |
| Ignite ignite = G.ignite(); |
| |
| URLConnection conn = new URL("http://localhost:" + TEST_JETTY_PORT + "/ignitetest/chngsesid").openConnection(); |
| |
| conn.connect(); |
| |
| try (BufferedReader rdr = new BufferedReader(new InputStreamReader(conn.getInputStream()))) { |
| |
| // checks if the old session object is invalidated. |
| String oldId = rdr.readLine(); |
| |
| assertNotNull(oldId); |
| |
| // id from genuine session |
| String newGenSesId = rdr.readLine(); |
| |
| assertNotNull(newGenSesId); |
| |
| assertFalse(newGenSesId.equals(oldId)); |
| |
| // id from replicated session |
| newWebSesId = rdr.readLine(); |
| |
| assertNotNull(newWebSesId); |
| |
| assertTrue(newGenSesId.equals(newWebSesId)); |
| |
| if (!keepBinary()) { |
| IgniteCache<String, HttpSession> cache = ignite.cache(getCacheName()); |
| |
| assertNotNull(cache); |
| |
| Thread.sleep(1000); |
| |
| HttpSession ses = cache.get(newWebSesId); |
| |
| assertNotNull(ses); |
| |
| assertEquals("val1", ses.getAttribute("key1")); |
| } |
| else { |
| IgniteCache<String, WebSessionEntity> cache = ignite.cache(getCacheName()); |
| |
| assertNotNull(cache); |
| |
| Thread.sleep(1000); |
| |
| WebSessionEntity ses = cache.get(newWebSesId); |
| |
| assertNotNull(ses); |
| |
| final Marshaller marshaller = ignite.configuration().getMarshaller(); |
| |
| assertEquals("val1", |
| marshaller.<String>unmarshal(ses.attributes().get("key1"), getClass().getClassLoader())); |
| } |
| } |
| |
| conn = new URL("http://localhost:" + TEST_JETTY_PORT + "/ignitetest/simple").openConnection(); |
| |
| conn.addRequestProperty("Cookie", "JSESSIONID=" + newWebSesId); |
| |
| conn.connect(); |
| |
| try (BufferedReader rdr = new BufferedReader(new InputStreamReader(conn.getInputStream()))) { |
| |
| // checks if it can be handled with the subsequent request. |
| String sesId = rdr.readLine(); |
| |
| assertTrue(newWebSesId.equals(sesId)); |
| |
| String attr = rdr.readLine(); |
| |
| assertEquals("val1", attr); |
| |
| String reqSesValid = rdr.readLine(); |
| |
| assertEquals("true", reqSesValid); |
| |
| StringBuilder sb = new StringBuilder(); |
| String line; |
| |
| while ((line = rdr.readLine()) != null) |
| sb.append(line); |
| |
| assertTrue(sb.toString().contains(INVALIDATED)); |
| } |
| } |
| finally { |
| stopServer(srv); |
| } |
| } |
| |
| /** |
| * @throws Exception If failed. |
| */ |
| @Test |
| public void testRestarts() throws Exception { |
| final AtomicReference<String> sesIdRef = new AtomicReference<>(); |
| |
| final AtomicReferenceArray<Server> srvs = new AtomicReferenceArray<>(SRV_CNT); |
| |
| for (int idx = 0; idx < SRV_CNT; idx++) { |
| String cfg = "/modules/core/src/test/config/websession/spring-cache-" + (idx + 1) + ".xml"; |
| |
| srvs.set(idx, startServer( |
| TEST_JETTY_PORT + idx, cfg, "grid-" + (idx + 1), new RestartsTestServlet(sesIdRef))); |
| } |
| |
| final AtomicBoolean stop = new AtomicBoolean(); |
| |
| IgniteInternalFuture<?> restarterFut = GridTestUtils.runMultiThreadedAsync(new Callable<Object>() { |
| @SuppressWarnings("BusyWait") |
| @Override public Object call() throws Exception { |
| Random rnd = new Random(); |
| |
| for (int i = 0; i < 10; i++) { |
| int idx = -1; |
| Server srv = null; |
| |
| while (srv == null) { |
| idx = rnd.nextInt(SRV_CNT); |
| |
| srv = srvs.getAndSet(idx, null); |
| } |
| |
| assert idx != -1; |
| |
| stopServer(srv); |
| |
| String cfg = "/modules/core/src/test/config/websession/spring-cache-" + (idx + 1) + ".xml"; |
| |
| srv = startServer( |
| TEST_JETTY_PORT + idx, cfg, "grid-" + (idx + 1), new RestartsTestServlet(sesIdRef)); |
| |
| assert srvs.compareAndSet(idx, null, srv); |
| |
| Thread.sleep(100); |
| } |
| |
| X.println("Stopping..."); |
| |
| stop.set(true); |
| |
| return null; |
| } |
| }, 1, "restarter"); |
| |
| Server srv = null; |
| |
| try { |
| Random rnd = new Random(); |
| |
| int n = 0; |
| |
| while (!stop.get()) { |
| int idx = -1; |
| |
| while (srv == null) { |
| idx = rnd.nextInt(SRV_CNT); |
| |
| srv = srvs.getAndSet(idx, null); |
| } |
| |
| assert idx != -1; |
| |
| int port = TEST_JETTY_PORT + idx; |
| |
| URLConnection conn = new URL("http://localhost:" + port + "/ignitetest/test").openConnection(); |
| |
| String sesId = sesIdRef.get(); |
| |
| if (sesId != null) |
| conn.addRequestProperty("Cookie", "JSESSIONID=" + sesId); |
| |
| conn.connect(); |
| |
| String str; |
| |
| try (BufferedReader rdr = new BufferedReader(new InputStreamReader(conn.getInputStream()))) { |
| str = rdr.readLine(); |
| } |
| |
| assertEquals(n, Integer.parseInt(str)); |
| |
| n++; |
| |
| assert srvs.compareAndSet(idx, null, srv); |
| |
| srv = null; |
| } |
| |
| X.println(">>> Made " + n + " requests."); |
| } |
| finally { |
| restarterFut.get(); |
| |
| if (srv != null) |
| stopServer(srv); |
| |
| for (int i = 0; i < srvs.length(); i++) |
| stopServer(srvs.get(i)); |
| } |
| } |
| |
| /** |
| * @param cfg Configuration. |
| * @param igniteInstanceName Ignite instance name. |
| * @param servlet Servlet. |
| * @return Servlet container web context for this test. |
| */ |
| protected WebAppContext getWebContext(@Nullable String cfg, @Nullable String igniteInstanceName, |
| boolean keepBinaryFlag, HttpServlet servlet) { |
| final String path = keepBinaryFlag ? "modules/core/src/test/webapp" : "modules/web/src/test/webapp2"; |
| |
| WebAppContext ctx = new WebAppContext(U.resolveIgnitePath(path).getAbsolutePath(), |
| "/ignitetest"); |
| |
| ctx.setInitParameter("IgniteConfigurationFilePath", cfg); |
| ctx.setInitParameter("IgniteWebSessionsGridName", igniteInstanceName); |
| ctx.setInitParameter("IgniteWebSessionsCacheName", getCacheName()); |
| ctx.setInitParameter("IgniteWebSessionsMaximumRetriesOnFail", "100"); |
| ctx.setInitParameter("IgniteWebSessionsKeepBinary", Boolean.toString(keepBinaryFlag)); |
| |
| ctx.addServlet(new ServletHolder(servlet), "/*"); |
| |
| return ctx; |
| } |
| |
| /** |
| * Starts server. |
| * |
| * @param port Port number. |
| * @param cfg Configuration. |
| * @param igniteInstanceName Ignite instance name. |
| * @param servlet Servlet. |
| * @return Server. |
| * @throws Exception In case of error. |
| */ |
| private Server startServer(int port, @Nullable String cfg, @Nullable String igniteInstanceName, HttpServlet servlet) |
| throws Exception { |
| Server srv = new Server(port); |
| |
| WebAppContext ctx = getWebContext(cfg, igniteInstanceName, keepBinary(), servlet); |
| |
| srv.setHandler(ctx); |
| |
| srv.start(); |
| |
| return srv; |
| } |
| |
| /** |
| * Starts server with Login Service and create a realm file. |
| * |
| * @param port Port number. |
| * @param cfg Configuration. |
| * @param igniteInstanceName Ignite instance name. |
| * @param servlet Servlet. |
| * @return Server. |
| * @throws Exception In case of error. |
| */ |
| private Server startServerWithLoginService( |
| int port, @Nullable String cfg, @Nullable String igniteInstanceName, HttpServlet servlet |
| ) throws Exception { |
| Server srv = new Server(port); |
| |
| WebAppContext ctx = getWebContext(cfg, igniteInstanceName, keepBinary(), servlet); |
| |
| HashLoginService hashLoginService = new HashLoginService(); |
| hashLoginService.setName("Test Realm"); |
| createRealm(); |
| hashLoginService.setConfig("/tmp/realm.properties"); |
| ctx.getSecurityHandler().setLoginService(hashLoginService); |
| |
| srv.setHandler(ctx); |
| |
| srv.start(); |
| |
| return srv; |
| } |
| |
| /** |
| * Stops server. |
| * |
| * @param srv Server. |
| * @throws Exception In case of error. |
| */ |
| private void stopServer(@Nullable Server srv) throws Exception { |
| if (srv != null) |
| srv.stop(); |
| } |
| |
| /** |
| * Stops server and delete realm file. |
| * |
| * @param srv Server. |
| * @throws Exception In case of error. |
| */ |
| private void stopServerWithLoginService(@Nullable Server srv) throws Exception { |
| if (srv != null) { |
| srv.stop(); |
| File realmFile = new File("/tmp/realm.properties"); |
| realmFile.delete(); |
| } |
| } |
| |
| /** Creates a realm file to store test user credentials */ |
| private void createRealm() throws Exception { |
| File realmFile = new File("/tmp/realm.properties"); |
| FileWriter fileWriter = new FileWriter(realmFile); |
| fileWriter.append("admin:admin"); |
| fileWriter.flush(); |
| fileWriter.close(); |
| } |
| |
| /** |
| * Retrieves HttpSession sessionId from Cookie |
| * |
| * @param conn URLConnection |
| * @return sesId |
| */ |
| private String getSessionIdFromCookie(URLConnection conn) { |
| String sessionCookieValue = null; |
| String sesId = null; |
| Map<String, List<String>> headerFields = conn.getHeaderFields(); |
| Set<String> headerFieldsSet = headerFields.keySet(); |
| Iterator<String> hearerFieldsIter = headerFieldsSet.iterator(); |
| |
| while (hearerFieldsIter.hasNext()) { |
| String headerFieldKey = hearerFieldsIter.next(); |
| |
| if ("Set-Cookie".equalsIgnoreCase(headerFieldKey)) { |
| List<String> headerFieldValue = headerFields.get(headerFieldKey); |
| |
| for (String headerValue : headerFieldValue) { |
| String[] fields = headerValue.split(";"); |
| sessionCookieValue = fields[0]; |
| sesId = sessionCookieValue.substring(sessionCookieValue.indexOf("=") + 1, |
| sessionCookieValue.length()); |
| } |
| } |
| } |
| |
| return sesId; |
| } |
| |
| /** |
| * Test servlet. |
| */ |
| private static class SessionCreateServlet extends HttpServlet { |
| /** Keep binary flag. */ |
| private final boolean keepBinaryFlag; |
| |
| /** |
| * @param keepBinaryFlag Keep binary flag. |
| */ |
| private SessionCreateServlet(final boolean keepBinaryFlag) { |
| this.keepBinaryFlag = keepBinaryFlag; |
| } |
| |
| /** {@inheritDoc} */ |
| @Override protected void doGet(HttpServletRequest req, HttpServletResponse res) |
| throws ServletException, IOException { |
| HttpSession ses = req.getSession(true); |
| |
| ses.setAttribute("checkCnt", 0); |
| ses.setAttribute("key1", "val1"); |
| ses.setAttribute("key2", "val2"); |
| ses.setAttribute("mkey", new TestObj("mval", keepBinaryFlag)); |
| |
| Profile p = (Profile)ses.getAttribute("profile"); |
| |
| if (p == null) { |
| p = new Profile(); |
| ses.setAttribute("profile", p); |
| } |
| |
| p.setMarker(req.getParameter("marker")); |
| |
| X.println(">>>", "Created session: " + ses.getId(), ">>>"); |
| |
| res.getWriter().write(ses.getId()); |
| |
| res.getWriter().flush(); |
| } |
| } |
| |
| /** |
| * Complex class for stored in session. |
| */ |
| private static class Profile implements Serializable { |
| |
| /** |
| * Marker string. |
| */ |
| String marker; |
| |
| /** |
| * @return marker |
| */ |
| public String getMarker() { |
| return marker; |
| } |
| |
| /** |
| * @param marker |
| */ |
| public void setMarker(String marker) { |
| this.marker = marker; |
| } |
| } |
| |
| /** |
| * Test for invalidated sessions. |
| */ |
| private static class InvalidatedSessionServlet extends HttpServlet { |
| /** {@inheritDoc} */ |
| @Override protected void doGet(HttpServletRequest req, HttpServletResponse res) |
| throws ServletException, IOException { |
| HttpSession ses = req.getSession(); |
| |
| assert ses != null; |
| |
| final String sesId = ses.getId(); |
| |
| if (req.getPathInfo().equals("/invalidated")) { |
| X.println(">>>", "Session to invalidate with id: " + sesId, ">>>"); |
| |
| ses.invalidate(); |
| |
| res.getWriter().println(sesId); |
| |
| // invalidates again. |
| req.getSession().invalidate(); |
| } |
| else if (req.getPathInfo().equals("/valid")) { |
| X.println(">>>", "Created session: " + sesId, ">>>"); |
| |
| ses.setAttribute("key10", "val10"); |
| } |
| |
| res.getWriter().println((req.getSession(false) == null) ? "null" : sesId); |
| |
| res.getWriter().flush(); |
| } |
| } |
| |
| /** |
| * Test session behavior on id change. |
| */ |
| private static class SessionIdChangeServlet extends HttpServlet { |
| /** {@inheritDoc} */ |
| @Override protected void doGet(HttpServletRequest req, HttpServletResponse res) |
| throws ServletException, IOException { |
| HttpSession ses = req.getSession(); |
| |
| assertNotNull(ses); |
| |
| if (req.getPathInfo().equals("/chngsesid")) { |
| |
| ses.setAttribute("key1", "val1"); |
| |
| X.println(">>>", "Created session: " + ses.getId(), ">>>"); |
| |
| res.getWriter().println(req.getSession().getId()); |
| |
| String newId = req.changeSessionId(); |
| |
| // new id from genuine session. |
| res.getWriter().println(newId); |
| |
| // new id from WebSession. |
| res.getWriter().println(req.getSession().getId()); |
| |
| res.getWriter().flush(); |
| } |
| else if (req.getPathInfo().equals("/simple")) { |
| res.getWriter().println(req.getSession().getId()); |
| |
| res.getWriter().println(req.getSession().getAttribute("key1")); |
| |
| res.getWriter().println(req.isRequestedSessionIdValid()); |
| |
| try { |
| req.getSession().invalidate(); |
| |
| res.getWriter().println(INVALIDATED); |
| } |
| catch (Exception ignored) { |
| res.getWriter().println(FAILED); |
| } |
| |
| res.getWriter().flush(); |
| } |
| else |
| throw new ServletException("Nonexisting path: " + req.getPathInfo()); |
| } |
| } |
| |
| /** |
| * Test session behavior on id change. |
| */ |
| private static class SessionLoginServlet extends HttpServlet { |
| /** {@inheritDoc} */ |
| @Override protected void doGet(HttpServletRequest req, HttpServletResponse res) |
| throws ServletException, IOException { |
| |
| if (req.getPathInfo().equals("/test")) { |
| HttpSession ses = req.getSession(true); |
| assertNotNull(ses); |
| ses.setAttribute("checkCnt", 0); |
| ses.setAttribute("key1", "val1"); |
| ses.setAttribute("key2", "val2"); |
| ses.setAttribute("mkey", new TestObj()); |
| |
| Profile p = (Profile) ses.getAttribute("profile"); |
| |
| if (p == null) { |
| p = new Profile(); |
| ses.setAttribute("profile", p); |
| } |
| |
| p.setMarker(req.getParameter("marker")); |
| |
| X.println(">>>", "Request session test: " + ses.getId(), ">>>"); |
| |
| res.getWriter().write(ses.getId()); |
| |
| res.getWriter().flush(); |
| |
| } else if (req.getPathInfo().equals("/simple")) { |
| HttpSession ses = req.getSession(); |
| X.println(">>>", "Request session simple: " + ses.getId(), ">>>"); |
| |
| res.getWriter().write(ses.getId()); |
| |
| res.getWriter().flush(); |
| } |
| } |
| |
| /** {@inheritDoc} */ |
| @Override protected void doPost(HttpServletRequest req, HttpServletResponse res) |
| throws ServletException, IOException { |
| if (req.getPathInfo().equals("/login")) { |
| try { |
| req.login("admin", "admin"); |
| } catch (Exception e) { |
| X.printerrln("Login failed due to exception.", e); |
| } |
| |
| HttpSession ses = req.getSession(); |
| |
| X.println(">>>", "Logged In session: " + ses.getId(), ">>>"); |
| |
| res.getWriter().write(ses.getId()); |
| |
| res.getWriter().flush(); |
| } |
| } |
| } |
| |
| /** |
| * Servlet for restarts test. |
| */ |
| private static class RestartsTestServlet extends HttpServlet { |
| /** Session ID. */ |
| private final AtomicReference<String> sesId; |
| |
| /** |
| * @param sesId Session ID. |
| */ |
| RestartsTestServlet(AtomicReference<String> sesId) { |
| this.sesId = sesId; |
| } |
| |
| /** {@inheritDoc} */ |
| @Override protected void doGet(HttpServletRequest req, HttpServletResponse res) |
| throws ServletException, IOException { |
| HttpSession ses = req.getSession(true); |
| |
| assertTrue(req.isRequestedSessionIdValid()); |
| |
| sesId.compareAndSet(null, ses.getId()); |
| |
| Integer attr = (Integer)ses.getAttribute("attr"); |
| |
| if (attr == null) |
| attr = 0; |
| else |
| attr++; |
| |
| ses.setAttribute("attr", attr); |
| |
| res.getWriter().write(attr.toString()); |
| |
| res.getWriter().flush(); |
| } |
| } |
| |
| /** |
| * |
| */ |
| private static class TestObj implements Externalizable { |
| /** */ |
| private static final long serialVersionUID = 0L; |
| |
| /** */ |
| private String val; |
| |
| /** */ |
| private boolean keepBinaryFlag; |
| |
| /** |
| * |
| */ |
| public TestObj() { |
| } |
| |
| /** |
| * @param val Value. |
| * @param keepBinaryFlag Keep binary flag. |
| */ |
| public TestObj(final String val, final boolean keepBinaryFlag) { |
| this.val = val; |
| this.keepBinaryFlag = keepBinaryFlag; |
| } |
| |
| /** {@inheritDoc} */ |
| @Override public void writeExternal(final ObjectOutput out) throws IOException { |
| U.writeString(out, val); |
| out.writeBoolean(keepBinaryFlag); |
| System.out.println("TestObj marshalled"); |
| } |
| |
| /** {@inheritDoc} */ |
| @Override public void readExternal(final ObjectInput in) throws IOException, ClassNotFoundException { |
| val = U.readString(in); |
| keepBinaryFlag = in.readBoolean(); |
| |
| // It must be unmarshalled only on client side. |
| if (keepBinaryFlag) |
| fail("Should not be unmarshalled"); |
| |
| System.out.println("TestObj unmarshalled"); |
| } |
| |
| /** {@inheritDoc} */ |
| @Override public boolean equals(final Object o) { |
| if (this == o) return true; |
| if (o == null || getClass() != o.getClass()) return false; |
| |
| final TestObj testObj = (TestObj) o; |
| |
| if (keepBinaryFlag != testObj.keepBinaryFlag) return false; |
| return val != null ? val.equals(testObj.val) : testObj.val == null; |
| |
| } |
| |
| /** {@inheritDoc} */ |
| @Override public int hashCode() { |
| int res = val != null ? val.hashCode() : 0; |
| |
| res = 31 * res + (keepBinaryFlag ? 1 : 0); |
| |
| return res; |
| } |
| } |
| } |