/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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 io.undertow.server.handlers.proxy; import static io.undertow.Handlers.jvmRoute; import static io.undertow.Handlers.path; import java.io.IOException; import java.nio.charset.StandardCharsets; import org.apache.http.HttpResponse; import org.apache.http.client.methods.HttpGet; import org.apache.http.impl.client.DecompressingHttpClient; import org.junit.AfterClass; import org.junit.Assert; import org.junit.BeforeClass; import org.junit.Test; import org.junit.runner.RunWith; import org.xnio.IoUtils; import io.undertow.Undertow; import io.undertow.UndertowLogger; import io.undertow.server.HttpHandler; import io.undertow.server.HttpServerExchange; import io.undertow.server.session.InMemorySessionManager; import io.undertow.server.session.Session; import io.undertow.server.session.SessionAttachmentHandler; import io.undertow.server.session.SessionCookieConfig; import io.undertow.server.session.SessionManager; import io.undertow.testutils.DefaultServer; import io.undertow.testutils.HttpClientUtils; import io.undertow.testutils.HttpOneOnly; import io.undertow.testutils.TestHttpClient; import io.undertow.util.AttachmentKey; import io.undertow.util.Protocols; import io.undertow.util.StatusCodes; /** * Tests the load balancing proxy * * @author Stuart Douglas */ @RunWith(DefaultServer.class) public abstract class AbstractLoadBalancingProxyTestCase { private static final String COUNT = "count"; public static final String RESPONSE_BODY = "This is a response body"; protected static Undertow server1; protected static Undertow server2; private static volatile boolean firstFail = true; @BeforeClass public static void setupFailTest() { firstFail = true; } protected static final int IDLE_TIMEOUT = 1000; @AfterClass public static void teardown() { server1.stop(); server2.stop(); } @Test public void testLoadShared() throws IOException { final StringBuilder resultString = new StringBuilder(); for (int i = 0; i < 6; ++i) { DecompressingHttpClient client = new DecompressingHttpClient(new TestHttpClient()); try { HttpGet get = new HttpGet(DefaultServer.getDefaultServerURL() + "/name"); HttpResponse result = client.execute(get); Assert.assertEquals(StatusCodes.OK, result.getStatusLine().getStatusCode()); resultString.append(HttpClientUtils.readResponse(result)); resultString.append(' '); } finally { client.getConnectionManager().shutdown(); } } Assert.assertTrue(resultString.toString().contains("server1")); Assert.assertTrue(resultString.toString().contains("server2")); } @Test public void testUrlEncoding() throws IOException { TestHttpClient client = new TestHttpClient(); try { HttpGet get = new HttpGet(DefaultServer.getDefaultServerURL() + "/url/foo=bar"); HttpResponse result = client.execute(get); Assert.assertEquals(StatusCodes.OK, result.getStatusLine().getStatusCode()); Assert.assertEquals("/url/foo=bar", HttpClientUtils.readResponse(result)); } finally { client.getConnectionManager().shutdown(); } } @Test @HttpOneOnly public void testOldBackend() throws IOException { TestHttpClient client = new TestHttpClient(); try { for(int i = 0; i < 10; ++i) { HttpGet get = new HttpGet(DefaultServer.getDefaultServerURL() + "/old"); HttpResponse result = client.execute(get); Assert.assertEquals(StatusCodes.OK, result.getStatusLine().getStatusCode()); Assert.assertEquals(RESPONSE_BODY, HttpClientUtils.readResponse(result)); } } finally { client.getConnectionManager().shutdown(); } } @Test public void testMaxRetries() throws IOException { TestHttpClient client = new TestHttpClient(); try { HttpGet get = new HttpGet(DefaultServer.getDefaultServerURL() + "/fail"); HttpResponse result = client.execute(get); Assert.assertEquals(StatusCodes.OK, result.getStatusLine().getStatusCode()); Assert.assertEquals("/fail:false", HttpClientUtils.readResponse(result)); } finally { client.getConnectionManager().shutdown(); } } @Test public void testLoadSharedWithServerShutdown() throws Exception { final StringBuilder resultString = new StringBuilder(); for (int i = 0; i < 6; ++i) { TestHttpClient client = new TestHttpClient(); HttpGet get = new HttpGet(DefaultServer.getDefaultServerURL() + "/name"); HttpResponse result = client.execute(get); Assert.assertEquals("Test failed with i=" + i, StatusCodes.OK, result.getStatusLine().getStatusCode()); resultString.append(HttpClientUtils.readResponse(result)); resultString.append(' '); server1.stop(); Thread.sleep(600); get = new HttpGet(DefaultServer.getDefaultServerURL() + "/name"); result = client.execute(get); Assert.assertEquals("Test failed with i=" + i, StatusCodes.OK, result.getStatusLine().getStatusCode()); resultString.append(HttpClientUtils.readResponse(result)); resultString.append(' '); server1.start(); server2.stop(); Thread.sleep(600); get = new HttpGet(DefaultServer.getDefaultServerURL() + "/name"); result = client.execute(get); Assert.assertEquals("Test failed with i=" + i, StatusCodes.OK, result.getStatusLine().getStatusCode()); resultString.append(HttpClientUtils.readResponse(result)); resultString.append(' '); server2.start(); } Assert.assertTrue(resultString.toString().contains("server1")); Assert.assertTrue(resultString.toString().contains("server2")); } @Test public void testStickySessions() throws IOException { int expected = 0; TestHttpClient client = new TestHttpClient(); try { for (int i = 0; i < 6; ++i) { try { HttpGet get = new HttpGet(DefaultServer.getDefaultServerURL() + "/session"); get.addHeader("Connection", "close"); HttpResponse result = client.execute(get); Assert.assertEquals("Test failed with i=" + i, StatusCodes.OK, result.getStatusLine().getStatusCode()); int count = Integer.parseInt(HttpClientUtils.readResponse(result)); Assert.assertEquals(expected++, count); } catch (AssertionError e) { throw e; } catch (Exception e) { throw new AssertionError("Test failed with i=" + i, e); } } } finally { client.getConnectionManager().shutdown(); } } //see https://issues.jboss.org/browse/UNDERTOW-276 @Test public void testDuplicateHeaders() throws IOException { int expected = 0; TestHttpClient client = new TestHttpClient(); try { for (int i = 0; i < 6; ++i) { try { HttpGet get = new HttpGet(DefaultServer.getDefaultServerURL() + "/session"); get.addHeader("a", "b"); get.addHeader("a", "b"); get.addHeader("a", "b"); get.addHeader("a", "b"); get.addHeader("a", "b"); get.addHeader("a", "b"); get.addHeader("a", "b"); get.addHeader("a", "b"); get.addHeader("Connection", "close"); HttpResponse result = client.execute(get); Assert.assertEquals("Test failed with i=" + i, StatusCodes.OK, result.getStatusLine().getStatusCode()); int count = Integer.parseInt(HttpClientUtils.readResponse(result)); Assert.assertEquals("Test failed with i=" + i, expected++, count); } catch (AssertionError e) { throw e; } catch (Exception e) { throw new AssertionError("Test failed with i=" + i, e); } } } finally { client.getConnectionManager().shutdown(); } } @Test public void testConnectionTimeout() throws Exception { TestHttpClient client = new TestHttpClient(); HttpGet get = new HttpGet(DefaultServer.getDefaultServerURL() + "/timeout"); get.addHeader("Connection", "close"); HttpResponse result = client.execute(get); boolean res = Boolean.parseBoolean(HttpClientUtils.readResponse(result)); Assert.assertEquals(false, res); try { for (int i = 0; i < 20; ++i) { //try and make sure that all IO threads have been used, this is not reliable however result = client.execute(get); HttpClientUtils.readResponse(result); } result = client.execute(get); res = Boolean.parseBoolean(HttpClientUtils.readResponse(result)); //Assert.assertEquals(true, res); //this will fail sometime, unless we make a huge number of requests to make sure all IO threads are utilised Thread.sleep(IDLE_TIMEOUT + 1000); UndertowLogger.ROOT_LOGGER.info("Sending timed out request"); result = client.execute(get); res = Boolean.parseBoolean(HttpClientUtils.readResponse(result)); Assert.assertEquals(false, res); } finally { client.getConnectionManager().shutdown(); } } private static final AttachmentKey<Boolean> EXISTING = AttachmentKey.create(Boolean.class); protected static HttpHandler getRootHandler(String s1, String server1) { final SessionCookieConfig sessionConfig = new SessionCookieConfig(); return jvmRoute("JSESSIONID", s1, path() .addPrefixPath("/session", new SessionAttachmentHandler(new SessionTestHandler(sessionConfig), new InMemorySessionManager(""), sessionConfig)) .addPrefixPath("/name", new StringSendHandler(server1)) .addPrefixPath("/url", new HttpHandler() { @Override public void handleRequest(HttpServerExchange exchange) throws Exception { exchange.getResponseSender().send(exchange.getRequestURI()); } }) .addPrefixPath("/path", new HttpHandler() { @Override public void handleRequest(HttpServerExchange exchange) throws Exception { exchange.getResponseSender().send(exchange.getRequestURI()); } }) .addPrefixPath("/fail", new HttpHandler() { @Override public void handleRequest(HttpServerExchange exchange) throws Exception { if (firstFail) { firstFail = false; IoUtils.safeClose(exchange.getConnection()); } exchange.getResponseSender().send(exchange.getRequestURI() + ":" + firstFail); } }).addPrefixPath("/timeout", new HttpHandler() { @Override public void handleRequest(HttpServerExchange exchange) throws Exception { if (exchange.getConnection().getAttachment(EXISTING) == null) { exchange.getConnection().putAttachment(EXISTING, true); exchange.getResponseSender().send("false"); } else { exchange.getResponseSender().send("true"); } } }).addPrefixPath("/old", new HttpHandler() { @Override public void handleRequest(HttpServerExchange exchange) throws Exception { if(exchange.isInIoThread()) { exchange.dispatch(this); return; } exchange.startBlocking(); exchange.setProtocol(Protocols.HTTP_1_0); exchange.getOutputStream().write(RESPONSE_BODY.getBytes(StandardCharsets.US_ASCII)); exchange.getOutputStream().flush(); } })); } protected static final class SessionTestHandler implements HttpHandler { private final SessionCookieConfig sessionConfig; protected SessionTestHandler(SessionCookieConfig sessionConfig) { this.sessionConfig = sessionConfig; } @Override public void handleRequest(final HttpServerExchange exchange) throws Exception { final SessionManager manager = exchange.getAttachment(SessionManager.ATTACHMENT_KEY); Session session = manager.getSession(exchange, sessionConfig); if (session == null) { session = manager.createSession(exchange, sessionConfig); session.setAttribute(COUNT, 0); } Integer count = (Integer) session.getAttribute(COUNT); session.setAttribute(COUNT, count + 1); exchange.getResponseSender().send("" + count); } } protected static final class StringSendHandler implements HttpHandler { private final String serverName; protected StringSendHandler(String serverName) { this.serverName = serverName; } @Override public void handleRequest(final HttpServerExchange exchange) throws Exception { exchange.getResponseSender().send(serverName); } } }