diff options
author | gjoranv <gv@verizonmedia.com> | 2021-03-23 21:21:39 +0100 |
---|---|---|
committer | gjoranv <gv@verizonmedia.com> | 2021-03-23 23:13:01 +0100 |
commit | 5617a82f7a32ebcc37be226b27f6ff284f5c896d (patch) | |
tree | 45e8b3be33c372971c2b1349d245e223a1f46085 /jdisc_http_service/src/test/java/com/yahoo/jdisc/http/server/jetty | |
parent | 266046b2bb8bebbb683499f558a05aaf8a4f1ff3 (diff) |
Remove the jdisc_http_service module.
- It has been merged into container-core.
Diffstat (limited to 'jdisc_http_service/src/test/java/com/yahoo/jdisc/http/server/jetty')
22 files changed, 0 insertions, 4508 deletions
diff --git a/jdisc_http_service/src/test/java/com/yahoo/jdisc/http/server/jetty/AccessLogRequestLogTest.java b/jdisc_http_service/src/test/java/com/yahoo/jdisc/http/server/jetty/AccessLogRequestLogTest.java deleted file mode 100644 index 6370912af48..00000000000 --- a/jdisc_http_service/src/test/java/com/yahoo/jdisc/http/server/jetty/AccessLogRequestLogTest.java +++ /dev/null @@ -1,156 +0,0 @@ -// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. -package com.yahoo.jdisc.http.server.jetty; - -import com.yahoo.container.logging.AccessLogEntry; -import com.yahoo.container.logging.RequestLog; -import com.yahoo.container.logging.RequestLogEntry; -import com.yahoo.jdisc.http.ConnectorConfig; -import com.yahoo.jdisc.http.ServerConfig; -import org.eclipse.jetty.http.MetaData; -import org.eclipse.jetty.server.HttpChannel; -import org.eclipse.jetty.server.HttpConnection; -import org.eclipse.jetty.server.Request; -import org.eclipse.jetty.server.Response; -import org.junit.Test; - -import java.util.List; -import java.util.Optional; - -import static org.hamcrest.CoreMatchers.is; -import static org.hamcrest.CoreMatchers.not; -import static org.hamcrest.CoreMatchers.nullValue; -import static org.hamcrest.MatcherAssert.assertThat; -import static org.junit.Assert.assertFalse; -import static org.junit.Assert.assertTrue; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.when; - -/** - * @author Oyvind Bakksjo - * @author bjorncs - */ -public class AccessLogRequestLogTest { - @Test - public void requireThatQueryWithUnquotedSpecialCharactersIsHandled() { - final Request jettyRequest = createRequestMock(); - when(jettyRequest.getRequestURI()).thenReturn("/search/"); - when(jettyRequest.getQueryString()).thenReturn("query=year:>2010"); - - InMemoryRequestLog requestLog = new InMemoryRequestLog(); - doAccessLoggingOfRequest(requestLog, jettyRequest); - RequestLogEntry entry = requestLog.entries().get(0); - - assertThat(entry.rawPath().get(), is(not(nullValue()))); - assertTrue(entry.rawQuery().isPresent()); - } - - @Test - public void requireThatDoubleQuotingIsNotPerformed() { - final Request jettyRequest = createRequestMock(); - final String path = "/search/"; - when(jettyRequest.getRequestURI()).thenReturn(path); - final String query = "query=year%252010+%3B&customParameter=something"; - when(jettyRequest.getQueryString()).thenReturn(query); - - InMemoryRequestLog requestLog = new InMemoryRequestLog(); - doAccessLoggingOfRequest(requestLog, jettyRequest); - RequestLogEntry entry = requestLog.entries().get(0); - - assertThat(entry.rawPath().get(), is(path)); - assertThat(entry.rawQuery().get(), is(query)); - - } - - @Test - public void raw_path_and_query_are_set_from_request() { - Request jettyRequest = createRequestMock(); - String rawPath = "//search/"; - when(jettyRequest.getRequestURI()).thenReturn(rawPath); - String rawQuery = "q=%%2"; - when(jettyRequest.getQueryString()).thenReturn(rawQuery); - - InMemoryRequestLog requestLog = new InMemoryRequestLog(); - doAccessLoggingOfRequest(requestLog, jettyRequest); - RequestLogEntry entry = requestLog.entries().get(0); - assertThat(entry.rawPath().get(), is(rawPath)); - Optional<String> actualRawQuery = entry.rawQuery(); - assertThat(actualRawQuery.isPresent(), is(true)); - assertThat(actualRawQuery.get(), is(rawQuery)); - } - - @Test - public void verify_x_forwarded_for_precedence () { - Request jettyRequest = createRequestMock(); - when(jettyRequest.getRequestURI()).thenReturn("//search/"); - when(jettyRequest.getQueryString()).thenReturn("q=%%2"); - when(jettyRequest.getHeader("x-forwarded-for")).thenReturn("1.2.3.4"); - when(jettyRequest.getHeader("y-ra")).thenReturn("2.3.4.5"); - - InMemoryRequestLog requestLog = new InMemoryRequestLog(); - doAccessLoggingOfRequest(requestLog, jettyRequest); - RequestLogEntry entry = requestLog.entries().get(0); - assertThat(entry.remoteAddress().get(), is("1.2.3.4")); - } - - @Test - public void verify_x_forwarded_port_precedence () { - Request jettyRequest = createRequestMock(); - when(jettyRequest.getRequestURI()).thenReturn("//search/"); - when(jettyRequest.getQueryString()).thenReturn("q=%%2"); - when(jettyRequest.getHeader("X-Forwarded-Port")).thenReturn("80"); - when(jettyRequest.getHeader("y-rp")).thenReturn("8080"); - - InMemoryRequestLog requestLog = new InMemoryRequestLog(); - doAccessLoggingOfRequest(requestLog, jettyRequest); - RequestLogEntry entry = requestLog.entries().get(0); - assertThat(entry.remotePort().getAsInt(), is(80)); - } - - @Test - public void defaults_to_peer_port_if_remote_port_header_is_invalid() { - final Request jettyRequest = createRequestMock(); - when(jettyRequest.getRequestURI()).thenReturn("/search/"); - when(jettyRequest.getHeader("X-Forwarded-Port")).thenReturn("8o8o"); - when(jettyRequest.getRemotePort()).thenReturn(80); - - InMemoryRequestLog requestLog = new InMemoryRequestLog(); - doAccessLoggingOfRequest(requestLog, jettyRequest); - RequestLogEntry entry = requestLog.entries().get(0); - assertFalse(entry.remotePort().isPresent()); - assertThat(entry.peerPort().getAsInt(), is(80)); - } - - private void doAccessLoggingOfRequest(RequestLog requestLog, Request jettyRequest) { - ServerConfig.AccessLog config = new ServerConfig.AccessLog( - new ServerConfig.AccessLog.Builder() - .remoteAddressHeaders(List.of("x-forwarded-for", "y-ra")) - .remotePortHeaders(List.of("X-Forwarded-Port", "y-rp"))); - new AccessLogRequestLog(requestLog, config).log(jettyRequest, createResponseMock()); - } - - private static Request createRequestMock() { - JDiscServerConnector serverConnector = mock(JDiscServerConnector.class); - int localPort = 1234; - when(serverConnector.connectorConfig()).thenReturn(new ConnectorConfig(new ConnectorConfig.Builder().listenPort(localPort))); - when(serverConnector.getLocalPort()).thenReturn(localPort); - HttpConnection httpConnection = mock(HttpConnection.class); - when(httpConnection.getConnector()).thenReturn(serverConnector); - Request request = mock(Request.class); - when(request.getMethod()).thenReturn("GET"); - when(request.getRemoteAddr()).thenReturn("localhost"); - when(request.getRemotePort()).thenReturn(12345); - when(request.getProtocol()).thenReturn("HTTP/1.1"); - when(request.getScheme()).thenReturn("http"); - when(request.getTimeStamp()).thenReturn(0L); - when(request.getAttribute(JDiscHttpServlet.ATTRIBUTE_NAME_ACCESS_LOG_ENTRY)).thenReturn(new AccessLogEntry()); - when(request.getAttribute("org.eclipse.jetty.server.HttpConnection")).thenReturn(httpConnection); - return request; - } - - private Response createResponseMock() { - Response response = mock(Response.class); - when(response.getHttpChannel()).thenReturn(mock(HttpChannel.class)); - when(response.getCommittedMetaData()).thenReturn(mock(MetaData.Response.class)); - return response; - } -} diff --git a/jdisc_http_service/src/test/java/com/yahoo/jdisc/http/server/jetty/BlockingQueueRequestLog.java b/jdisc_http_service/src/test/java/com/yahoo/jdisc/http/server/jetty/BlockingQueueRequestLog.java deleted file mode 100644 index c1a2bea8ac4..00000000000 --- a/jdisc_http_service/src/test/java/com/yahoo/jdisc/http/server/jetty/BlockingQueueRequestLog.java +++ /dev/null @@ -1,24 +0,0 @@ -// Copyright Verizon Media. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. -package com.yahoo.jdisc.http.server.jetty; - -import com.yahoo.container.logging.RequestLog; -import com.yahoo.container.logging.RequestLogEntry; - -import java.time.Duration; -import java.util.concurrent.BlockingQueue; -import java.util.concurrent.LinkedBlockingDeque; -import java.util.concurrent.TimeUnit; - -/** - * @author bjorncs - */ -class BlockingQueueRequestLog implements RequestLog { - - private final BlockingQueue<RequestLogEntry> entries = new LinkedBlockingDeque<>(); - - @Override public void log(RequestLogEntry entry) { entries.offer(entry); } - - RequestLogEntry poll(Duration timeout) throws InterruptedException { - return entries.poll(timeout.toMillis(), TimeUnit.MILLISECONDS); - } -} diff --git a/jdisc_http_service/src/test/java/com/yahoo/jdisc/http/server/jetty/ConnectionThrottlerTest.java b/jdisc_http_service/src/test/java/com/yahoo/jdisc/http/server/jetty/ConnectionThrottlerTest.java deleted file mode 100644 index 65eb7e1c145..00000000000 --- a/jdisc_http_service/src/test/java/com/yahoo/jdisc/http/server/jetty/ConnectionThrottlerTest.java +++ /dev/null @@ -1,78 +0,0 @@ -// Copyright 2019 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. -package com.yahoo.jdisc.http.server.jetty; - -import com.yahoo.jdisc.http.ConnectorConfig; -import org.eclipse.jetty.server.AbstractConnector; -import org.eclipse.jetty.util.component.AbstractLifeCycle; -import org.eclipse.jetty.util.statistic.RateStatistic; -import org.eclipse.jetty.util.thread.Scheduler; -import org.junit.Test; - -import java.util.concurrent.TimeUnit; - -import static org.junit.Assert.assertNotNull; -import static org.mockito.ArgumentMatchers.anyBoolean; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.when; -import static org.mockito.internal.verification.VerificationModeFactory.times; - -/** - * @author bjorncs - */ -public class ConnectionThrottlerTest { - - @Test - public void throttles_when_any_resource_check_exceeds_configured_threshold() { - Runtime runtime = mock(Runtime.class); - when(runtime.maxMemory()).thenReturn(100l); - RateStatistic rateStatistic = new RateStatistic(1, TimeUnit.HOURS); - MockScheduler scheduler = new MockScheduler(); - ConnectorConfig.Throttling config = new ConnectorConfig.Throttling(new ConnectorConfig.Throttling.Builder() - .maxHeapUtilization(0.8) - .maxAcceptRate(1)); - - AbstractConnector connector = mock(AbstractConnector.class); - - ConnectionThrottler throttler = new ConnectionThrottler(runtime, rateStatistic, scheduler, connector, config); - - // Heap utilization above configured threshold, but connection rate below threshold. - when(runtime.freeMemory()).thenReturn(10l); - when(connector.isAccepting()).thenReturn(true); - throttler.onAccepting(null); - assertNotNull(scheduler.task); - verify(connector).setAccepting(false); - - // Heap utilization below threshold, but connection rate above threshold. - when(runtime.freeMemory()).thenReturn(80l); - rateStatistic.record(); - rateStatistic.record(); // above accept rate limit (2 > 1) - scheduler.task.run(); // run unthrottleIfBelowThresholds() - verify(connector, times(1)).setAccepting(anyBoolean()); // verify setAccepting has not been called any mores times - - // Both heap utilization and accept rate below threshold - when(runtime.freeMemory()).thenReturn(80l); - when(connector.isAccepting()).thenReturn(false); - rateStatistic.reset(); - scheduler.task.run(); // run unthrottleIfBelowThresholds() - verify(connector).setAccepting(true); - - // Both heap utilization and accept rate below threshold - when(connector.isAccepting()).thenReturn(true); - when(runtime.freeMemory()).thenReturn(80l); - rateStatistic.record(); - throttler.onAccepting(null); - verify(connector, times(2)).setAccepting(anyBoolean()); // verify setAccepting has not been called any mores times - } - - private static class MockScheduler extends AbstractLifeCycle implements Scheduler { - Runnable task; - - @Override - public Task schedule(Runnable task, long delay, TimeUnit units) { - this.task = task; - return () -> false; - } - } - -}
\ No newline at end of file diff --git a/jdisc_http_service/src/test/java/com/yahoo/jdisc/http/server/jetty/ConnectorFactoryTest.java b/jdisc_http_service/src/test/java/com/yahoo/jdisc/http/server/jetty/ConnectorFactoryTest.java deleted file mode 100644 index df794c7ecb8..00000000000 --- a/jdisc_http_service/src/test/java/com/yahoo/jdisc/http/server/jetty/ConnectorFactoryTest.java +++ /dev/null @@ -1,83 +0,0 @@ -// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. -package com.yahoo.jdisc.http.server.jetty; - -import com.yahoo.jdisc.Metric; -import com.yahoo.jdisc.http.ConnectorConfig; -import com.yahoo.jdisc.http.ServerConfig; -import com.yahoo.jdisc.http.ssl.impl.ConfiguredSslContextFactoryProvider; -import org.eclipse.jetty.server.Request; -import org.eclipse.jetty.server.Server; -import org.eclipse.jetty.server.handler.AbstractHandler; -import org.junit.Test; - -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; -import java.io.IOException; -import java.util.Map; - -import static org.hamcrest.CoreMatchers.equalTo; - -/** - * @author Einar M R Rosenvinge - */ -public class ConnectorFactoryTest { - - @Test - public void requireThatServerCanBindChannel() throws Exception { - Server server = new Server(); - try { - ConnectorConfig config = new ConnectorConfig(new ConnectorConfig.Builder()); - ConnectorFactory factory = createConnectorFactory(config); - JettyConnectionLogger connectionLogger = new JettyConnectionLogger( - new ServerConfig.ConnectionLog.Builder().enabled(false).build(), - new VoidConnectionLog()); - JDiscServerConnector connector = - (JDiscServerConnector)factory.createConnector(new DummyMetric(), server, connectionLogger); - server.addConnector(connector); - server.setHandler(new HelloWorldHandler()); - server.start(); - - SimpleHttpClient client = new SimpleHttpClient(null, connector.getLocalPort(), false); - SimpleHttpClient.RequestExecutor ex = client.newGet("/blaasdfnb"); - SimpleHttpClient.ResponseValidator val = ex.execute(); - val.expectContent(equalTo("Hello world")); - } finally { - try { - server.stop(); - } catch (Exception e) { - //ignore - } - } - } - - private static ConnectorFactory createConnectorFactory(ConnectorConfig config) { - return new ConnectorFactory(config, new ConfiguredSslContextFactoryProvider(config)); - } - - private static class HelloWorldHandler extends AbstractHandler { - @Override - public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException { - response.getWriter().write("Hello world"); - response.getWriter().flush(); - response.getWriter().close(); - baseRequest.setHandled(true); - } - } - - private static class DummyMetric implements Metric { - @Override - public void set(String key, Number val, Context ctx) { } - - @Override - public void add(String key, Number val, Context ctx) { } - - @Override - public Context createContext(Map<String, ?> properties) { - return new DummyContext(); - } - } - - private static class DummyContext implements Metric.Context { - } - -} diff --git a/jdisc_http_service/src/test/java/com/yahoo/jdisc/http/server/jetty/ErrorResponseContentCreatorTest.java b/jdisc_http_service/src/test/java/com/yahoo/jdisc/http/server/jetty/ErrorResponseContentCreatorTest.java deleted file mode 100644 index d66f22801f7..00000000000 --- a/jdisc_http_service/src/test/java/com/yahoo/jdisc/http/server/jetty/ErrorResponseContentCreatorTest.java +++ /dev/null @@ -1,44 +0,0 @@ -// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. -package com.yahoo.jdisc.http.server.jetty; - - -import org.junit.Test; - -import javax.servlet.http.HttpServletResponse; -import java.nio.charset.StandardCharsets; -import java.util.Optional; - -import static org.junit.Assert.assertEquals; - - -/** - * @author bjorncs - */ -public class ErrorResponseContentCreatorTest { - - @Test - public void response_content_matches_expected_string() { - String expectedHtml = - "<html>\n" + - "<head>\n" + - "<meta http-equiv=\"Content-Type\" content=\"text/html;charset=ISO-8859-1\"/>\n" + - "<title>Error 200</title>\n" + - "</head>\n" + - "<body>\n" + - "<h2>HTTP ERROR: 200</h2>\n" + - "<p>Problem accessing http://foo.bar. Reason:\n" + - "<pre> My custom error message</pre></p>\n" + - "<hr/>\n" + - "</body>\n" + - "</html>\n"; - - ErrorResponseContentCreator c = new ErrorResponseContentCreator(); - byte[] rawContent = c.createErrorContent( - "http://foo.bar", - HttpServletResponse.SC_OK, - Optional.of("My custom error message")); - String actualHtml = new String(rawContent, StandardCharsets.ISO_8859_1); - assertEquals(expectedHtml, actualHtml); - } - -} diff --git a/jdisc_http_service/src/test/java/com/yahoo/jdisc/http/server/jetty/ExceptionWrapperTest.java b/jdisc_http_service/src/test/java/com/yahoo/jdisc/http/server/jetty/ExceptionWrapperTest.java deleted file mode 100644 index de8df283afe..00000000000 --- a/jdisc_http_service/src/test/java/com/yahoo/jdisc/http/server/jetty/ExceptionWrapperTest.java +++ /dev/null @@ -1,51 +0,0 @@ -// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. -package com.yahoo.jdisc.http.server.jetty; - -import org.junit.Test; - -import static org.hamcrest.CoreMatchers.equalTo; -import static org.hamcrest.MatcherAssert.assertThat; - -/** - * Check basic error message formatting. Do note these tests are sensitive to - * the line numbering in this file. (And that's a feature, not a bug.) - * - * @author <a href="mailto:steinar@yahoo-inc.com">Steinar Knutsen</a> - */ -public class ExceptionWrapperTest { - - @Test - public final void requireNoMessageIsOK() { - final Throwable t = new Throwable(); - final ExceptionWrapper e = new ExceptionWrapper(t); - final String expected = "Throwable() at com.yahoo.jdisc.http.server.jetty.ExceptionWrapperTest(ExceptionWrapperTest.java:19)"; - - assertThat(e.getMessage(), equalTo(expected)); - } - - @Test - public final void requireAllWrappedLevelsShowUp() { - final Throwable t0 = new Throwable("t0"); - final Throwable t1 = new Throwable("t1", t0); - final Throwable t2 = new Throwable("t2", t1); - final ExceptionWrapper e = new ExceptionWrapper(t2); - final String expected = "Throwable(\"t2\") at com.yahoo.jdisc.http.server.jetty.ExceptionWrapperTest(ExceptionWrapperTest.java:30):" - + " Throwable(\"t1\") at com.yahoo.jdisc.http.server.jetty.ExceptionWrapperTest(ExceptionWrapperTest.java:29):" - + " Throwable(\"t0\") at com.yahoo.jdisc.http.server.jetty.ExceptionWrapperTest(ExceptionWrapperTest.java:28)"; - - assertThat(e.getMessage(), equalTo(expected)); - } - - @Test - public final void requireMixOfMessageAndNoMessageWorks() { - final Throwable t0 = new Throwable("t0"); - final Throwable t1 = new Throwable(t0); - final Throwable t2 = new Throwable("t2", t1); - final ExceptionWrapper e = new ExceptionWrapper(t2); - final String expected = "Throwable(\"t2\") at com.yahoo.jdisc.http.server.jetty.ExceptionWrapperTest(ExceptionWrapperTest.java:43):" - + " Throwable(\"java.lang.Throwable: t0\") at com.yahoo.jdisc.http.server.jetty.ExceptionWrapperTest(ExceptionWrapperTest.java:42):" - + " Throwable(\"t0\") at com.yahoo.jdisc.http.server.jetty.ExceptionWrapperTest(ExceptionWrapperTest.java:41)"; - - assertThat(e.getMessage(), equalTo(expected)); - } -} diff --git a/jdisc_http_service/src/test/java/com/yahoo/jdisc/http/server/jetty/FilterTestCase.java b/jdisc_http_service/src/test/java/com/yahoo/jdisc/http/server/jetty/FilterTestCase.java deleted file mode 100644 index a67656dd5ca..00000000000 --- a/jdisc_http_service/src/test/java/com/yahoo/jdisc/http/server/jetty/FilterTestCase.java +++ /dev/null @@ -1,667 +0,0 @@ -// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. -package com.yahoo.jdisc.http.server.jetty; - -import com.google.inject.AbstractModule; -import com.google.inject.util.Modules; -import com.yahoo.container.logging.ConnectionLog; -import com.yahoo.container.logging.RequestLog; -import com.yahoo.jdisc.AbstractResource; -import com.yahoo.jdisc.Request; -import com.yahoo.jdisc.ResourceReference; -import com.yahoo.jdisc.Response; -import com.yahoo.jdisc.handler.AbstractRequestHandler; -import com.yahoo.jdisc.handler.CompletionHandler; -import com.yahoo.jdisc.handler.ContentChannel; -import com.yahoo.jdisc.handler.ResponseDispatch; -import com.yahoo.jdisc.handler.ResponseHandler; -import com.yahoo.jdisc.http.ConnectorConfig; -import com.yahoo.jdisc.http.HttpRequest; -import com.yahoo.jdisc.http.HttpResponse; -import com.yahoo.jdisc.http.ServerConfig; -import com.yahoo.jdisc.http.ServletPathsConfig; -import com.yahoo.jdisc.http.filter.RequestFilter; -import com.yahoo.jdisc.http.filter.ResponseFilter; -import com.yahoo.jdisc.http.filter.ResponseHeaderFilter; -import com.yahoo.jdisc.http.filter.chain.RequestFilterChain; -import com.yahoo.jdisc.http.filter.chain.ResponseFilterChain; -import com.yahoo.jdisc.http.guiceModules.ConnectorFactoryRegistryModule; -import org.junit.Test; -import org.mockito.ArgumentCaptor; - -import java.io.IOException; -import java.nio.ByteBuffer; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.concurrent.CountDownLatch; -import java.util.concurrent.TimeUnit; -import java.util.concurrent.atomic.AtomicReference; - -import static org.hamcrest.CoreMatchers.containsString; -import static org.hamcrest.CoreMatchers.is; -import static org.hamcrest.MatcherAssert.assertThat; -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.ArgumentMatchers.isNull; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.never; -import static org.mockito.Mockito.times; -import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.when; - -/** - * @author Oyvind Bakksjo - * @author bjorncs - */ -public class FilterTestCase { - @Test - public void requireThatRequestFilterIsNotRunOnUnboundPath() throws Exception { - RequestFilterMockBase filter = mock(RequestFilterMockBase.class); - FilterBindings filterBindings = new FilterBindings.Builder() - .addRequestFilter("my-request-filter", filter) - .addRequestFilterBinding("my-request-filter", "http://*/filtered/*") - .build(); - final MyRequestHandler requestHandler = new MyRequestHandler(); - final TestDriver testDriver = newDriver(requestHandler, filterBindings); - - testDriver.client().get("/status.html"); - - assertThat(requestHandler.awaitInvocation(), is(true)); - verify(filter, never()).filter(any(HttpRequest.class), any(ResponseHandler.class)); - - assertThat(testDriver.close(), is(true)); - } - - @Test - public void requireThatRequestFilterIsRunOnBoundPath() throws Exception { - final RequestFilter filter = mock(RequestFilterMockBase.class); - FilterBindings filterBindings = new FilterBindings.Builder() - .addRequestFilter("my-request-filter", filter) - .addRequestFilterBinding("my-request-filter", "http://*/filtered/*") - .build(); - final MyRequestHandler requestHandler = new MyRequestHandler(); - final TestDriver testDriver = newDriver(requestHandler, filterBindings); - - testDriver.client().get("/filtered/status.html"); - - assertThat(requestHandler.awaitInvocation(), is(true)); - verify(filter, times(1)).filter(any(HttpRequest.class), any(ResponseHandler.class)); - - assertThat(testDriver.close(), is(true)); - } - - @Test - public void requireThatRequestFilterChangesAreSeenByRequestHandler() throws Exception { - final RequestFilter filter = new HeaderRequestFilter("foo", "bar"); - FilterBindings filterBindings = new FilterBindings.Builder() - .addRequestFilter("my-request-filter", filter) - .addRequestFilterBinding("my-request-filter", "http://*/*") - .build(); - final MyRequestHandler requestHandler = new MyRequestHandler(); - final TestDriver testDriver = newDriver(requestHandler, filterBindings); - - testDriver.client().get("status.html"); - - assertThat(requestHandler.awaitInvocation(), is(true)); - assertThat(requestHandler.getHeaderMap().get("foo").get(0), is("bar")); - - assertThat(testDriver.close(), is(true)); - } - - @Test - public void requireThatRequestFilterCanRespond() throws Exception { - FilterBindings filterBindings = new FilterBindings.Builder() - .addRequestFilter("my-request-filter", new RespondForbiddenFilter()) - .addRequestFilterBinding("my-request-filter", "http://*/*") - .build(); - final MyRequestHandler requestHandler = new MyRequestHandler(); - final TestDriver testDriver = newDriver(requestHandler, filterBindings); - - testDriver.client().get("/status.html").expectStatusCode(is(Response.Status.FORBIDDEN)); - - assertThat(requestHandler.hasBeenInvokedYet(), is(false)); - - assertThat(testDriver.close(), is(true)); - } - - @Test - public void requireThatFilterCanHaveNullCompletionHandler() throws Exception { - final int responseStatus = Response.Status.OK; - final String responseMessage = "Excellent"; - FilterBindings filterBindings = new FilterBindings.Builder() - .addRequestFilter("my-request-filter", new NullCompletionHandlerFilter(responseStatus, responseMessage)) - .addRequestFilterBinding("my-request-filter", "http://*/*") - .build(); - final MyRequestHandler requestHandler = new MyRequestHandler(); - final TestDriver testDriver = newDriver(requestHandler, filterBindings); - - testDriver.client().get("/status.html") - .expectStatusCode(is(responseStatus)) - .expectContent(is(responseMessage)); - - assertThat(requestHandler.hasBeenInvokedYet(), is(false)); - - assertThat(testDriver.close(), is(true)); - } - - @Test - public void requireThatRequestFilterExecutionIsExceptionSafe() throws Exception { - FilterBindings filterBindings = new FilterBindings.Builder() - .addRequestFilter("my-request-filter", new ThrowingRequestFilter()) - .addRequestFilterBinding("my-request-filter", "http://*/*") - .build(); - final MyRequestHandler requestHandler = new MyRequestHandler(); - final TestDriver testDriver = newDriver(requestHandler, filterBindings); - - testDriver.client().get("/status.html").expectStatusCode(is(Response.Status.INTERNAL_SERVER_ERROR)); - - assertThat(requestHandler.hasBeenInvokedYet(), is(false)); - - assertThat(testDriver.close(), is(true)); - } - - @Test - public void requireThatResponseFilterIsNotRunOnUnboundPath() throws Exception { - final ResponseFilter filter = mock(ResponseFilterMockBase.class); - FilterBindings filterBindings = new FilterBindings.Builder() - .addResponseFilter("my-response-filter", filter) - .addResponseFilterBinding("my-response-filter", "http://*/filtered/*") - .build(); - final MyRequestHandler requestHandler = new MyRequestHandler(); - final TestDriver testDriver = newDriver(requestHandler, filterBindings); - - testDriver.client().get("/status.html"); - - assertThat(requestHandler.awaitInvocation(), is(true)); - verify(filter, never()).filter(any(Response.class), any(Request.class)); - - assertThat(testDriver.close(), is(true)); - } - - @Test - public void requireThatResponseFilterIsRunOnBoundPath() throws Exception { - final ResponseFilter filter = mock(ResponseFilterMockBase.class); - FilterBindings filterBindings = new FilterBindings.Builder() - .addResponseFilter("my-response-filter", filter) - .addResponseFilterBinding("my-response-filter", "http://*/filtered/*") - .build(); - final MyRequestHandler requestHandler = new MyRequestHandler(); - final TestDriver testDriver = newDriver(requestHandler, filterBindings); - - testDriver.client().get("/filtered/status.html"); - - assertThat(requestHandler.awaitInvocation(), is(true)); - verify(filter, times(1)).filter(any(Response.class), any(Request.class)); - - assertThat(testDriver.close(), is(true)); - } - - @Test - public void requireThatResponseFilterChangesAreWrittenToResponse() throws Exception { - FilterBindings filterBindings = new FilterBindings.Builder() - .addResponseFilter("my-response-filter", new HeaderResponseFilter("foo", "bar")) - .addResponseFilterBinding("my-response-filter", "http://*/*") - .build(); - final MyRequestHandler requestHandler = new MyRequestHandler(); - final TestDriver testDriver = newDriver(requestHandler, filterBindings); - - testDriver.client().get("/status.html") - .expectHeader("foo", is("bar")); - - assertThat(requestHandler.awaitInvocation(), is(true)); - - assertThat(testDriver.close(), is(true)); - } - - @Test - public void requireThatResponseFilterExecutionIsExceptionSafe() throws Exception { - FilterBindings filterBindings = new FilterBindings.Builder() - .addResponseFilter("my-response-filter", new ThrowingResponseFilter()) - .addResponseFilterBinding("my-response-filter", "http://*/*") - .build(); - final MyRequestHandler requestHandler = new MyRequestHandler(); - final TestDriver testDriver = newDriver(requestHandler, filterBindings); - - testDriver.client().get("/status.html").expectStatusCode(is(Response.Status.INTERNAL_SERVER_ERROR)); - - assertThat(requestHandler.awaitInvocation(), is(true)); - - assertThat(testDriver.close(), is(true)); - } - - @Test - public void requireThatRequestFilterAndResponseFilterCanBindToSamePath() throws Exception { - final RequestFilter requestFilter = mock(RequestFilterMockBase.class); - final ResponseFilter responseFilter = mock(ResponseFilterMockBase.class); - final String uriPattern = "http://*/*"; - FilterBindings filterBindings = new FilterBindings.Builder() - .addRequestFilter("my-request-filter", requestFilter) - .addRequestFilterBinding("my-request-filter", uriPattern) - .addResponseFilter("my-response-filter", responseFilter) - .addResponseFilterBinding("my-response-filter", uriPattern) - .build(); - final MyRequestHandler requestHandler = new MyRequestHandler(); - final TestDriver testDriver = newDriver(requestHandler, filterBindings); - - testDriver.client().get("/status.html"); - - assertThat(requestHandler.awaitInvocation(), is(true)); - verify(requestFilter, times(1)).filter(any(HttpRequest.class), any(ResponseHandler.class)); - verify(responseFilter, times(1)).filter(any(Response.class), any(Request.class)); - - assertThat(testDriver.close(), is(true)); - } - - @Test - public void requireThatResponseFromRequestFilterGoesThroughResponseFilter() throws Exception { - FilterBindings filterBindings = new FilterBindings.Builder() - .addRequestFilter("my-request-filter", new RespondForbiddenFilter()) - .addRequestFilterBinding("my-request-filter", "http://*/*") - .addResponseFilter("my-response-filter", new HeaderResponseFilter("foo", "bar")) - .addResponseFilterBinding("my-response-filter", "http://*/*") - .build(); - final MyRequestHandler requestHandler = new MyRequestHandler(); - final TestDriver testDriver = newDriver(requestHandler, filterBindings); - - testDriver.client().get("/status.html") - .expectStatusCode(is(Response.Status.FORBIDDEN)) - .expectHeader("foo", is("bar")); - - assertThat(requestHandler.hasBeenInvokedYet(), is(false)); - - assertThat(testDriver.close(), is(true)); - } - - @Test - public void requireThatRequestFilterChainRetainsFilters() { - final RequestFilter requestFilter1 = mock(RequestFilter.class); - final RequestFilter requestFilter2 = mock(RequestFilter.class); - - verify(requestFilter1, never()).refer(); - verify(requestFilter2, never()).refer(); - final ResourceReference reference1 = mock(ResourceReference.class); - final ResourceReference reference2 = mock(ResourceReference.class); - when(requestFilter1.refer()).thenReturn(reference1); - when(requestFilter2.refer()).thenReturn(reference2); - final RequestFilter chain = RequestFilterChain.newInstance(requestFilter1, requestFilter2); - verify(requestFilter1, times(1)).refer(); - verify(requestFilter2, times(1)).refer(); - - verify(reference1, never()).close(); - verify(reference2, never()).close(); - chain.release(); - verify(reference1, times(1)).close(); - verify(reference2, times(1)).close(); - } - - @Test - public void requireThatRequestFilterChainIsRun() throws Exception { - final RequestFilter requestFilter1 = mock(RequestFilter.class); - final RequestFilter requestFilter2 = mock(RequestFilter.class); - final RequestFilter requestFilterChain = RequestFilterChain.newInstance(requestFilter1, requestFilter2); - final HttpRequest request = null; - final ResponseHandler responseHandler = null; - requestFilterChain.filter(request, responseHandler); - verify(requestFilter1).filter(isNull(), any(ResponseHandler.class)); - verify(requestFilter2).filter(isNull(), any(ResponseHandler.class)); - } - - @Test - public void requireThatRequestFilterChainCallsFilterWithOriginalRequest() throws Exception { - final RequestFilter requestFilter = mock(RequestFilter.class); - final RequestFilter requestFilterChain = RequestFilterChain.newInstance(requestFilter); - final HttpRequest request = mock(HttpRequest.class); - final ResponseHandler responseHandler = null; - requestFilterChain.filter(request, responseHandler); - - // Check that the filter is called with the same request argument as the chain was, - // in a manner that allows the request object to be wrapped. - final ArgumentCaptor<HttpRequest> requestCaptor = ArgumentCaptor.forClass(HttpRequest.class); - verify(requestFilter).filter(requestCaptor.capture(), isNull()); - verify(request, never()).getUri(); - requestCaptor.getValue().getUri(); - verify(request, times(1)).getUri(); - } - - @Test - public void requireThatRequestFilterChainCallsFilterWithOriginalResponseHandler() throws Exception { - final RequestFilter requestFilter = mock(RequestFilter.class); - final RequestFilter requestFilterChain = RequestFilterChain.newInstance(requestFilter); - final HttpRequest request = null; - final ResponseHandler responseHandler = mock(ResponseHandler.class); - requestFilterChain.filter(request, responseHandler); - - // Check that the filter is called with the same response handler argument as the chain was, - // in a manner that allows the handler object to be wrapped. - final ArgumentCaptor<ResponseHandler> responseHandlerCaptor = ArgumentCaptor.forClass(ResponseHandler.class); - verify(requestFilter).filter(isNull(), responseHandlerCaptor.capture()); - verify(responseHandler, never()).handleResponse(any(Response.class)); - responseHandlerCaptor.getValue().handleResponse(mock(Response.class)); - verify(responseHandler, times(1)).handleResponse(any(Response.class)); - } - - @Test - public void requireThatRequestFilterCanTerminateChain() throws Exception { - final RequestFilter requestFilter1 = new RespondForbiddenFilter(); - final RequestFilter requestFilter2 = mock(RequestFilter.class); - final RequestFilter requestFilterChain = RequestFilterChain.newInstance(requestFilter1, requestFilter2); - final HttpRequest request = null; - final ResponseHandler responseHandler = mock(ResponseHandler.class); - when(responseHandler.handleResponse(any(Response.class))).thenReturn(mock(ContentChannel.class)); - - requestFilterChain.filter(request, responseHandler); - - verify(requestFilter2, never()).filter(any(HttpRequest.class), any(ResponseHandler.class)); - - final ArgumentCaptor<Response> responseCaptor = ArgumentCaptor.forClass(Response.class); - verify(responseHandler).handleResponse(responseCaptor.capture()); - assertThat(responseCaptor.getValue().getStatus(), is(Response.Status.FORBIDDEN)); - } - - @Test - public void requireThatResponseFilterChainRetainsFilters() { - final ResponseFilter responseFilter1 = mock(ResponseFilter.class); - final ResponseFilter responseFilter2 = mock(ResponseFilter.class); - - verify(responseFilter1, never()).refer(); - verify(responseFilter2, never()).refer(); - final ResourceReference reference1 = mock(ResourceReference.class); - final ResourceReference reference2 = mock(ResourceReference.class); - when(responseFilter1.refer()).thenReturn(reference1); - when(responseFilter2.refer()).thenReturn(reference2); - final ResponseFilter chain = ResponseFilterChain.newInstance(responseFilter1, responseFilter2); - verify(responseFilter1, times(1)).refer(); - verify(responseFilter2, times(1)).refer(); - - verify(reference1, never()).close(); - verify(reference2, never()).close(); - chain.release(); - verify(reference1, times(1)).close(); - verify(reference2, times(1)).close(); - } - - @Test - public void requireThatResponseFilterChainIsRun() { - final ResponseFilter responseFilter1 = new ResponseHeaderFilter("foo", "bar"); - final ResponseFilter responseFilter2 = mock(ResponseFilter.class); - final int statusCode = Response.Status.BAD_GATEWAY; - final Response response = new Response(statusCode); - final Request request = null; - - ResponseFilterChain.newInstance(responseFilter1, responseFilter2).filter(response, request); - - final ArgumentCaptor<Response> responseCaptor = ArgumentCaptor.forClass(Response.class); - verify(responseFilter2).filter(responseCaptor.capture(), isNull()); - assertThat(responseCaptor.getValue().getStatus(), is(statusCode)); - assertThat(responseCaptor.getValue().headers().getFirst("foo"), is("bar")); - - assertThat(response.getStatus(), is(statusCode)); - assertThat(response.headers().getFirst("foo"), is("bar")); - } - - @Test - public void requireThatDefaultRequestFilterChainIsRunIfNoOtherFilterChainMatches() throws IOException, InterruptedException { - RequestFilter filterWithBinding = mock(RequestFilter.class); - RequestFilter defaultFilter = mock(RequestFilter.class); - String defaultFilterId = "default-request-filter"; - FilterBindings filterBindings = new FilterBindings.Builder() - .addRequestFilter("my-request-filter", filterWithBinding) - .addRequestFilterBinding("my-request-filter", "http://*/filtered/*") - .addRequestFilter(defaultFilterId, defaultFilter) - .setRequestFilterDefaultForPort(defaultFilterId, 0) - .build(); - MyRequestHandler requestHandler = new MyRequestHandler(); - TestDriver testDriver = newDriver(requestHandler, filterBindings); - - testDriver.client().get("/status.html"); - - assertThat(requestHandler.awaitInvocation(), is(true)); - verify(defaultFilter, times(1)).filter(any(HttpRequest.class), any(ResponseHandler.class)); - verify(filterWithBinding, never()).filter(any(HttpRequest.class), any(ResponseHandler.class)); - - assertThat(testDriver.close(), is(true)); - } - - @Test - public void requireThatDefaultResponseFilterChainIsRunIfNoOtherFilterChainMatches() throws IOException, InterruptedException { - ResponseFilter filterWithBinding = mock(ResponseFilter.class); - ResponseFilter defaultFilter = mock(ResponseFilter.class); - String defaultFilterId = "default-response-filter"; - FilterBindings filterBindings = new FilterBindings.Builder() - .addResponseFilter("my-response-filter", filterWithBinding) - .addResponseFilterBinding("my-response-filter", "http://*/filtered/*") - .addResponseFilter(defaultFilterId, defaultFilter) - .setResponseFilterDefaultForPort(defaultFilterId, 0) - .build(); - MyRequestHandler requestHandler = new MyRequestHandler(); - TestDriver testDriver = newDriver(requestHandler, filterBindings); - - testDriver.client().get("/status.html"); - - assertThat(requestHandler.awaitInvocation(), is(true)); - verify(defaultFilter, times(1)).filter(any(Response.class), any(Request.class)); - verify(filterWithBinding, never()).filter(any(Response.class), any(Request.class)); - - assertThat(testDriver.close(), is(true)); - } - - @Test - public void requireThatRequestFilterWithBindingMatchHasPrecedenceOverDefaultFilter() throws IOException, InterruptedException { - RequestFilterMockBase filterWithBinding = mock(RequestFilterMockBase.class); - RequestFilterMockBase defaultFilter = mock(RequestFilterMockBase.class); - String defaultFilterId = "default-request-filter"; - FilterBindings filterBindings = new FilterBindings.Builder() - .addRequestFilter("my-request-filter", filterWithBinding) - .addRequestFilterBinding("my-request-filter", "http://*/filtered/*") - .addRequestFilter(defaultFilterId, defaultFilter) - .setRequestFilterDefaultForPort(defaultFilterId, 0) - .build(); - MyRequestHandler requestHandler = new MyRequestHandler(); - TestDriver testDriver = newDriver(requestHandler, filterBindings); - - testDriver.client().get("/filtered/status.html"); - - assertThat(requestHandler.awaitInvocation(), is(true)); - verify(defaultFilter, never()).filter(any(HttpRequest.class), any(ResponseHandler.class)); - verify(filterWithBinding).filter(any(HttpRequest.class), any(ResponseHandler.class)); - - assertThat(testDriver.close(), is(true)); - } - - @Test - public void requireThatResponseFilterWithBindingMatchHasPrecedenceOverDefaultFilter() throws IOException, InterruptedException { - ResponseFilter filterWithBinding = mock(ResponseFilter.class); - ResponseFilter defaultFilter = mock(ResponseFilter.class); - String defaultFilterId = "default-response-filter"; - FilterBindings filterBindings = new FilterBindings.Builder() - .addResponseFilter("my-response-filter", filterWithBinding) - .addResponseFilterBinding("my-response-filter", "http://*/filtered/*") - .addResponseFilter(defaultFilterId, defaultFilter) - .setResponseFilterDefaultForPort(defaultFilterId, 0) - .build(); - MyRequestHandler requestHandler = new MyRequestHandler(); - TestDriver testDriver = newDriver(requestHandler, filterBindings); - - testDriver.client().get("/filtered/status.html"); - - assertThat(requestHandler.awaitInvocation(), is(true)); - verify(defaultFilter, never()).filter(any(Response.class), any(Request.class)); - verify(filterWithBinding, times(1)).filter(any(Response.class), any(Request.class)); - - assertThat(testDriver.close(), is(true)); - } - - @Test - public void requireThatMetricAreReported() throws IOException, InterruptedException { - FilterBindings filterBindings = new FilterBindings.Builder() - .addRequestFilter("my-request-filter", mock(RequestFilter.class)) - .addRequestFilterBinding("my-request-filter", "http://*/*") - .build(); - MetricConsumerMock metricConsumerMock = new MetricConsumerMock(); - MyRequestHandler requestHandler = new MyRequestHandler(); - TestDriver testDriver = newDriver(requestHandler, filterBindings, metricConsumerMock, false); - - testDriver.client().get("/status.html"); - assertThat(requestHandler.awaitInvocation(), is(true)); - verify(metricConsumerMock.mockitoMock()) - .add(MetricDefinitions.FILTERING_REQUEST_HANDLED, 1L, MetricConsumerMock.STATIC_CONTEXT); - verify(metricConsumerMock.mockitoMock(), never()) - .add(MetricDefinitions.FILTERING_REQUEST_UNHANDLED, 1L, MetricConsumerMock.STATIC_CONTEXT); - verify(metricConsumerMock.mockitoMock(), never()) - .add(MetricDefinitions.FILTERING_RESPONSE_HANDLED, 1L, MetricConsumerMock.STATIC_CONTEXT); - verify(metricConsumerMock.mockitoMock()) - .add(MetricDefinitions.FILTERING_RESPONSE_UNHANDLED, 1L, MetricConsumerMock.STATIC_CONTEXT); - assertThat(testDriver.close(), is(true)); - } - - @Test - public void requireThatStrictFilteringRejectsRequestsNotMatchingFilterChains() throws IOException { - RequestFilter filter = mock(RequestFilter.class); - FilterBindings filterBindings = new FilterBindings.Builder() - .addRequestFilter("my-request-filter", filter) - .addRequestFilterBinding("my-request-filter", "http://*/filtered/*") - .build(); - MyRequestHandler requestHandler = new MyRequestHandler(); - TestDriver testDriver = newDriver(requestHandler, filterBindings, new MetricConsumerMock(), true); - - testDriver.client().get("/unfiltered/") - .expectStatusCode(is(Response.Status.FORBIDDEN)) - .expectContent(containsString("Request did not match any request filter chain")); - verify(filter, never()).filter(any(), any()); - assertThat(testDriver.close(), is(true)); - } - - private static TestDriver newDriver(MyRequestHandler requestHandler, FilterBindings filterBindings) { - return newDriver(requestHandler, filterBindings, new MetricConsumerMock(), false); - } - - private static TestDriver newDriver( - MyRequestHandler requestHandler, - FilterBindings filterBindings, - MetricConsumerMock metricConsumer, - boolean strictFiltering) { - return TestDriver.newInstance( - JettyHttpServer.class, - requestHandler, - newFilterModule(filterBindings, metricConsumer, strictFiltering)); - } - - private static com.google.inject.Module newFilterModule( - FilterBindings filterBindings, MetricConsumerMock metricConsumer, boolean strictFiltering) { - return Modules.combine( - new AbstractModule() { - @Override - protected void configure() { - - bind(FilterBindings.class).toInstance(filterBindings); - bind(ServerConfig.class).toInstance(new ServerConfig(new ServerConfig.Builder().strictFiltering(strictFiltering))); - bind(ConnectorConfig.class).toInstance(new ConnectorConfig(new ConnectorConfig.Builder())); - bind(ServletPathsConfig.class).toInstance(new ServletPathsConfig(new ServletPathsConfig.Builder())); - bind(ConnectionLog.class).toInstance(new VoidConnectionLog()); - bind(RequestLog.class).toInstance(new VoidRequestLog()); - } - }, - new ConnectorFactoryRegistryModule(), - metricConsumer.asGuiceModule()); - } - - private static abstract class RequestFilterMockBase extends AbstractResource implements RequestFilter {} - private static abstract class ResponseFilterMockBase extends AbstractResource implements ResponseFilter {} - - private static class MyRequestHandler extends AbstractRequestHandler { - private final CountDownLatch invocationLatch = new CountDownLatch(1); - private final AtomicReference<Map<String, List<String>>> headerCopy = new AtomicReference<>(null); - - @Override - public ContentChannel handleRequest(final Request request, final ResponseHandler handler) { - try { - headerCopy.set(new HashMap<String, List<String>>(request.headers())); - ResponseDispatch.newInstance(Response.Status.OK).dispatch(handler); - return null; - } finally { - invocationLatch.countDown(); - } - } - - public boolean hasBeenInvokedYet() { - return invocationLatch.getCount() == 0L; - } - - public boolean awaitInvocation() throws InterruptedException { - return invocationLatch.await(60, TimeUnit.SECONDS); - } - - public Map<String, List<String>> getHeaderMap() { - return headerCopy.get(); - } - } - - private static class RespondForbiddenFilter extends AbstractResource implements RequestFilter { - @Override - public void filter(final HttpRequest request, final ResponseHandler handler) { - ResponseDispatch.newInstance(Response.Status.FORBIDDEN).dispatch(handler); - } - } - - private static class ThrowingRequestFilter extends AbstractResource implements RequestFilter { - @Override - public void filter(final HttpRequest request, final ResponseHandler handler) { - throw new RuntimeException(); - } - } - - private static class ThrowingResponseFilter extends AbstractResource implements ResponseFilter { - @Override - public void filter(final Response response, final Request request) { - throw new RuntimeException(); - } - } - - private static class HeaderRequestFilter extends AbstractResource implements RequestFilter { - private final String key; - private final String val; - - public HeaderRequestFilter(final String key, final String val) { - this.key = key; - this.val = val; - } - - @Override - public void filter(final HttpRequest request, final ResponseHandler handler) { - request.headers().add(key, val); - } - } - - private static class HeaderResponseFilter extends AbstractResource implements ResponseFilter { - private final String key; - private final String val; - - public HeaderResponseFilter(final String key, final String val) { - this.key = key; - this.val = val; - } - - @Override - public void filter(final Response response, final Request request) { - response.headers().add(key, val); - } - } - - public class NullCompletionHandlerFilter extends AbstractResource implements RequestFilter { - private final int responseStatus; - private final String responseMessage; - - public NullCompletionHandlerFilter(final int responseStatus, final String responseMessage) { - this.responseStatus = responseStatus; - this.responseMessage = responseMessage; - } - - @Override - public void filter(final HttpRequest request, final ResponseHandler responseHandler) { - final HttpResponse response = HttpResponse.newInstance(responseStatus); - final ContentChannel channel = responseHandler.handleResponse(response); - final CompletionHandler completionHandler = null; - channel.write(ByteBuffer.wrap(responseMessage.getBytes()), completionHandler); - channel.close(null); - } - } -} diff --git a/jdisc_http_service/src/test/java/com/yahoo/jdisc/http/server/jetty/HttpRequestFactoryTest.java b/jdisc_http_service/src/test/java/com/yahoo/jdisc/http/server/jetty/HttpRequestFactoryTest.java deleted file mode 100644 index 9c1348004ee..00000000000 --- a/jdisc_http_service/src/test/java/com/yahoo/jdisc/http/server/jetty/HttpRequestFactoryTest.java +++ /dev/null @@ -1,204 +0,0 @@ -// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. -package com.yahoo.jdisc.http.server.jetty; - -import com.google.inject.Key; -import com.yahoo.jdisc.Container; -import com.yahoo.jdisc.References; -import com.yahoo.jdisc.ResourceReference; -import com.yahoo.jdisc.Response; -import com.yahoo.jdisc.handler.RequestHandler; -import com.yahoo.jdisc.http.ConnectorConfig; -import com.yahoo.jdisc.http.HttpRequest; -import com.yahoo.jdisc.service.CurrentContainer; -import org.eclipse.jetty.server.HttpConnection; -import org.junit.Test; - -import javax.servlet.http.HttpServletRequest; -import java.net.URI; - -import static org.hamcrest.CoreMatchers.equalTo; -import static org.hamcrest.CoreMatchers.is; -import static org.hamcrest.MatcherAssert.assertThat; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertNull; -import static org.junit.Assert.fail; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.when; - -/** - * @author Steinar Knutsen - * @author bjorncs - */ -public class HttpRequestFactoryTest { - - private static final int LOCAL_PORT = 80; - - @Test - public void testLegalURIs() { - { - URI uri = HttpRequestFactory.getUri(createMockRequest("https", "host", null, null)); - assertEquals("https", uri.getScheme()); - assertEquals("host", uri.getHost()); - assertEquals("", uri.getRawPath()); - assertNull(uri.getRawQuery()); - } - { - URI uri = HttpRequestFactory.getUri(createMockRequest("https", "host", "", "")); - assertEquals("https", uri.getScheme()); - assertEquals("host", uri.getHost()); - assertEquals("", uri.getRawPath()); - assertEquals("", uri.getRawQuery()); - } - { - URI uri = HttpRequestFactory.getUri(createMockRequest("http", "host.a1-2-3", "", "")); - assertEquals("http", uri.getScheme()); - assertEquals("host.a1-2-3", uri.getHost()); - assertEquals("", uri.getRawPath()); - assertEquals("", uri.getRawQuery()); - } - { - URI uri = HttpRequestFactory.getUri(createMockRequest("https", "host", "/:1/../1=.", "")); - assertEquals("https", uri.getScheme()); - assertEquals("host", uri.getHost()); - assertEquals("/:1/../1=.", uri.getRawPath()); - assertEquals("", uri.getRawQuery()); - } - { - URI uri = HttpRequestFactory.getUri(createMockRequest("https", "host", "", "a=/../&?=")); - assertEquals("https", uri.getScheme()); - assertEquals("host", uri.getHost()); - assertEquals("", uri.getRawPath()); - assertEquals("a=/../&?=", uri.getRawQuery()); - } - } - - @Test - public void testIllegalQuery() { - try { - HttpRequestFactory.newJDiscRequest( - new MockContainer(), - createMockRequest("http", "example.com", "/search", "query=\"contains_quotes\"")); - fail("Above statement should throw"); - } catch (RequestException e) { - assertThat(e.getResponseStatus(), is(Response.Status.BAD_REQUEST)); - } - } - - @Test - public final void illegal_host_throws_requestexception1() { - try { - HttpRequestFactory.newJDiscRequest( - new MockContainer(), - createMockRequest("http", "?", "/foo", "")); - fail("Above statement should throw"); - } catch (RequestException e) { - assertThat(e.getResponseStatus(), is(Response.Status.BAD_REQUEST)); - } - } - - @Test - public final void illegal_host_throws_requestexception2() { - try { - HttpRequestFactory.newJDiscRequest( - new MockContainer(), - createMockRequest("http", ".", "/foo", "")); - fail("Above statement should throw"); - } catch (RequestException e) { - assertThat(e.getResponseStatus(), is(Response.Status.BAD_REQUEST)); - } - } - - @Test - public final void illegal_host_throws_requestexception3() { - try { - HttpRequestFactory.newJDiscRequest( - new MockContainer(), - createMockRequest("http", "*", "/foo", "")); - fail("Above statement should throw"); - } catch (RequestException e) { - assertThat(e.getResponseStatus(), is(Response.Status.BAD_REQUEST)); - } - } - - @Test - public final void illegal_unicode_in_query_throws_requestexception() { - try { - HttpRequestFactory.newJDiscRequest( - new MockContainer(), - createMockRequest("http", "example.com", "/search", "query=%c0%ae")); - fail("Above statement should throw"); - } catch (RequestException e) { - assertThat(e.getResponseStatus(), is(Response.Status.BAD_REQUEST)); - assertThat(e.getMessage(), equalTo("URL violates RFC 2396: Not valid UTF8! byte C0 in state 0")); - } - } - - @Test - public void request_uri_uses_local_port() { - HttpRequest request = HttpRequestFactory.newJDiscRequest( - new MockContainer(), - createMockRequest("https", "example.com", "/search", "query=value")); - assertEquals(LOCAL_PORT, request.getUri().getPort()); - } - - private static HttpServletRequest createMockRequest(String scheme, String serverName, String path, String queryString) { - HttpServletRequest request = mock(HttpServletRequest.class); - HttpConnection connection = mock(HttpConnection.class); - JDiscServerConnector connector = mock(JDiscServerConnector.class); - when(connector.connectorConfig()).thenReturn(new ConnectorConfig(new ConnectorConfig.Builder().listenPort(LOCAL_PORT))); - when(connector.getLocalPort()).thenReturn(LOCAL_PORT); - when(connection.getCreatedTimeStamp()).thenReturn(System.currentTimeMillis()); - when(connection.getConnector()).thenReturn(connector); - when(request.getAttribute("org.eclipse.jetty.server.HttpConnection")).thenReturn(connection); - when(request.getProtocol()).thenReturn("HTTP/1.1"); - when(request.getScheme()).thenReturn(scheme); - when(request.getServerName()).thenReturn(serverName); - when(request.getRemoteAddr()).thenReturn("127.0.0.1"); - when(request.getRemotePort()).thenReturn(1234); - when(request.getLocalPort()).thenReturn(LOCAL_PORT); - when(request.getMethod()).thenReturn("GET"); - when(request.getQueryString()).thenReturn(queryString); - when(request.getRequestURI()).thenReturn(path); - return request; - } - - private static final class MockContainer implements CurrentContainer { - - @Override - public Container newReference(URI uri) { - return new Container() { - - @Override - public RequestHandler resolveHandler(com.yahoo.jdisc.Request request) { - return null; - } - - @Override - public <T> T getInstance(Key<T> tKey) { - return null; - } - - @Override - public <T> T getInstance(Class<T> tClass) { - return null; - } - - @Override - public ResourceReference refer() { - return References.NOOP_REFERENCE; - } - - @Override - public void release() { - - } - - @Override - public long currentTimeMillis() { - return 0; - } - }; - } - } - -} diff --git a/jdisc_http_service/src/test/java/com/yahoo/jdisc/http/server/jetty/HttpResponseStatisticsCollectorTest.java b/jdisc_http_service/src/test/java/com/yahoo/jdisc/http/server/jetty/HttpResponseStatisticsCollectorTest.java deleted file mode 100644 index bb92d75bed5..00000000000 --- a/jdisc_http_service/src/test/java/com/yahoo/jdisc/http/server/jetty/HttpResponseStatisticsCollectorTest.java +++ /dev/null @@ -1,221 +0,0 @@ -// Copyright 2018 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. -package com.yahoo.jdisc.http.server.jetty; - -import com.yahoo.jdisc.http.server.jetty.HttpResponseStatisticsCollector.StatisticsEntry; -import org.eclipse.jetty.http.HttpFields; -import org.eclipse.jetty.http.HttpURI; -import org.eclipse.jetty.http.HttpVersion; -import org.eclipse.jetty.http.MetaData; -import org.eclipse.jetty.http.MetaData.Response; -import org.eclipse.jetty.server.AbstractConnector; -import org.eclipse.jetty.server.Connector; -import org.eclipse.jetty.server.HttpChannel; -import org.eclipse.jetty.server.HttpConfiguration; -import org.eclipse.jetty.server.HttpTransport; -import org.eclipse.jetty.server.Request; -import org.eclipse.jetty.server.Server; -import org.eclipse.jetty.server.handler.AbstractHandler; -import org.eclipse.jetty.util.Callback; -import org.junit.Before; -import org.junit.Test; - -import javax.servlet.ServletException; -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; -import java.io.IOException; -import java.nio.ByteBuffer; -import java.util.List; - -import static org.hamcrest.MatcherAssert.assertThat; -import static org.hamcrest.Matchers.equalTo; - -/** - * @author ollivir - */ -public class HttpResponseStatisticsCollectorTest { - - private Connector connector; - private List<String> monitoringPaths = List.of("/status.html"); - private List<String> searchPaths = List.of("/search"); - private HttpResponseStatisticsCollector collector = new HttpResponseStatisticsCollector(monitoringPaths, searchPaths); - private int httpResponseCode = 500; - - @Test - public void statistics_are_aggregated_by_category() { - testRequest("http", 300, "GET"); - testRequest("http", 301, "GET"); - testRequest("http", 200, "GET"); - - var stats = collector.takeStatistics(); - assertStatisticsEntryPresent(stats, "http", "GET", MetricDefinitions.RESPONSES_2XX, 1L); - assertStatisticsEntryPresent(stats, "http", "GET", MetricDefinitions.RESPONSES_3XX, 2L); - } - - @Test - public void statistics_are_grouped_by_http_method_and_scheme() { - testRequest("http", 200, "GET"); - testRequest("http", 200, "PUT"); - testRequest("http", 200, "POST"); - testRequest("http", 200, "POST"); - testRequest("http", 404, "GET"); - testRequest("https", 404, "GET"); - testRequest("https", 200, "POST"); - testRequest("https", 200, "POST"); - testRequest("https", 200, "POST"); - testRequest("https", 200, "POST"); - - var stats = collector.takeStatistics(); - assertStatisticsEntryPresent(stats, "http", "GET", MetricDefinitions.RESPONSES_2XX, 1L); - assertStatisticsEntryPresent(stats, "http", "GET", MetricDefinitions.RESPONSES_4XX, 1L); - assertStatisticsEntryPresent(stats, "http", "PUT", MetricDefinitions.RESPONSES_2XX, 1L); - assertStatisticsEntryPresent(stats, "http", "POST", MetricDefinitions.RESPONSES_2XX, 2L); - assertStatisticsEntryPresent(stats, "https", "GET", MetricDefinitions.RESPONSES_4XX, 1L); - assertStatisticsEntryPresent(stats, "https", "POST", MetricDefinitions.RESPONSES_2XX, 4L); - } - - @Test - public void statistics_include_grouped_and_single_statuscodes() { - testRequest("http", 401, "GET"); - testRequest("http", 404, "GET"); - testRequest("http", 403, "GET"); - - var stats = collector.takeStatistics(); - assertStatisticsEntryPresent(stats, "http", "GET", MetricDefinitions.RESPONSES_4XX, 3L); - assertStatisticsEntryPresent(stats, "http", "GET", MetricDefinitions.RESPONSES_401, 1L); - assertStatisticsEntryPresent(stats, "http", "GET", MetricDefinitions.RESPONSES_403, 1L); - - } - - @Test - public void retrieving_statistics_resets_the_counters() { - testRequest("http", 200, "GET"); - testRequest("http", 200, "GET"); - - var stats = collector.takeStatistics(); - assertStatisticsEntryPresent(stats, "http", "GET", MetricDefinitions.RESPONSES_2XX, 2L); - - testRequest("http", 200, "GET"); - - stats = collector.takeStatistics(); - assertStatisticsEntryPresent(stats, "http", "GET", MetricDefinitions.RESPONSES_2XX, 1L); - } - - @Test - public void statistics_include_request_type_dimension() { - testRequest("http", 200, "GET", "/search"); - testRequest("http", 200, "POST", "/search"); - testRequest("http", 200, "POST", "/feed"); - testRequest("http", 200, "GET", "/status.html?foo=bar"); - - var stats = collector.takeStatistics(); - assertStatisticsEntryWithRequestTypePresent(stats, "http", "GET", MetricDefinitions.RESPONSES_2XX, "monitoring", 1L); - assertStatisticsEntryWithRequestTypePresent(stats, "http", "GET", MetricDefinitions.RESPONSES_2XX, "read", 1L); - assertStatisticsEntryWithRequestTypePresent(stats, "http", "POST", MetricDefinitions.RESPONSES_2XX, "read", 1L); - assertStatisticsEntryWithRequestTypePresent(stats, "http", "POST", MetricDefinitions.RESPONSES_2XX, "write", 1L); - - testRequest("http", 200, "GET"); - - stats = collector.takeStatistics(); - assertStatisticsEntryPresent(stats, "http", "GET", MetricDefinitions.RESPONSES_2XX, 1L); - } - - @Test - public void request_type_can_be_set_explicitly() { - testRequest("http", 200, "GET", "/search", com.yahoo.jdisc.Request.RequestType.WRITE); - - var stats = collector.takeStatistics(); - assertStatisticsEntryWithRequestTypePresent(stats, "http", "GET", MetricDefinitions.RESPONSES_2XX, "write", 1L); - } - - @Before - public void initializeCollector() throws Exception { - Server server = new Server(); - connector = new AbstractConnector(server, null, null, null, 0) { - @Override - protected void accept(int acceptorID) throws IOException, InterruptedException { - } - - @Override - public Object getTransport() { - return null; - } - }; - collector.setHandler(new AbstractHandler() { - @Override - public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) - throws IOException, ServletException { - baseRequest.setHandled(true); - baseRequest.getResponse().setStatus(httpResponseCode); - } - }); - server.setHandler(collector); - server.start(); - } - - private Request testRequest(String scheme, int responseCode, String httpMethod) { - return testRequest(scheme, responseCode, httpMethod, "foo/bar"); - } - private Request testRequest(String scheme, int responseCode, String httpMethod, String path) { - return testRequest(scheme, responseCode, httpMethod, path, null); - } - private Request testRequest(String scheme, int responseCode, String httpMethod, String path, - com.yahoo.jdisc.Request.RequestType explicitRequestType) { - HttpChannel channel = new HttpChannel(connector, new HttpConfiguration(), null, new DummyTransport()); - MetaData.Request metaData = new MetaData.Request(httpMethod, new HttpURI(scheme + "://" + path), HttpVersion.HTTP_1_1, new HttpFields()); - Request req = channel.getRequest(); - if (explicitRequestType != null) - req.setAttribute("requestType", explicitRequestType); - req.setMetaData(metaData); - - this.httpResponseCode = responseCode; - channel.handle(); - return req; - } - - private static void assertStatisticsEntryPresent(List<StatisticsEntry> result, String scheme, String method, String name, long expectedValue) { - long value = result.stream() - .filter(entry -> entry.method.equals(method) && entry.scheme.equals(scheme) && entry.name.equals(name)) - .mapToLong(entry -> entry.value) - .findAny() - .orElseThrow(() -> new AssertionError(String.format("Not matching entry in result (scheme=%s, method=%s, name=%s)", scheme, method, name))); - assertThat(value, equalTo(expectedValue)); - } - - private static void assertStatisticsEntryWithRequestTypePresent(List<StatisticsEntry> result, String scheme, String method, String name, String requestType, long expectedValue) { - long value = result.stream() - .filter(entry -> entry.method.equals(method) && entry.scheme.equals(scheme) && entry.name.equals(name) && entry.requestType.equals(requestType)) - .mapToLong(entry -> entry.value) - .reduce(Long::sum) - .orElseThrow(() -> new AssertionError(String.format("Not matching entry in result (scheme=%s, method=%s, name=%s, type=%s)", scheme, method, name, requestType))); - assertThat(value, equalTo(expectedValue)); - } - - private final class DummyTransport implements HttpTransport { - @Override - public void send(Response info, boolean head, ByteBuffer content, boolean lastContent, Callback callback) { - callback.succeeded(); - } - - @Override - public boolean isPushSupported() { - return false; - } - - @Override - public boolean isOptimizedForDirectBuffers() { - return false; - } - - @Override - public void push(MetaData.Request request) { - } - - @Override - public void onCompleted() { - } - - @Override - public void abort(Throwable failure) { - } - } -} diff --git a/jdisc_http_service/src/test/java/com/yahoo/jdisc/http/server/jetty/HttpServerConformanceTest.java b/jdisc_http_service/src/test/java/com/yahoo/jdisc/http/server/jetty/HttpServerConformanceTest.java deleted file mode 100644 index 5659dfc2d3c..00000000000 --- a/jdisc_http_service/src/test/java/com/yahoo/jdisc/http/server/jetty/HttpServerConformanceTest.java +++ /dev/null @@ -1,847 +0,0 @@ -// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. -package com.yahoo.jdisc.http.server.jetty; - -import com.google.inject.AbstractModule; -import com.google.inject.Module; -import com.google.inject.util.Modules; -import com.yahoo.container.logging.ConnectionLog; -import com.yahoo.container.logging.RequestLog; -import com.yahoo.jdisc.http.ServerConfig; -import com.yahoo.jdisc.http.ServletPathsConfig; -import com.yahoo.jdisc.http.guiceModules.ConnectorFactoryRegistryModule; -import com.yahoo.jdisc.test.ServerProviderConformanceTest; -import org.apache.http.HttpResponse; -import org.apache.http.HttpVersion; -import org.apache.http.ProtocolVersion; -import org.apache.http.client.HttpClient; -import org.apache.http.client.methods.HttpGet; -import org.apache.http.client.methods.HttpPost; -import org.apache.http.client.methods.HttpUriRequest; -import org.apache.http.entity.StringEntity; -import org.apache.http.impl.client.HttpClientBuilder; -import org.apache.http.util.EntityUtils; -import org.hamcrest.Description; -import org.hamcrest.Matcher; -import org.hamcrest.TypeSafeMatcher; -import org.junit.AfterClass; -import org.junit.BeforeClass; -import org.junit.Test; - -import java.net.URI; -import java.nio.ByteBuffer; -import java.nio.charset.StandardCharsets; -import java.util.Collections; -import java.util.concurrent.ExecutorService; -import java.util.concurrent.Executors; -import java.util.concurrent.Future; -import java.util.logging.Level; -import java.util.logging.Logger; -import java.util.regex.Pattern; - -import static com.yahoo.jdisc.Response.Status.INTERNAL_SERVER_ERROR; -import static com.yahoo.jdisc.Response.Status.NOT_FOUND; -import static com.yahoo.jdisc.Response.Status.OK; -import static org.apache.http.HttpStatus.SC_INTERNAL_SERVER_ERROR; -import static org.apache.http.HttpStatus.SC_NOT_FOUND; -import static org.cthul.matchers.CthulMatchers.containsPattern; -import static org.cthul.matchers.CthulMatchers.matchesPattern; -import static org.hamcrest.CoreMatchers.any; -import static org.hamcrest.CoreMatchers.anyOf; -import static org.hamcrest.CoreMatchers.containsString; -import static org.hamcrest.CoreMatchers.is; -import static org.hamcrest.MatcherAssert.assertThat; - -/** - * @author Simon Thoresen Hult - */ -public class HttpServerConformanceTest extends ServerProviderConformanceTest { - - private static final Logger log = Logger.getLogger(HttpServerConformanceTest.class.getName()); - - private static final String REQUEST_CONTENT = "myRequestContent"; - private static final String RESPONSE_CONTENT = "myResponseContent"; - - @SuppressWarnings("LoggerInitializedWithForeignClass") - private static Logger httpRequestDispatchLogger = Logger.getLogger(HttpRequestDispatch.class.getName()); - private static Level httpRequestDispatchLoggerOriginalLevel; - - /* - * Reduce logging of every stack trace for {@link ServerProviderConformanceTest.ConformanceException} thrown. - * This makes the log more readable and the test faster as well. - */ - @BeforeClass - public static void reduceExcessiveLogging() { - httpRequestDispatchLoggerOriginalLevel = httpRequestDispatchLogger.getLevel(); - httpRequestDispatchLogger.setLevel(Level.SEVERE); - } - - @AfterClass - public static void restoreExcessiveLogging() { - httpRequestDispatchLogger.setLevel(httpRequestDispatchLoggerOriginalLevel); - } - - @AfterClass - public static void reportDiagnostics() { - System.out.println( - "After " + HttpServerConformanceTest.class.getSimpleName() - + ": #threads=" + Thread.getAllStackTraces().size()); - } - - @Override - @Test - public void testContainerNotReadyException() throws Throwable { - new TestRunner().expect(errorWithReason(is(SC_INTERNAL_SERVER_ERROR), containsString("Container not ready."))) - .execute(); - } - - @Override - @Test - public void testBindingSetNotFoundException() throws Throwable { - new TestRunner().expect(errorWithReason(is(SC_NOT_FOUND), containsString("No binding set named 'unknown'."))) - .execute(); - } - - @Override - @Test - public void testNoBindingSetSelectedException() throws Throwable { - final Pattern reasonPattern = Pattern.compile(".*No binding set selected for URI 'http://.+/status.html'\\."); - new TestRunner().expect(errorWithReason(is(SC_INTERNAL_SERVER_ERROR), matchesPattern(reasonPattern))) - .execute(); - } - - @Override - @Test - public void testBindingNotFoundException() throws Throwable { - final Pattern contentPattern = Pattern.compile("No binding for URI 'http://.+/status.html'\\."); - new TestRunner().expect(errorWithReason(is(NOT_FOUND), containsPattern(contentPattern))) - .execute(); - } - - @Override - @Test - public void testRequestHandlerWithSyncCloseResponse() throws Throwable { - new TestRunner().expect(success()) - .execute(); - } - - @Override - @Test - public void testRequestHandlerWithSyncWriteResponse() throws Throwable { - new TestRunner().expect(success()) - .execute(); - } - - @Override - @Test - public void testRequestHandlerWithSyncHandleResponse() throws Throwable { - new TestRunner().expect(success()) - .execute(); - } - - @Override - @Test - public void testRequestHandlerWithAsyncHandleResponse() throws Throwable { - new TestRunner().expect(success()) - .execute(); - } - - @Override - @Test - public void testRequestException() throws Throwable { - new TestRunner().expect(serverError()) - .execute(); - } - - @Override - @Test - public void testRequestExceptionWithSyncCloseResponse() throws Throwable { - new TestRunner().expect(success()) - .execute(); - } - - @Override - @Test - public void testRequestExceptionWithSyncWriteResponse() throws Throwable { - new TestRunner().expect(success()) - .execute(); - } - - @Override - @Test - public void testRequestNondeterministicExceptionWithSyncHandleResponse() throws Throwable { - new TestRunner().expect(anyOf(success(), serverError())) - .execute(); - } - - @Override - @Test - public void testRequestExceptionBeforeResponseWriteWithSyncHandleResponse() throws Throwable { - new TestRunner().expect(serverError()) - .execute(); - } - - @Override - @Test - public void testRequestExceptionAfterResponseWriteWithSyncHandleResponse() throws Throwable { - new TestRunner().expect(success()) - .execute(); - } - - @Override - @Test - public void testRequestNondeterministicExceptionWithAsyncHandleResponse() throws Throwable { - new TestRunner().expect(anyOf(successNoContent(), serverError())) - .execute(); - } - - @Override - @Test - public void testRequestExceptionBeforeResponseWriteWithAsyncHandleResponse() throws Throwable { - new TestRunner().expect(serverError()) - .execute(); - } - - @Override - @Test - public void testRequestExceptionAfterResponseCloseNoContentWithAsyncHandleResponse() throws Throwable { - new TestRunner().expect(successNoContent()) - .execute(); - } - - @Override - @Test - public void testRequestExceptionAfterResponseWriteWithAsyncHandleResponse() throws Throwable { - new TestRunner().expect(success()) - .execute(); - } - - @Override - @Test - public void testRequestContentWriteWithSyncCompletion() throws Throwable { - new TestRunner().expect(success()) - .execute(); - } - - @Override - @Test - public void testRequestContentWriteWithAsyncCompletion() throws Throwable { - new TestRunner().expect(success()) - .execute(); - } - - @Override - @Test - public void testRequestContentWriteWithNondeterministicSyncFailure() throws Throwable { - new TestRunner().expect(anyOf(success(), serverError())) - .execute(); - } - - @Override - @Test - public void testRequestContentWriteWithSyncFailureBeforeResponseWrite() throws Throwable { - new TestRunner().expect(serverError()) - .execute(); - } - - @Override - @Test - public void testRequestContentWriteWithSyncFailureAfterResponseWrite() throws Throwable { - new TestRunner().expect(success()) - .execute(); - } - - @Override - @Test - public void testRequestContentWriteWithNondeterministicAsyncFailure() throws Throwable { - new TestRunner().expect(anyOf(success(), serverError())) - .execute(); - } - - @Override - @Test - public void testRequestContentWriteWithAsyncFailureBeforeResponseWrite() throws Throwable { - new TestRunner().expect(serverError()) - .execute(); - } - - @Override - @Test - public void testRequestContentWriteWithAsyncFailureAfterResponseWrite() throws Throwable { - new TestRunner().expect(success()) - .execute(); - } - - @Override - @Test - public void testRequestContentWriteWithAsyncFailureAfterResponseCloseNoContent() throws Throwable { - new TestRunner().expect(successNoContent()) - .execute(); - } - - @Override - @Test - public void testRequestContentWriteNondeterministicException() throws Throwable { - new TestRunner().expect(anyOf(success(), serverError(), successNoContent())) - .execute(); - } - - @Override - @Test - public void testRequestContentWriteExceptionBeforeResponseWrite() throws Throwable { - new TestRunner().expect(serverError()) - .execute(); - } - - @Override - @Test - public void testRequestContentWriteExceptionAfterResponseWrite() throws Throwable { - new TestRunner().expect(success()) - .execute(); - } - - @Override - @Test - public void testRequestContentWriteExceptionAfterResponseCloseNoContent() throws Throwable { - new TestRunner().expect(successNoContent()) - .execute(); - } - - @Override - @Test - public void testRequestContentWriteNondeterministicExceptionWithSyncCompletion() throws Throwable { - new TestRunner().expect(anyOf(success(), serverError())) - .execute(); - } - - @Override - @Test - public void testRequestContentWriteExceptionBeforeResponseWriteWithSyncCompletion() throws Throwable { - new TestRunner().expect(serverError()) - .execute(); - } - - @Override - @Test - public void testRequestContentWriteExceptionAfterResponseWriteWithSyncCompletion() throws Throwable { - new TestRunner().expect(anyOf(success(), successNoContent())) - .execute(); - } - - @Override - @Test - public void testRequestContentWriteExceptionAfterResponseCloseNoContentWithSyncCompletion() throws Throwable { - new TestRunner().expect(anyOf(success(), successNoContent())) - .execute(); - } - - @Override - @Test - public void testRequestContentWriteNondeterministicExceptionWithAsyncCompletion() throws Throwable { - new TestRunner() - .expect(anyOf(success(), successNoContent(), serverError())) - .execute(); - } - - @Override - @Test - public void testRequestContentWriteExceptionBeforeResponseWriteWithAsyncCompletion() throws Throwable { - new TestRunner().expect(serverError()) - .execute(); - } - - @Override - @Test - public void testRequestContentWriteExceptionAfterResponseWriteWithAsyncCompletion() throws Throwable { - new TestRunner().expect(success()) - .execute(); - } - - @Override - @Test - public void testRequestContentWriteExceptionAfterResponseCloseNoContentWithAsyncCompletion() throws Throwable { - new TestRunner().expect(successNoContent()) - .execute(); - } - - @Override - @Test - public void testRequestContentWriteExceptionWithNondeterministicSyncFailure() throws Throwable { - new TestRunner().expect(anyOf(success(), successNoContent(), serverError())) - .execute(); - } - - @Override - @Test - public void testRequestContentWriteExceptionWithSyncFailureBeforeResponseWrite() throws Throwable { - new TestRunner().expect(serverError()) - .execute(); - } - - @Override - @Test - public void testRequestContentWriteExceptionWithSyncFailureAfterResponseWrite() throws Throwable { - new TestRunner().expect(success()) - .execute(); - } - - @Override - @Test - public void testRequestContentWriteExceptionWithSyncFailureAfterResponseCloseNoContent() throws Throwable { - new TestRunner().expect(successNoContent()) - .execute(); - } - - @Override - @Test - public void testRequestContentWriteExceptionWithNondeterministicAsyncFailure() throws Throwable { - new TestRunner().expect(anyOf(success(), serverError())) - .execute(); - } - - @Override - @Test - public void testRequestContentWriteExceptionWithAsyncFailureBeforeResponseWrite() throws Throwable { - new TestRunner().expect(serverError()) - .execute(); - } - - @Override - @Test - public void testRequestContentWriteExceptionWithAsyncFailureAfterResponseWrite() throws Throwable { - new TestRunner().expect(success()) - .execute(); - } - - @Override - @Test - public void testRequestContentWriteExceptionWithAsyncFailureAfterResponseCloseNoContent() throws Throwable { - new TestRunner().expect(successNoContent()) - .execute(); - } - - @Override - @Test - public void testRequestContentCloseWithSyncCompletion() throws Throwable { - new TestRunner().expect(success()) - .execute(); - } - - @Override - @Test - public void testRequestContentCloseWithAsyncCompletion() throws Throwable { - new TestRunner().expect(success()) - .execute(); - } - - @Override - @Test - public void testRequestContentCloseWithNondeterministicSyncFailure() throws Throwable { - new TestRunner().expect(anyOf(success(), successNoContent(), serverError())) - .execute(); - } - - @Override - @Test - public void testRequestContentCloseWithSyncFailureBeforeResponseWrite() throws Throwable { - new TestRunner().expect(serverError()) - .execute(); - } - - @Override - @Test - public void testRequestContentCloseWithSyncFailureAfterResponseWrite() throws Throwable { - new TestRunner().expect(success()) - .execute(); - } - - @Override - @Test - public void testRequestContentCloseWithSyncFailureAfterResponseCloseNoContent() throws Throwable { - new TestRunner().expect(successNoContent()) - .execute(); - } - - @Override - @Test - public void testRequestContentCloseWithNondeterministicAsyncFailure() throws Throwable { - new TestRunner().expect(anyOf(success(), successNoContent(), serverError())) - .execute(); - } - - @Override - @Test - public void testRequestContentCloseWithAsyncFailureBeforeResponseWrite() throws Throwable { - new TestRunner().expect(serverError()) - .execute(); - } - - @Override - @Test - public void testRequestContentCloseWithAsyncFailureAfterResponseWrite() throws Throwable { - new TestRunner().expect(success()) - .execute(); - } - - @Override - @Test - public void testRequestContentCloseWithAsyncFailureAfterResponseCloseNoContent() throws Throwable { - new TestRunner().expect(successNoContent()) - .execute(); - } - - @Override - @Test - public void testRequestContentCloseNondeterministicException() throws Throwable { - new TestRunner().expect(anyOf(success(), successNoContent(), serverError())) - .execute(); - } - - @Override - @Test - public void testRequestContentCloseExceptionBeforeResponseWrite() throws Throwable { - new TestRunner().expect(serverError()) - .execute(); - } - - @Override - @Test - public void testRequestContentCloseExceptionAfterResponseWrite() throws Throwable { - new TestRunner().expect(success()) - .execute(); - } - - @Override - @Test - public void testRequestContentCloseExceptionAfterResponseCloseNoContent() throws Throwable { - new TestRunner().expect(successNoContent()) - .execute(); - } - - @Override - @Test - public void testRequestContentCloseNondeterministicExceptionWithSyncCompletion() throws Throwable { - new TestRunner().expect(anyOf(success(), serverError(), successNoContent())) - .execute(); - } - - @Override - @Test - public void testRequestContentCloseExceptionBeforeResponseWriteWithSyncCompletion() throws Throwable { - new TestRunner().expect(serverError()) - .execute(); - } - - @Override - @Test - public void testRequestContentCloseExceptionAfterResponseWriteWithSyncCompletion() throws Throwable { - new TestRunner().expect(success()) - .execute(); - } - - @Override - @Test - public void testRequestContentCloseExceptionAfterResponseCloseNoContentWithSyncCompletion() throws Throwable { - new TestRunner().expect(successNoContent()) - .execute(); - } - - @Override - @Test - public void testRequestContentCloseNondeterministicExceptionWithAsyncCompletion() throws Throwable { - new TestRunner().expect(anyOf(success(), serverError(), successNoContent())) - .execute(); - } - - @Override - @Test - public void testRequestContentCloseExceptionBeforeResponseWriteWithAsyncCompletion() throws Throwable { - new TestRunner().expect(serverError()) - .execute(); - } - - @Override - @Test - public void testRequestContentCloseExceptionAfterResponseWriteWithAsyncCompletion() throws Throwable { - new TestRunner().expect(success()) - .execute(); - } - - @Override - @Test - public void testRequestContentCloseExceptionAfterResponseCloseNoContentWithAsyncCompletion() throws Throwable { - new TestRunner().expect(successNoContent()) - .execute(); - } - - @Override - @Test - public void testRequestContentCloseNondeterministicExceptionWithSyncFailure() throws Throwable { - new TestRunner().expect(anyOf(success(), successNoContent(), serverError())) - .execute(); - } - - @Override - @Test - public void testRequestContentCloseExceptionBeforeResponseWriteWithSyncFailure() throws Throwable { - new TestRunner().expect(serverError()) - .execute(); - } - - @Override - @Test - public void testRequestContentCloseExceptionAfterResponseWriteWithSyncFailure() throws Throwable { - new TestRunner().expect(success()) - .execute(); - } - - @Override - @Test - public void testRequestContentCloseExceptionAfterResponseCloseNoContentWithSyncFailure() throws Throwable { - new TestRunner().expect(successNoContent()) - .execute(); - } - - @Override - @Test - public void testRequestContentCloseNondeterministicExceptionWithAsyncFailure() throws Throwable { - new TestRunner().expect(anyOf(success(), successNoContent(), serverError())) - .execute(); - } - - @Override - @Test - public void testRequestContentCloseExceptionBeforeResponseWriteWithAsyncFailure() throws Throwable { - new TestRunner().expect(serverError()) - .execute(); - } - - @Override - @Test - public void testRequestContentCloseExceptionAfterResponseWriteWithAsyncFailure() throws Throwable { - new TestRunner().expect(success()) - .execute(); - } - - @Override - @Test - public void testRequestContentCloseExceptionAfterResponseCloseNoContentWithAsyncFailure() throws Throwable { - new TestRunner().expect(successNoContent()) - .execute(); - } - - @Override - @Test - public void testResponseWriteCompletionException() throws Throwable { - new TestRunner().expect(success()) - .execute(); - } - - @Override - @Test - public void testResponseCloseCompletionException() throws Throwable { - new TestRunner().expect(success()) - .execute(); - } - - @Override - @Test - public void testResponseCloseCompletionExceptionNoContent() throws Throwable { - new TestRunner().expect(successNoContent()) - .execute(); - } - - private static Matcher<ResponseGist> success() { - final Matcher<Integer> expectedStatusCode = is(OK); - final Matcher<String> expectedReasonPhrase = is("OK"); - final Matcher<String> expectedContent = is(RESPONSE_CONTENT); - return responseMatcher(expectedStatusCode, expectedReasonPhrase, expectedContent); - } - - private static Matcher<ResponseGist> successNoContent() { - final Matcher<Integer> expectedStatusCode = is(OK); - final Matcher<String> expectedReasonPhrase = is("OK"); - final Matcher<String> expectedContent = is(""); - return responseMatcher(expectedStatusCode, expectedReasonPhrase, expectedContent); - } - - private static Matcher<ResponseGist> serverError() { - final Matcher<Integer> expectedStatusCode = is(INTERNAL_SERVER_ERROR); - final Matcher<String> expectedReasonPhrase = any(String.class); - final Matcher<String> expectedContent = containsString(ConformanceException.class.getSimpleName()); - return responseMatcher(expectedStatusCode, expectedReasonPhrase, expectedContent); - } - - private static Matcher<ResponseGist> errorWithReason( - final Matcher<Integer> expectedStatusCode, final Matcher<String> expectedReasonPhrase) { - final Matcher<String> expectedContent = any(String.class); - return responseMatcher(expectedStatusCode, expectedReasonPhrase, expectedContent); - } - - private static Matcher<ResponseGist> responseMatcher( - final Matcher<Integer> expectedStatusCode, - final Matcher<String> expectedReasonPhrase, - final Matcher<String> expectedContent) { - return new TypeSafeMatcher<ResponseGist>() { - @Override - public void describeTo(final Description description) { - description.appendText("status code "); - expectedStatusCode.describeTo(description); - description.appendText(", reason "); - expectedReasonPhrase.describeTo(description); - description.appendText(" and content "); - expectedContent.describeTo(description); - } - - @Override - protected void describeMismatchSafely( - final ResponseGist response, final Description mismatchDescription) { - mismatchDescription.appendText(" status code was ").appendValue(response.getStatusCode()) - .appendText(", reason was ").appendValue(response.getReasonPhrase()) - .appendText(" and content was ").appendValue(response.getContent()); - } - - @Override - protected boolean matchesSafely(final ResponseGist response) { - return expectedStatusCode.matches(response.getStatusCode()) - && expectedReasonPhrase.matches(response.getReasonPhrase()) - && expectedContent.matches(response.getContent()); - } - }; - } - - private static class ResponseGist { - private final int statusCode; - private final String content; - private String reasonPhrase; - - public ResponseGist(int statusCode, String reasonPhrase, String content) { - this.statusCode = statusCode; - this.reasonPhrase = reasonPhrase; - this.content = content; - } - - public int getStatusCode() { - return statusCode; - } - - public String getContent() { - return content; - } - - public String getReasonPhrase() { - return reasonPhrase; - } - - @Override - public String toString() { - return "ResponseGist {" - + " statusCode=" + statusCode - + " reasonPhrase=" + reasonPhrase - + " content=" + content - + " }"; - } - } - - private class TestRunner implements Adapter<JettyHttpServer, ClientProxy, Future<HttpResponse>> { - - private Matcher<ResponseGist> expectedResponse = null; - HttpVersion requestVersion; - private final ExecutorService executorService = Executors.newSingleThreadExecutor(); - - void execute() throws Throwable { - requestVersion = HttpVersion.HTTP_1_0; - runTest(this); - - requestVersion = HttpVersion.HTTP_1_1; - runTest(this); - - executorService.shutdown(); - } - - TestRunner expect(final Matcher<ResponseGist> matcher) { - expectedResponse = matcher; - return this; - } - - @Override - public Module newConfigModule() { - return Modules.combine( - new AbstractModule() { - @Override - protected void configure() { - bind(FilterBindings.class) - .toInstance(new FilterBindings.Builder().build()); - bind(ServerConfig.class) - .toInstance(new ServerConfig(new ServerConfig.Builder())); - bind(ServletPathsConfig.class) - .toInstance(new ServletPathsConfig(new ServletPathsConfig.Builder())); - bind(ConnectionLog.class) - .toInstance(new VoidConnectionLog()); - bind(RequestLog.class) - .toInstance(new VoidRequestLog()); - } - }, - new ConnectorFactoryRegistryModule()); - } - - @Override - public Class<JettyHttpServer> getServerProviderClass() { - return JettyHttpServer.class; - } - - @Override - public ClientProxy newClient(final JettyHttpServer server) throws Throwable { - return new ClientProxy(server.getListenPort(), requestVersion); - } - - @Override - public Future<HttpResponse> executeRequest( - final ClientProxy client, - final boolean withRequestContent) throws Throwable { - final HttpUriRequest request; - final URI requestUri = URI.create("http://localhost:" + client.listenPort + "/status.html"); - if (!withRequestContent) { - HttpGet httpGet = new HttpGet(requestUri); - httpGet.setProtocolVersion(client.requestVersion); - request = httpGet; - } else { - final HttpPost post = new HttpPost(requestUri); - post.setEntity(new StringEntity(REQUEST_CONTENT, StandardCharsets.UTF_8)); - post.setProtocolVersion(client.requestVersion); - request = post; - } - log.fine(() -> "executorService:" - + " .isShutDown()=" + executorService.isShutdown() - + " .isTerminated()=" + executorService.isTerminated()); - return executorService.submit(() -> client.delegate.execute(request)); - } - - @Override - public Iterable<ByteBuffer> newResponseContent() { - return Collections.singleton(StandardCharsets.UTF_8.encode(RESPONSE_CONTENT)); - } - - @Override - public void validateResponse(final Future<HttpResponse> responseFuture) throws Throwable { - final HttpResponse response = responseFuture.get(); - final ResponseGist responseGist = new ResponseGist( - response.getStatusLine().getStatusCode(), - response.getStatusLine().getReasonPhrase(), - EntityUtils.toString(response.getEntity(), StandardCharsets.UTF_8)); - assertThat(responseGist, expectedResponse); - } - } - - private static class ClientProxy { - - final HttpClient delegate; - final int listenPort; - final ProtocolVersion requestVersion; - - ClientProxy(final int listenPort, final HttpVersion requestVersion) { - this.delegate = HttpClientBuilder.create().build(); - this.requestVersion = requestVersion; - this.listenPort = listenPort; - } - } -} diff --git a/jdisc_http_service/src/test/java/com/yahoo/jdisc/http/server/jetty/HttpServerTest.java b/jdisc_http_service/src/test/java/com/yahoo/jdisc/http/server/jetty/HttpServerTest.java deleted file mode 100644 index c00525a3ddc..00000000000 --- a/jdisc_http_service/src/test/java/com/yahoo/jdisc/http/server/jetty/HttpServerTest.java +++ /dev/null @@ -1,1201 +0,0 @@ -// Copyright 2018 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. -package com.yahoo.jdisc.http.server.jetty; - -import com.google.inject.AbstractModule; -import com.google.inject.Module; -import com.yahoo.container.logging.ConnectionLog; -import com.yahoo.container.logging.ConnectionLogEntry; -import com.yahoo.container.logging.ConnectionLogEntry.SslHandshakeFailure.ExceptionEntry; -import com.yahoo.container.logging.RequestLog; -import com.yahoo.container.logging.RequestLogEntry; -import com.yahoo.jdisc.References; -import com.yahoo.jdisc.Request; -import com.yahoo.jdisc.Response; -import com.yahoo.jdisc.application.BindingSetSelector; -import com.yahoo.jdisc.application.MetricConsumer; -import com.yahoo.jdisc.handler.AbstractRequestHandler; -import com.yahoo.jdisc.handler.CompletionHandler; -import com.yahoo.jdisc.handler.ContentChannel; -import com.yahoo.jdisc.handler.RequestHandler; -import com.yahoo.jdisc.handler.ResponseDispatch; -import com.yahoo.jdisc.handler.ResponseHandler; -import com.yahoo.jdisc.http.ConnectorConfig; -import com.yahoo.jdisc.http.ConnectorConfig.Throttling; -import com.yahoo.jdisc.http.Cookie; -import com.yahoo.jdisc.http.HttpRequest; -import com.yahoo.jdisc.http.HttpResponse; -import com.yahoo.jdisc.http.ServerConfig; -import com.yahoo.jdisc.http.server.jetty.TestDrivers.TlsClientAuth; -import com.yahoo.jdisc.service.BindingSetNotFoundException; -import com.yahoo.security.KeyUtils; -import com.yahoo.security.Pkcs10Csr; -import com.yahoo.security.Pkcs10CsrBuilder; -import com.yahoo.security.SslContextBuilder; -import com.yahoo.security.X509CertificateBuilder; -import com.yahoo.security.X509CertificateUtils; -import com.yahoo.security.tls.TlsContext; -import org.apache.http.conn.ssl.NoopHostnameVerifier; -import org.apache.http.entity.ContentType; -import org.apache.http.entity.mime.FormBodyPart; -import org.apache.http.entity.mime.content.StringBody; -import org.assertj.core.api.Assertions; -import org.eclipse.jetty.client.HttpClient; -import org.eclipse.jetty.client.ProxyProtocolClientConnectionFactory.V1; -import org.eclipse.jetty.client.ProxyProtocolClientConnectionFactory.V2; -import org.eclipse.jetty.client.api.ContentResponse; -import org.eclipse.jetty.server.handler.AbstractHandlerContainer; -import org.eclipse.jetty.util.ssl.SslContextFactory; -import org.junit.Rule; -import org.junit.Test; -import org.junit.rules.TemporaryFolder; - -import javax.net.ssl.SSLContext; -import javax.net.ssl.SSLException; -import javax.net.ssl.SSLHandshakeException; -import javax.security.auth.x500.X500Principal; -import java.io.IOException; -import java.math.BigInteger; -import java.net.BindException; -import java.net.URI; -import java.nio.ByteBuffer; -import java.nio.charset.StandardCharsets; -import java.nio.file.Files; -import java.nio.file.Path; -import java.security.KeyPair; -import java.security.PrivateKey; -import java.security.cert.X509Certificate; -import java.time.Duration; -import java.time.Instant; -import java.time.temporal.ChronoUnit; -import java.util.ArrayList; -import java.util.Collections; -import java.util.Comparator; -import java.util.List; -import java.util.Map; -import java.util.TreeMap; -import java.util.UUID; -import java.util.concurrent.ExecutionException; -import java.util.concurrent.TimeUnit; -import java.util.logging.Level; -import java.util.logging.Logger; -import java.util.regex.Pattern; - -import static com.yahoo.jdisc.Response.Status.GATEWAY_TIMEOUT; -import static com.yahoo.jdisc.Response.Status.INTERNAL_SERVER_ERROR; -import static com.yahoo.jdisc.Response.Status.NOT_FOUND; -import static com.yahoo.jdisc.Response.Status.OK; -import static com.yahoo.jdisc.Response.Status.REQUEST_URI_TOO_LONG; -import static com.yahoo.jdisc.Response.Status.UNAUTHORIZED; -import static com.yahoo.jdisc.Response.Status.UNSUPPORTED_MEDIA_TYPE; -import static com.yahoo.jdisc.http.HttpHeaders.Names.CONNECTION; -import static com.yahoo.jdisc.http.HttpHeaders.Names.CONTENT_TYPE; -import static com.yahoo.jdisc.http.HttpHeaders.Names.COOKIE; -import static com.yahoo.jdisc.http.HttpHeaders.Names.X_DISABLE_CHUNKING; -import static com.yahoo.jdisc.http.HttpHeaders.Values.APPLICATION_X_WWW_FORM_URLENCODED; -import static com.yahoo.jdisc.http.HttpHeaders.Values.CLOSE; -import static com.yahoo.jdisc.http.server.jetty.SimpleHttpClient.ResponseValidator; -import static com.yahoo.security.KeyAlgorithm.EC; -import static com.yahoo.security.SignatureAlgorithm.SHA256_WITH_ECDSA; -import static org.cthul.matchers.CthulMatchers.containsPattern; -import static org.cthul.matchers.CthulMatchers.matchesPattern; -import static org.hamcrest.CoreMatchers.containsString; -import static org.hamcrest.CoreMatchers.instanceOf; -import static org.hamcrest.CoreMatchers.is; -import static org.hamcrest.CoreMatchers.startsWith; -import static org.hamcrest.MatcherAssert.assertThat; -import static org.hamcrest.Matchers.anyOf; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertNotEquals; -import static org.junit.Assert.assertTrue; -import static org.junit.Assert.fail; -import static org.junit.Assume.assumeTrue; -import static org.mockito.Mockito.atLeast; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.when; - -/** - * @author Oyvind Bakksjo - * @author Simon Thoresen Hult - * @author bjorncs - */ -public class HttpServerTest { - - private static final Logger log = Logger.getLogger(HttpServerTest.class.getName()); - - @Rule - public TemporaryFolder tmpFolder = new TemporaryFolder(); - - @Test - public void requireThatServerCanListenToRandomPort() throws Exception { - final TestDriver driver = TestDrivers.newInstance(mockRequestHandler()); - assertNotEquals(0, driver.server().getListenPort()); - assertTrue(driver.close()); - } - - @Test - public void requireThatServerCanNotListenToBoundPort() throws Exception { - final TestDriver driver = TestDrivers.newInstance(mockRequestHandler()); - try { - TestDrivers.newConfiguredInstance( - mockRequestHandler(), - new ServerConfig.Builder(), - new ConnectorConfig.Builder() - .listenPort(driver.server().getListenPort()) - ); - } catch (final Throwable t) { - assertThat(t.getCause(), instanceOf(BindException.class)); - } - assertTrue(driver.close()); - } - - @Test - public void requireThatBindingSetNotFoundReturns404() throws Exception { - final TestDriver driver = TestDrivers.newConfiguredInstance( - mockRequestHandler(), - new ServerConfig.Builder() - .developerMode(true), - new ConnectorConfig.Builder(), - newBindingSetSelector("unknown")); - driver.client().get("/status.html") - .expectStatusCode(is(NOT_FOUND)) - .expectContent(containsPattern(Pattern.compile( - Pattern.quote(BindingSetNotFoundException.class.getName()) + - ": No binding set named 'unknown'\\.\n\tat .+", - Pattern.DOTALL | Pattern.MULTILINE))); - assertTrue(driver.close()); - } - - @Test - public void requireThatTooLongInitLineReturns414() throws Exception { - final TestDriver driver = TestDrivers.newConfiguredInstance( - mockRequestHandler(), - new ServerConfig.Builder(), - new ConnectorConfig.Builder() - .requestHeaderSize(1)); - driver.client().get("/status.html") - .expectStatusCode(is(REQUEST_URI_TOO_LONG)); - assertTrue(driver.close()); - } - - @Test - public void requireThatAccessLogIsCalledForRequestRejectedByJetty() throws Exception { - BlockingQueueRequestLog requestLogMock = new BlockingQueueRequestLog(); - final TestDriver driver = TestDrivers.newConfiguredInstance( - mockRequestHandler(), - new ServerConfig.Builder(), - new ConnectorConfig.Builder().requestHeaderSize(1), - binder -> binder.bind(RequestLog.class).toInstance(requestLogMock)); - driver.client().get("/status.html") - .expectStatusCode(is(REQUEST_URI_TOO_LONG)); - RequestLogEntry entry = requestLogMock.poll(Duration.ofSeconds(30)); - assertEquals(414, entry.statusCode().getAsInt()); - assertThat(driver.close(), is(true)); - } - - @Test - public void requireThatServerCanEcho() throws Exception { - final TestDriver driver = TestDrivers.newInstance(new EchoRequestHandler()); - driver.client().get("/status.html") - .expectStatusCode(is(OK)); - assertTrue(driver.close()); - } - - @Test - public void requireThatServerCanEchoCompressed() throws Exception { - final TestDriver driver = TestDrivers.newInstance(new EchoRequestHandler()); - SimpleHttpClient client = driver.newClient(true); - client.get("/status.html") - .expectStatusCode(is(OK)); - assertTrue(driver.close()); - } - - @Test - public void requireThatServerCanHandleMultipleRequests() throws Exception { - final TestDriver driver = TestDrivers.newInstance(new EchoRequestHandler()); - driver.client().get("/status.html") - .expectStatusCode(is(OK)); - driver.client().get("/status.html") - .expectStatusCode(is(OK)); - assertTrue(driver.close()); - } - - @Test - public void requireThatFormPostWorks() throws Exception { - final TestDriver driver = TestDrivers.newInstance(new ParameterPrinterRequestHandler()); - final String requestContent = generateContent('a', 30); - final ResponseValidator response = - driver.client().newPost("/status.html") - .addHeader(CONTENT_TYPE, APPLICATION_X_WWW_FORM_URLENCODED) - .setContent(requestContent) - .execute(); - response.expectStatusCode(is(OK)) - .expectContent(startsWith('{' + requestContent + "=[]}")); - assertTrue(driver.close()); - } - - @Test - public void requireThatFormPostDoesNotRemoveContentByDefault() throws Exception { - final TestDriver driver = TestDrivers.newInstance(new ParameterPrinterRequestHandler()); - final ResponseValidator response = - driver.client().newPost("/status.html") - .addHeader(CONTENT_TYPE, APPLICATION_X_WWW_FORM_URLENCODED) - .setContent("foo=bar") - .execute(); - response.expectStatusCode(is(OK)) - .expectContent(is("{foo=[bar]}foo=bar")); - assertTrue(driver.close()); - } - - @Test - public void requireThatFormPostKeepsContentWhenConfiguredTo() throws Exception { - final TestDriver driver = newDriverWithFormPostContentRemoved(new ParameterPrinterRequestHandler(), false); - final ResponseValidator response = - driver.client().newPost("/status.html") - .addHeader(CONTENT_TYPE, APPLICATION_X_WWW_FORM_URLENCODED) - .setContent("foo=bar") - .execute(); - response.expectStatusCode(is(OK)) - .expectContent(is("{foo=[bar]}foo=bar")); - assertTrue(driver.close()); - } - - @Test - public void requireThatFormPostRemovesContentWhenConfiguredTo() throws Exception { - final TestDriver driver = newDriverWithFormPostContentRemoved(new ParameterPrinterRequestHandler(), true); - final ResponseValidator response = - driver.client().newPost("/status.html") - .addHeader(CONTENT_TYPE, APPLICATION_X_WWW_FORM_URLENCODED) - .setContent("foo=bar") - .execute(); - response.expectStatusCode(is(OK)) - .expectContent(is("{foo=[bar]}")); - assertTrue(driver.close()); - } - - @Test - public void requireThatFormPostWithCharsetSpecifiedWorks() throws Exception { - final TestDriver driver = TestDrivers.newInstance(new ParameterPrinterRequestHandler()); - final String requestContent = generateContent('a', 30); - final ResponseValidator response = - driver.client().newPost("/status.html") - .addHeader(X_DISABLE_CHUNKING, "true") - .addHeader(CONTENT_TYPE, APPLICATION_X_WWW_FORM_URLENCODED + ";charset=UTF-8") - .setContent(requestContent) - .execute(); - response.expectStatusCode(is(OK)) - .expectContent(startsWith('{' + requestContent + "=[]}")); - assertTrue(driver.close()); - } - - @Test - public void requireThatEmptyFormPostWorks() throws Exception { - final TestDriver driver = TestDrivers.newInstance(new ParameterPrinterRequestHandler()); - final ResponseValidator response = - driver.client().newPost("/status.html") - .addHeader(CONTENT_TYPE, APPLICATION_X_WWW_FORM_URLENCODED) - .execute(); - response.expectStatusCode(is(OK)) - .expectContent(is("{}")); - assertTrue(driver.close()); - } - - @Test - public void requireThatFormParametersAreParsed() throws Exception { - final TestDriver driver = TestDrivers.newInstance(new ParameterPrinterRequestHandler()); - final ResponseValidator response = - driver.client().newPost("/status.html") - .addHeader(CONTENT_TYPE, APPLICATION_X_WWW_FORM_URLENCODED) - .setContent("a=b&c=d") - .execute(); - response.expectStatusCode(is(OK)) - .expectContent(startsWith("{a=[b], c=[d]}")); - assertTrue(driver.close()); - } - - @Test - public void requireThatUriParametersAreParsed() throws Exception { - final TestDriver driver = TestDrivers.newInstance(new ParameterPrinterRequestHandler()); - final ResponseValidator response = - driver.client().newPost("/status.html?a=b&c=d") - .addHeader(CONTENT_TYPE, APPLICATION_X_WWW_FORM_URLENCODED) - .execute(); - response.expectStatusCode(is(OK)) - .expectContent(is("{a=[b], c=[d]}")); - assertTrue(driver.close()); - } - - @Test - public void requireThatFormAndUriParametersAreMerged() throws Exception { - final TestDriver driver = TestDrivers.newInstance(new ParameterPrinterRequestHandler()); - final ResponseValidator response = - driver.client().newPost("/status.html?a=b&c=d1") - .addHeader(CONTENT_TYPE, APPLICATION_X_WWW_FORM_URLENCODED) - .setContent("c=d2&e=f") - .execute(); - response.expectStatusCode(is(OK)) - .expectContent(startsWith("{a=[b], c=[d1, d2], e=[f]}")); - assertTrue(driver.close()); - } - - @Test - public void requireThatFormCharsetIsHonored() throws Exception { - final TestDriver driver = newDriverWithFormPostContentRemoved(new ParameterPrinterRequestHandler(), true); - final ResponseValidator response = - driver.client().newPost("/status.html") - .addHeader(CONTENT_TYPE, APPLICATION_X_WWW_FORM_URLENCODED + ";charset=ISO-8859-1") - .setBinaryContent(new byte[]{66, (byte) 230, 114, 61, 98, 108, (byte) 229}) - .execute(); - response.expectStatusCode(is(OK)) - .expectContent(is("{B\u00e6r=[bl\u00e5]}")); - assertTrue(driver.close()); - } - - @Test - public void requireThatUnknownFormCharsetIsTreatedAsBadRequest() throws Exception { - final TestDriver driver = TestDrivers.newInstance(new ParameterPrinterRequestHandler()); - final ResponseValidator response = - driver.client().newPost("/status.html") - .addHeader(CONTENT_TYPE, APPLICATION_X_WWW_FORM_URLENCODED + ";charset=FLARBA-GARBA-7") - .setContent("a=b") - .execute(); - response.expectStatusCode(is(UNSUPPORTED_MEDIA_TYPE)); - assertTrue(driver.close()); - } - - @Test - public void requireThatFormPostWithPercentEncodedContentIsDecoded() throws Exception { - final TestDriver driver = TestDrivers.newInstance(new ParameterPrinterRequestHandler()); - final ResponseValidator response = - driver.client().newPost("/status.html") - .addHeader(CONTENT_TYPE, APPLICATION_X_WWW_FORM_URLENCODED) - .setContent("%20%3D%C3%98=%22%25+") - .execute(); - response.expectStatusCode(is(OK)) - .expectContent(startsWith("{ =\u00d8=[\"% ]}")); - assertTrue(driver.close()); - } - - @Test - public void requireThatFormPostWithThrowingHandlerIsExceptionSafe() throws Exception { - final TestDriver driver = TestDrivers.newInstance(new ThrowingHandler()); - final ResponseValidator response = - driver.client().newPost("/status.html") - .addHeader(CONTENT_TYPE, APPLICATION_X_WWW_FORM_URLENCODED) - .setContent("a=b") - .execute(); - response.expectStatusCode(is(INTERNAL_SERVER_ERROR)); - assertTrue(driver.close()); - } - - @Test - public void requireThatMultiPostWorks() throws Exception { - // This is taken from tcpdump of bug 5433352 and reassembled here to see that httpserver passes things on. - final String startTxtContent = "this is a test for POST."; - final String updaterConfContent - = "identifier = updater\n" - + "server_type = gds\n"; - final TestDriver driver = TestDrivers.newInstance(new EchoRequestHandler()); - final ResponseValidator response = - driver.client().newPost("/status.html") - .setMultipartContent( - newFileBody("", "start.txt", startTxtContent), - newFileBody("", "updater.conf", updaterConfContent)) - .execute(); - response.expectStatusCode(is(OK)) - .expectContent(containsString(startTxtContent)) - .expectContent(containsString(updaterConfContent)); - } - - @Test - public void requireThatRequestCookiesAreReceived() throws Exception { - final TestDriver driver = TestDrivers.newInstance(new CookiePrinterRequestHandler()); - final ResponseValidator response = - driver.client().newPost("/status.html") - .addHeader(COOKIE, "foo=bar") - .execute(); - response.expectStatusCode(is(OK)) - .expectContent(containsString("[foo=bar]")); - assertTrue(driver.close()); - } - - @Test - public void requireThatSetCookieHeaderIsCorrect() throws Exception { - final TestDriver driver = TestDrivers.newInstance(new CookieSetterRequestHandler( - new Cookie("foo", "bar") - .setDomain(".localhost") - .setHttpOnly(true) - .setPath("/foopath") - .setSecure(true))); - driver.client().get("/status.html") - .expectStatusCode(is(OK)) - .expectHeader("Set-Cookie", - is("foo=bar; Path=/foopath; Domain=.localhost; Secure; HttpOnly")); - assertTrue(driver.close()); - } - - @Test - public void requireThatTimeoutWorks() throws Exception { - final UnresponsiveHandler requestHandler = new UnresponsiveHandler(); - final TestDriver driver = TestDrivers.newInstance(requestHandler); - driver.client().get("/status.html") - .expectStatusCode(is(GATEWAY_TIMEOUT)); - ResponseDispatch.newInstance(OK).dispatch(requestHandler.responseHandler); - assertTrue(driver.close()); - } - - // Header with no value is disallowed by https://tools.ietf.org/html/rfc7230#section-3.2 - // Details in https://github.com/eclipse/jetty.project/issues/1116 - @Test - public void requireThatHeaderWithNullValueIsOmitted() throws Exception { - final TestDriver driver = TestDrivers.newInstance(new EchoWithHeaderRequestHandler("X-Foo", null)); - driver.client().get("/status.html") - .expectStatusCode(is(OK)) - .expectNoHeader("X-Foo"); - assertTrue(driver.close()); - } - - // Header with empty value is allowed by https://tools.ietf.org/html/rfc7230#section-3.2 - // Details in https://github.com/eclipse/jetty.project/issues/1116 - @Test - public void requireThatHeaderWithEmptyValueIsAllowed() throws Exception { - final TestDriver driver = TestDrivers.newInstance(new EchoWithHeaderRequestHandler("X-Foo", "")); - driver.client().get("/status.html") - .expectStatusCode(is(OK)) - .expectHeader("X-Foo", is("")); - assertTrue(driver.close()); - } - - @Test - public void requireThatNoConnectionHeaderMeansKeepAliveInHttp11KeepAliveDisabled() throws Exception { - final TestDriver driver = TestDrivers.newInstance(new EchoWithHeaderRequestHandler(CONNECTION, CLOSE)); - driver.client().get("/status.html") - .expectHeader(CONNECTION, is(CLOSE)); - assertThat(driver.close(), is(true)); - } - - @Test - public void requireThatConnectionIsClosedAfterXRequests() throws Exception { - final int MAX_KEEPALIVE_REQUESTS = 100; - final TestDriver driver = TestDrivers.newConfiguredInstance(new EchoRequestHandler(), - new ServerConfig.Builder(), - new ConnectorConfig.Builder().maxRequestsPerConnection(MAX_KEEPALIVE_REQUESTS)); - for (int i = 0; i < MAX_KEEPALIVE_REQUESTS - 1; i++) { - driver.client().get("/status.html") - .expectStatusCode(is(OK)) - .expectNoHeader(CONNECTION); - } - driver.client().get("/status.html") - .expectStatusCode(is(OK)) - .expectHeader(CONNECTION, is(CLOSE)); - assertTrue(driver.close()); - } - - @Test - public void requireThatServerCanRespondToSslRequest() throws Exception { - Path privateKeyFile = tmpFolder.newFile().toPath(); - Path certificateFile = tmpFolder.newFile().toPath(); - generatePrivateKeyAndCertificate(privateKeyFile, certificateFile); - - final TestDriver driver = TestDrivers.newInstanceWithSsl(new EchoRequestHandler(), certificateFile, privateKeyFile, TlsClientAuth.WANT); - driver.client().get("/status.html") - .expectStatusCode(is(OK)); - assertTrue(driver.close()); - } - - @Test - public void requireThatTlsClientAuthenticationEnforcerRejectsRequestsForNonWhitelistedPaths() throws IOException { - Path privateKeyFile = tmpFolder.newFile().toPath(); - Path certificateFile = tmpFolder.newFile().toPath(); - generatePrivateKeyAndCertificate(privateKeyFile, certificateFile); - TestDriver driver = TestDrivers.newInstanceWithSsl(new EchoRequestHandler(), certificateFile, privateKeyFile, TlsClientAuth.WANT); - - SSLContext trustStoreOnlyCtx = new SslContextBuilder() - .withTrustStore(certificateFile) - .build(); - - new SimpleHttpClient(trustStoreOnlyCtx, driver.server().getListenPort(), false) - .get("/dummy.html") - .expectStatusCode(is(UNAUTHORIZED)); - - assertTrue(driver.close()); - } - - @Test - public void requireThatTlsClientAuthenticationEnforcerAllowsRequestForWhitelistedPaths() throws IOException { - Path privateKeyFile = tmpFolder.newFile().toPath(); - Path certificateFile = tmpFolder.newFile().toPath(); - generatePrivateKeyAndCertificate(privateKeyFile, certificateFile); - TestDriver driver = TestDrivers.newInstanceWithSsl(new EchoRequestHandler(), certificateFile, privateKeyFile, TlsClientAuth.WANT); - - SSLContext trustStoreOnlyCtx = new SslContextBuilder() - .withTrustStore(certificateFile) - .build(); - - new SimpleHttpClient(trustStoreOnlyCtx, driver.server().getListenPort(), false) - .get("/status.html") - .expectStatusCode(is(OK)); - - assertTrue(driver.close()); - } - - @Test - public void requireThatConnectedAtReturnsNonZero() throws Exception { - final TestDriver driver = TestDrivers.newInstance(new ConnectedAtRequestHandler()); - driver.client().get("/status.html") - .expectStatusCode(is(OK)) - .expectContent(matchesPattern("\\d{13,}")); - assertThat(driver.close(), is(true)); - } - - @Test - public void requireThatGzipEncodingRequestsAreAutomaticallyDecompressed() throws Exception { - TestDriver driver = TestDrivers.newInstance(new ParameterPrinterRequestHandler()); - String requestContent = generateContent('a', 30); - ResponseValidator response = driver.client().newPost("/status.html") - .addHeader(CONTENT_TYPE, APPLICATION_X_WWW_FORM_URLENCODED) - .setGzipContent(requestContent) - .execute(); - response.expectStatusCode(is(OK)) - .expectContent(startsWith('{' + requestContent + "=[]}")); - assertTrue(driver.close()); - } - - @Test - public void requireThatResponseStatsAreCollected() throws Exception { - RequestTypeHandler handler = new RequestTypeHandler(); - TestDriver driver = TestDrivers.newInstance(handler); - HttpResponseStatisticsCollector statisticsCollector = ((AbstractHandlerContainer) driver.server().server().getHandler()) - .getChildHandlerByClass(HttpResponseStatisticsCollector.class); - - { - List<HttpResponseStatisticsCollector.StatisticsEntry> stats = statisticsCollector.takeStatistics(); - assertEquals(0, stats.size()); - } - - { - driver.client().newPost("/status.html").execute(); - var entry = waitForStatistics(statisticsCollector); - assertEquals("http", entry.scheme); - assertEquals("POST", entry.method); - assertEquals("http.status.2xx", entry.name); - assertEquals("write", entry.requestType); - assertEquals(1, entry.value); - } - - { - driver.client().newGet("/status.html").execute(); - var entry = waitForStatistics(statisticsCollector); - assertEquals("http", entry.scheme); - assertEquals("GET", entry.method); - assertEquals("http.status.2xx", entry.name); - assertEquals("read", entry.requestType); - assertEquals(1, entry.value); - } - - { - handler.setRequestType(Request.RequestType.READ); - driver.client().newPost("/status.html").execute(); - var entry = waitForStatistics(statisticsCollector); - assertEquals("Handler overrides request type", "read", entry.requestType); - } - - assertTrue(driver.close()); - } - - private HttpResponseStatisticsCollector.StatisticsEntry waitForStatistics(HttpResponseStatisticsCollector - statisticsCollector) { - List<HttpResponseStatisticsCollector.StatisticsEntry> entries = Collections.emptyList(); - int tries = 0; - while (entries.isEmpty() && tries < 10000) { - entries = statisticsCollector.takeStatistics(); - if (entries.isEmpty()) - try {Thread.sleep(100); } catch (InterruptedException e) {} - tries++; - } - assertEquals(1, entries.size()); - return entries.get(0); - } - - @Test - public void requireThatConnectionThrottleDoesNotBlockConnectionsBelowThreshold() throws Exception { - TestDriver driver = TestDrivers.newConfiguredInstance( - new EchoRequestHandler(), - new ServerConfig.Builder(), - new ConnectorConfig.Builder() - .throttling(new Throttling.Builder() - .enabled(true) - .maxAcceptRate(10) - .maxHeapUtilization(1.0) - .maxConnections(10))); - driver.client().get("/status.html") - .expectStatusCode(is(OK)); - assertTrue(driver.close()); - } - - @Test - public void requireThatMetricIsIncrementedWhenClientIsMissingCertificateOnHandshake() throws IOException { - Path privateKeyFile = tmpFolder.newFile().toPath(); - Path certificateFile = tmpFolder.newFile().toPath(); - generatePrivateKeyAndCertificate(privateKeyFile, certificateFile); - var metricConsumer = new MetricConsumerMock(); - InMemoryConnectionLog connectionLog = new InMemoryConnectionLog(); - TestDriver driver = createSslTestDriver(certificateFile, privateKeyFile, metricConsumer, connectionLog); - - SSLContext clientCtx = new SslContextBuilder() - .withTrustStore(certificateFile) - .build(); - assertHttpsRequestTriggersSslHandshakeException( - driver, clientCtx, null, null, "Received fatal alert: bad_certificate"); - verify(metricConsumer.mockitoMock(), atLeast(1)) - .add(MetricDefinitions.SSL_HANDSHAKE_FAILURE_MISSING_CLIENT_CERT, 1L, MetricConsumerMock.STATIC_CONTEXT); - assertTrue(driver.close()); - Assertions.assertThat(connectionLog.logEntries()).hasSize(1); - assertSslHandshakeFailurePresent( - connectionLog.logEntries().get(0), SSLHandshakeException.class, SslHandshakeFailure.MISSING_CLIENT_CERT.failureType()); - } - - @Test - public void requireThatMetricIsIncrementedWhenClientUsesIncompatibleTlsVersion() throws IOException { - Path privateKeyFile = tmpFolder.newFile().toPath(); - Path certificateFile = tmpFolder.newFile().toPath(); - generatePrivateKeyAndCertificate(privateKeyFile, certificateFile); - var metricConsumer = new MetricConsumerMock(); - InMemoryConnectionLog connectionLog = new InMemoryConnectionLog(); - TestDriver driver = createSslTestDriver(certificateFile, privateKeyFile, metricConsumer, connectionLog); - - SSLContext clientCtx = new SslContextBuilder() - .withTrustStore(certificateFile) - .withKeyStore(privateKeyFile, certificateFile) - .build(); - - boolean tlsv11Enabled = List.of(clientCtx.getDefaultSSLParameters().getProtocols()).contains("TLSv1.1"); - assumeTrue("TLSv1.1 must be enabled in installed JDK", tlsv11Enabled); - - assertHttpsRequestTriggersSslHandshakeException(driver, clientCtx, "TLSv1.1", null, "protocol"); - verify(metricConsumer.mockitoMock(), atLeast(1)) - .add(MetricDefinitions.SSL_HANDSHAKE_FAILURE_INCOMPATIBLE_PROTOCOLS, 1L, MetricConsumerMock.STATIC_CONTEXT); - assertTrue(driver.close()); - Assertions.assertThat(connectionLog.logEntries()).hasSize(1); - assertSslHandshakeFailurePresent( - connectionLog.logEntries().get(0), SSLHandshakeException.class, SslHandshakeFailure.INCOMPATIBLE_PROTOCOLS.failureType()); - } - - @Test - public void requireThatMetricIsIncrementedWhenClientUsesIncompatibleCiphers() throws IOException { - Path privateKeyFile = tmpFolder.newFile().toPath(); - Path certificateFile = tmpFolder.newFile().toPath(); - generatePrivateKeyAndCertificate(privateKeyFile, certificateFile); - var metricConsumer = new MetricConsumerMock(); - InMemoryConnectionLog connectionLog = new InMemoryConnectionLog(); - TestDriver driver = createSslTestDriver(certificateFile, privateKeyFile, metricConsumer, connectionLog); - - SSLContext clientCtx = new SslContextBuilder() - .withTrustStore(certificateFile) - .withKeyStore(privateKeyFile, certificateFile) - .build(); - - assertHttpsRequestTriggersSslHandshakeException( - driver, clientCtx, null, "TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA", "Received fatal alert: handshake_failure"); - verify(metricConsumer.mockitoMock(), atLeast(1)) - .add(MetricDefinitions.SSL_HANDSHAKE_FAILURE_INCOMPATIBLE_CIPHERS, 1L, MetricConsumerMock.STATIC_CONTEXT); - assertTrue(driver.close()); - Assertions.assertThat(connectionLog.logEntries()).hasSize(1); - assertSslHandshakeFailurePresent( - connectionLog.logEntries().get(0), SSLHandshakeException.class, SslHandshakeFailure.INCOMPATIBLE_CIPHERS.failureType()); - } - - @Test - public void requireThatMetricIsIncrementedWhenClientUsesInvalidCertificateInHandshake() throws IOException { - Path serverPrivateKeyFile = tmpFolder.newFile().toPath(); - Path serverCertificateFile = tmpFolder.newFile().toPath(); - generatePrivateKeyAndCertificate(serverPrivateKeyFile, serverCertificateFile); - var metricConsumer = new MetricConsumerMock(); - InMemoryConnectionLog connectionLog = new InMemoryConnectionLog(); - TestDriver driver = createSslTestDriver(serverCertificateFile, serverPrivateKeyFile, metricConsumer, connectionLog); - - Path clientPrivateKeyFile = tmpFolder.newFile().toPath(); - Path clientCertificateFile = tmpFolder.newFile().toPath(); - generatePrivateKeyAndCertificate(clientPrivateKeyFile, clientCertificateFile); - - SSLContext clientCtx = new SslContextBuilder() - .withKeyStore(clientPrivateKeyFile, clientCertificateFile) - .withTrustStore(serverCertificateFile) - .build(); - - assertHttpsRequestTriggersSslHandshakeException( - driver, clientCtx, null, null, "Received fatal alert: certificate_unknown"); - verify(metricConsumer.mockitoMock(), atLeast(1)) - .add(MetricDefinitions.SSL_HANDSHAKE_FAILURE_INVALID_CLIENT_CERT, 1L, MetricConsumerMock.STATIC_CONTEXT); - assertTrue(driver.close()); - Assertions.assertThat(connectionLog.logEntries()).hasSize(1); - assertSslHandshakeFailurePresent( - connectionLog.logEntries().get(0), SSLHandshakeException.class, SslHandshakeFailure.INVALID_CLIENT_CERT.failureType()); - } - - @Test - public void requireThatMetricIsIncrementedWhenClientUsesExpiredCertificateInHandshake() throws IOException { - Path rootPrivateKeyFile = tmpFolder.newFile().toPath(); - Path rootCertificateFile = tmpFolder.newFile().toPath(); - Path privateKeyFile = tmpFolder.newFile().toPath(); - Path certificateFile = tmpFolder.newFile().toPath(); - Instant notAfter = Instant.now().minus(100, ChronoUnit.DAYS); - generatePrivateKeyAndCertificate(rootPrivateKeyFile, rootCertificateFile, privateKeyFile, certificateFile, notAfter); - var metricConsumer = new MetricConsumerMock(); - InMemoryConnectionLog connectionLog = new InMemoryConnectionLog(); - TestDriver driver = createSslTestDriver(rootCertificateFile, rootPrivateKeyFile, metricConsumer, connectionLog); - - SSLContext clientCtx = new SslContextBuilder() - .withTrustStore(rootCertificateFile) - .withKeyStore(privateKeyFile, certificateFile) - .build(); - - assertHttpsRequestTriggersSslHandshakeException( - driver, clientCtx, null, null, "Received fatal alert: certificate_unknown"); - verify(metricConsumer.mockitoMock(), atLeast(1)) - .add(MetricDefinitions.SSL_HANDSHAKE_FAILURE_EXPIRED_CLIENT_CERT, 1L, MetricConsumerMock.STATIC_CONTEXT); - assertTrue(driver.close()); - Assertions.assertThat(connectionLog.logEntries()).hasSize(1); - - } - - @Test - public void requireThatProxyProtocolIsAcceptedAndActualRemoteAddressStoredInAccessLog() throws Exception { - Path privateKeyFile = tmpFolder.newFile().toPath(); - Path certificateFile = tmpFolder.newFile().toPath(); - generatePrivateKeyAndCertificate(privateKeyFile, certificateFile); - InMemoryRequestLog requestLogMock = new InMemoryRequestLog(); - InMemoryConnectionLog connectionLog = new InMemoryConnectionLog(); - TestDriver driver = createSslWithProxyProtocolTestDriver(certificateFile, privateKeyFile, requestLogMock, /*mixedMode*/connectionLog, false); - - String proxiedRemoteAddress = "192.168.0.100"; - int proxiedRemotePort = 12345; - sendJettyClientRequest(driver, certificateFile, new V1.Tag(proxiedRemoteAddress, proxiedRemotePort)); - sendJettyClientRequest(driver, certificateFile, new V2.Tag(proxiedRemoteAddress, proxiedRemotePort)); - assertTrue(driver.close()); - - assertEquals(2, requestLogMock.entries().size()); - assertLogEntryHasRemote(requestLogMock.entries().get(0), proxiedRemoteAddress, proxiedRemotePort); - assertLogEntryHasRemote(requestLogMock.entries().get(1), proxiedRemoteAddress, proxiedRemotePort); - Assertions.assertThat(connectionLog.logEntries()).hasSize(2); - assertLogEntryHasRemote(connectionLog.logEntries().get(0), proxiedRemoteAddress, proxiedRemotePort); - assertLogEntryHasRemote(connectionLog.logEntries().get(1), proxiedRemoteAddress, proxiedRemotePort); - } - - @Test - public void requireThatConnectorWithProxyProtocolMixedEnabledAcceptsBothProxyProtocolAndHttps() throws Exception { - Path privateKeyFile = tmpFolder.newFile().toPath(); - Path certificateFile = tmpFolder.newFile().toPath(); - generatePrivateKeyAndCertificate(privateKeyFile, certificateFile); - InMemoryRequestLog requestLogMock = new InMemoryRequestLog(); - InMemoryConnectionLog connectionLog = new InMemoryConnectionLog(); - TestDriver driver = createSslWithProxyProtocolTestDriver(certificateFile, privateKeyFile, requestLogMock, /*mixedMode*/connectionLog, true); - - String proxiedRemoteAddress = "192.168.0.100"; - sendJettyClientRequest(driver, certificateFile, null); - sendJettyClientRequest(driver, certificateFile, new V2.Tag(proxiedRemoteAddress, 12345)); - assertTrue(driver.close()); - - assertEquals(2, requestLogMock.entries().size()); - assertLogEntryHasRemote(requestLogMock.entries().get(0), "127.0.0.1", 0); - assertLogEntryHasRemote(requestLogMock.entries().get(1), proxiedRemoteAddress, 0); - Assertions.assertThat(connectionLog.logEntries()).hasSize(2); - assertLogEntryHasRemote(connectionLog.logEntries().get(0), null, 0); - assertLogEntryHasRemote(connectionLog.logEntries().get(1), proxiedRemoteAddress, 12345); - } - - @Test - public void requireThatJdiscLocalPortPropertyIsNotOverriddenByProxyProtocol() throws Exception { - Path privateKeyFile = tmpFolder.newFile().toPath(); - Path certificateFile = tmpFolder.newFile().toPath(); - generatePrivateKeyAndCertificate(privateKeyFile, certificateFile); - InMemoryRequestLog requestLogMock = new InMemoryRequestLog(); - InMemoryConnectionLog connectionLog = new InMemoryConnectionLog(); - TestDriver driver = createSslWithProxyProtocolTestDriver(certificateFile, privateKeyFile, requestLogMock, connectionLog, /*mixedMode*/false); - - String proxiedRemoteAddress = "192.168.0.100"; - int proxiedRemotePort = 12345; - String proxyLocalAddress = "10.0.0.10"; - int proxyLocalPort = 23456; - V2.Tag v2Tag = new V2.Tag(V2.Tag.Command.PROXY, null, V2.Tag.Protocol.STREAM, - proxiedRemoteAddress, proxiedRemotePort, proxyLocalAddress, proxyLocalPort, null); - ContentResponse response = sendJettyClientRequest(driver, certificateFile, v2Tag); - assertTrue(driver.close()); - - int clientPort = Integer.parseInt(response.getHeaders().get("Jdisc-Local-Port")); - assertNotEquals(proxyLocalPort, clientPort); - assertNotEquals(proxyLocalPort, connectionLog.logEntries().get(0).localPort().get().intValue()); - } - - @Test - public void requireThatConnectionIsTrackedInConnectionLog() throws Exception { - Path privateKeyFile = tmpFolder.newFile().toPath(); - Path certificateFile = tmpFolder.newFile().toPath(); - generatePrivateKeyAndCertificate(privateKeyFile, certificateFile); - InMemoryConnectionLog connectionLog = new InMemoryConnectionLog(); - Module overrideModule = binder -> binder.bind(ConnectionLog.class).toInstance(connectionLog); - TestDriver driver = TestDrivers.newInstanceWithSsl(new EchoRequestHandler(), certificateFile, privateKeyFile, TlsClientAuth.NEED, overrideModule); - int listenPort = driver.server().getListenPort(); - driver.client().get("/status.html"); - assertTrue(driver.close()); - List<ConnectionLogEntry> logEntries = connectionLog.logEntries(); - Assertions.assertThat(logEntries).hasSize(1); - ConnectionLogEntry logEntry = logEntries.get(0); - assertEquals(4, UUID.fromString(logEntry.id()).version()); - Assertions.assertThat(logEntry.timestamp()).isAfter(Instant.EPOCH); - Assertions.assertThat(logEntry.requests()).hasValue(1L); - Assertions.assertThat(logEntry.responses()).hasValue(1L); - Assertions.assertThat(logEntry.peerAddress()).hasValue("127.0.0.1"); - Assertions.assertThat(logEntry.localAddress()).hasValue("127.0.0.1"); - Assertions.assertThat(logEntry.localPort()).hasValue(listenPort); - Assertions.assertThat(logEntry.httpBytesReceived()).hasValueSatisfying(value -> Assertions.assertThat(value).isPositive()); - Assertions.assertThat(logEntry.httpBytesSent()).hasValueSatisfying(value -> Assertions.assertThat(value).isPositive()); - Assertions.assertThat(logEntry.sslProtocol()).hasValueSatisfying(TlsContext.ALLOWED_PROTOCOLS::contains); - Assertions.assertThat(logEntry.sslPeerSubject()).hasValue("CN=localhost"); - Assertions.assertThat(logEntry.sslCipherSuite()).hasValueSatisfying(cipher -> Assertions.assertThat(cipher).isNotBlank()); - Assertions.assertThat(logEntry.sslSessionId()).hasValueSatisfying(sessionId -> Assertions.assertThat(sessionId).hasSize(64)); - Assertions.assertThat(logEntry.sslPeerNotBefore()).hasValue(Instant.EPOCH); - Assertions.assertThat(logEntry.sslPeerNotAfter()).hasValue(Instant.EPOCH.plus(100_000, ChronoUnit.DAYS)); - } - - private ContentResponse sendJettyClientRequest(TestDriver testDriver, Path certificateFile, Object tag) - throws Exception { - HttpClient client = createJettyHttpClient(certificateFile); - try { - int maxAttempts = 3; - for (int attempt = 0; attempt < maxAttempts; attempt++) { - try { - ContentResponse response = client.newRequest(URI.create("https://localhost:" + testDriver.server().getListenPort() + "/")) - .tag(tag) - .send(); - assertEquals(200, response.getStatus()); - return response; - } catch (ExecutionException e) { - // Retry when the server closes the connection before the TLS handshake is completed. This have been observed in CI. - // We have been unable to reproduce this locally. The cause is therefor currently unknown. - log.log(Level.WARNING, String.format("Attempt %d failed: %s", attempt, e.getMessage()), e); - Thread.sleep(10); - } - } - throw new AssertionError("Failed to send request, see log for details"); - } finally { - client.stop(); - } - } - - // Using Jetty's http client as Apache httpclient does not support the proxy-protocol v1/v2. - private static HttpClient createJettyHttpClient(Path certificateFile) throws Exception { - SslContextFactory.Client clientSslCtxFactory = new SslContextFactory.Client(); - clientSslCtxFactory.setHostnameVerifier(NoopHostnameVerifier.INSTANCE); - clientSslCtxFactory.setSslContext(new SslContextBuilder().withTrustStore(certificateFile).build()); - - HttpClient client = new HttpClient(clientSslCtxFactory); - client.start(); - return client; - } - - private static void assertLogEntryHasRemote(RequestLogEntry entry, String expectedAddress, int expectedPort) { - assertEquals(expectedAddress, entry.peerAddress().get()); - if (expectedPort > 0) { - assertEquals(expectedPort, entry.peerPort().getAsInt()); - } - } - - private static void assertLogEntryHasRemote(ConnectionLogEntry entry, String expectedAddress, int expectedPort) { - if (expectedAddress != null) { - Assertions.assertThat(entry.remoteAddress()).hasValue(expectedAddress); - } else { - Assertions.assertThat(entry.remoteAddress()).isEmpty(); - } - if (expectedPort > 0) { - Assertions.assertThat(entry.remotePort()).hasValue(expectedPort); - } else { - Assertions.assertThat(entry.remotePort()).isEmpty(); - } - } - - private static void assertSslHandshakeFailurePresent( - ConnectionLogEntry entry, Class<? extends SSLHandshakeException> expectedException, String expectedType) { - Assertions.assertThat(entry.sslHandshakeFailure()).isPresent(); - ConnectionLogEntry.SslHandshakeFailure failure = entry.sslHandshakeFailure().get(); - assertEquals(expectedType, failure.type()); - ExceptionEntry exceptionEntry = failure.exceptionChain().get(0); - assertEquals(expectedException.getName(), exceptionEntry.name()); - } - - private static TestDriver createSslWithProxyProtocolTestDriver( - Path certificateFile, Path privateKeyFile, RequestLog requestLog, - ConnectionLog connectionLog, boolean mixedMode) { - ConnectorConfig.Builder connectorConfig = new ConnectorConfig.Builder() - .proxyProtocol(new ConnectorConfig.ProxyProtocol.Builder() - .enabled(true) - .mixedMode(mixedMode)) - .ssl(new ConnectorConfig.Ssl.Builder() - .enabled(true) - .privateKeyFile(privateKeyFile.toString()) - .certificateFile(certificateFile.toString()) - .caCertificateFile(certificateFile.toString())); - return TestDrivers.newConfiguredInstance( - new EchoRequestHandler(), - new ServerConfig.Builder().connectionLog(new ServerConfig.ConnectionLog.Builder().enabled(true)), - connectorConfig, - binder -> { - binder.bind(RequestLog.class).toInstance(requestLog); - binder.bind(ConnectionLog.class).toInstance(connectionLog); - }); - } - - private static TestDriver createSslTestDriver( - Path serverCertificateFile, Path serverPrivateKeyFile, MetricConsumerMock metricConsumer, InMemoryConnectionLog connectionLog) throws IOException { - Module extraModule = binder -> { - binder.bind(MetricConsumer.class).toInstance(metricConsumer.mockitoMock()); - binder.bind(ConnectionLog.class).toInstance(connectionLog); - }; - return TestDrivers.newInstanceWithSsl( - new EchoRequestHandler(), serverCertificateFile, serverPrivateKeyFile, TlsClientAuth.NEED, extraModule); - } - - private static void assertHttpsRequestTriggersSslHandshakeException( - TestDriver testDriver, - SSLContext sslContext, - String protocolOverride, - String cipherOverride, - String expectedExceptionSubstring) throws IOException { - List<String> protocols = protocolOverride != null ? List.of(protocolOverride) : null; - List<String> ciphers = cipherOverride != null ? List.of(cipherOverride) : null; - try (var client = new SimpleHttpClient(sslContext, protocols, ciphers, testDriver.server().getListenPort(), false)) { - client.get("/status.html"); - fail("SSLHandshakeException expected"); - } catch (SSLHandshakeException e) { - assertThat(e.getMessage(), containsString(expectedExceptionSubstring)); - } catch (SSLException e) { - // This exception is thrown if Apache httpclient's write thread detects the handshake failure before the read thread. - log.log(Level.WARNING, "Client failed to get a proper TLS handshake response: " + e.getMessage(), e); - // Only ignore a subset of exceptions - assertThat(e.getMessage(), anyOf(containsString("readHandshakeRecord"), containsString("Broken pipe"))); - } - } - - private static void generatePrivateKeyAndCertificate(Path privateKeyFile, Path certificateFile) throws IOException { - KeyPair keyPair = KeyUtils.generateKeypair(EC); - Files.writeString(privateKeyFile, KeyUtils.toPem(keyPair.getPrivate())); - - X509Certificate certificate = X509CertificateBuilder - .fromKeypair( - keyPair, new X500Principal("CN=localhost"), Instant.EPOCH, Instant.EPOCH.plus(100_000, ChronoUnit.DAYS), SHA256_WITH_ECDSA, BigInteger.ONE) - .build(); - Files.writeString(certificateFile, X509CertificateUtils.toPem(certificate)); - } - - private static void generatePrivateKeyAndCertificate(Path rootPrivateKeyFile, Path rootCertificateFile, - Path privateKeyFile, Path certificateFile, Instant notAfter) throws IOException { - generatePrivateKeyAndCertificate(rootPrivateKeyFile, rootCertificateFile); - X509Certificate rootCertificate = X509CertificateUtils.fromPem(Files.readString(rootCertificateFile)); - PrivateKey privateKey = KeyUtils.fromPemEncodedPrivateKey(Files.readString(rootPrivateKeyFile)); - - KeyPair keyPair = KeyUtils.generateKeypair(EC); - Files.writeString(privateKeyFile, KeyUtils.toPem(keyPair.getPrivate())); - Pkcs10Csr csr = Pkcs10CsrBuilder.fromKeypair(new X500Principal("CN=myclient"), keyPair, SHA256_WITH_ECDSA).build(); - X509Certificate certificate = X509CertificateBuilder - .fromCsr(csr, rootCertificate.getSubjectX500Principal(), Instant.EPOCH, notAfter, privateKey, SHA256_WITH_ECDSA, BigInteger.ONE) - .build(); - Files.writeString(certificateFile, X509CertificateUtils.toPem(certificate)); - } - - private static RequestHandler mockRequestHandler() { - final RequestHandler mockRequestHandler = mock(RequestHandler.class); - when(mockRequestHandler.refer()).thenReturn(References.NOOP_REFERENCE); - return mockRequestHandler; - } - - private static String generateContent(final char c, final int len) { - final StringBuilder ret = new StringBuilder(len); - for (int i = 0; i < len; ++i) { - ret.append(c); - } - return ret.toString(); - } - - private static TestDriver newDriverWithFormPostContentRemoved(RequestHandler requestHandler, - boolean removeFormPostBody) throws Exception { - return TestDrivers.newConfiguredInstance( - requestHandler, - new ServerConfig.Builder() - .removeRawPostBodyForWwwUrlEncodedPost(removeFormPostBody), - new ConnectorConfig.Builder()); - } - - private static FormBodyPart newFileBody(final String parameterName, final String fileName, final String fileContent) { - return new FormBodyPart( - parameterName, - new StringBody(fileContent, ContentType.TEXT_PLAIN) { - @Override - public String getFilename() { - return fileName; - } - - @Override - public String getTransferEncoding() { - return "binary"; - } - - @Override - public String getMimeType() { - return ""; - } - - @Override - public String getCharset() { - return null; - } - }); - } - - private static class ConnectedAtRequestHandler extends AbstractRequestHandler { - - @Override - public ContentChannel handleRequest(final Request request, final ResponseHandler handler) { - final HttpRequest httpRequest = (HttpRequest)request; - final String connectedAt = String.valueOf(httpRequest.getConnectedAt(TimeUnit.MILLISECONDS)); - final ContentChannel ch = handler.handleResponse(new Response(OK)); - ch.write(ByteBuffer.wrap(connectedAt.getBytes(StandardCharsets.UTF_8)), null); - ch.close(null); - return null; - } - } - - private static class CookieSetterRequestHandler extends AbstractRequestHandler { - - final Cookie cookie; - - CookieSetterRequestHandler(final Cookie cookie) { - this.cookie = cookie; - } - - @Override - public ContentChannel handleRequest(final Request request, final ResponseHandler handler) { - final HttpResponse response = HttpResponse.newInstance(OK); - response.encodeSetCookieHeader(Collections.singletonList(cookie)); - ResponseDispatch.newInstance(response).dispatch(handler); - return null; - } - } - - private static class CookiePrinterRequestHandler extends AbstractRequestHandler { - - @Override - public ContentChannel handleRequest(final Request request, final ResponseHandler handler) { - final List<Cookie> cookies = new ArrayList<>(((HttpRequest)request).decodeCookieHeader()); - Collections.sort(cookies, new CookieComparator()); - final ContentChannel out = ResponseDispatch.newInstance(Response.Status.OK).connect(handler); - out.write(StandardCharsets.UTF_8.encode(cookies.toString()), null); - out.close(null); - return null; - } - } - - private static class ParameterPrinterRequestHandler extends AbstractRequestHandler { - - private static final CompletionHandler NULL_COMPLETION_HANDLER = null; - - @Override - public ContentChannel handleRequest(Request request, ResponseHandler handler) { - Map<String, List<String>> parameters = new TreeMap<>(((HttpRequest)request).parameters()); - ContentChannel responseContentChannel = ResponseDispatch.newInstance(Response.Status.OK).connect(handler); - responseContentChannel.write(ByteBuffer.wrap(parameters.toString().getBytes(StandardCharsets.UTF_8)), - NULL_COMPLETION_HANDLER); - - // Have the request content written back to the response. - return responseContentChannel; - } - } - - private static class RequestTypeHandler extends AbstractRequestHandler { - - private Request.RequestType requestType = null; - - public void setRequestType(Request.RequestType requestType) { - this.requestType = requestType; - } - - @Override - public ContentChannel handleRequest(Request request, ResponseHandler handler) { - Response response = new Response(OK); - response.setRequestType(requestType); - return handler.handleResponse(response); - } - } - - private static class ThrowingHandler extends AbstractRequestHandler { - @Override - public ContentChannel handleRequest(final Request request, final ResponseHandler handler) { - throw new RuntimeException("Deliberately thrown exception"); - } - } - - private static class UnresponsiveHandler extends AbstractRequestHandler { - - ResponseHandler responseHandler; - - @Override - public ContentChannel handleRequest(final Request request, final ResponseHandler handler) { - request.setTimeout(100, TimeUnit.MILLISECONDS); - responseHandler = handler; - return null; - } - } - - private static class EchoRequestHandler extends AbstractRequestHandler { - - @Override - public ContentChannel handleRequest(final Request request, final ResponseHandler handler) { - int port = request.getUri().getPort(); - Response response = new Response(OK); - response.headers().put("Jdisc-Local-Port", Integer.toString(port)); - return handler.handleResponse(response); - } - } - - private static class EchoWithHeaderRequestHandler extends AbstractRequestHandler { - - final String headerName; - final String headerValue; - - EchoWithHeaderRequestHandler(final String headerName, final String headerValue) { - this.headerName = headerName; - this.headerValue = headerValue; - } - - @Override - public ContentChannel handleRequest(final Request request, final ResponseHandler handler) { - final Response response = new Response(OK); - response.headers().add(headerName, headerValue); - return handler.handleResponse(response); - } - } - - private static Module newBindingSetSelector(final String setName) { - return new AbstractModule() { - - @Override - protected void configure() { - bind(BindingSetSelector.class).toInstance(new BindingSetSelector() { - - @Override - public String select(final URI uri) { - return setName; - } - }); - } - }; - } - - private static class CookieComparator implements Comparator<Cookie> { - - @Override - public int compare(final Cookie lhs, final Cookie rhs) { - return lhs.getName().compareTo(rhs.getName()); - } - } - -} diff --git a/jdisc_http_service/src/test/java/com/yahoo/jdisc/http/server/jetty/InMemoryConnectionLog.java b/jdisc_http_service/src/test/java/com/yahoo/jdisc/http/server/jetty/InMemoryConnectionLog.java deleted file mode 100644 index 6d1baf0423f..00000000000 --- a/jdisc_http_service/src/test/java/com/yahoo/jdisc/http/server/jetty/InMemoryConnectionLog.java +++ /dev/null @@ -1,25 +0,0 @@ -// Copyright Verizon Media. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. -package com.yahoo.jdisc.http.server.jetty; - -import com.yahoo.container.logging.ConnectionLog; -import com.yahoo.container.logging.ConnectionLogEntry; - -import java.util.List; -import java.util.concurrent.CopyOnWriteArrayList; - -/** - * A {@link ConnectionLog} that aggregates log entries in memory - * - * @author bjorncs - */ -class InMemoryConnectionLog implements ConnectionLog { - - private final List<ConnectionLogEntry> logEntries = new CopyOnWriteArrayList<>(); - - @Override - public void log(ConnectionLogEntry entry) { - logEntries.add(entry); - } - - List<ConnectionLogEntry> logEntries() { return logEntries; } -} diff --git a/jdisc_http_service/src/test/java/com/yahoo/jdisc/http/server/jetty/InMemoryRequestLog.java b/jdisc_http_service/src/test/java/com/yahoo/jdisc/http/server/jetty/InMemoryRequestLog.java deleted file mode 100644 index b87ec5e8b8b..00000000000 --- a/jdisc_http_service/src/test/java/com/yahoo/jdisc/http/server/jetty/InMemoryRequestLog.java +++ /dev/null @@ -1,20 +0,0 @@ -// Copyright Verizon Media. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. -package com.yahoo.jdisc.http.server.jetty; - -import com.yahoo.container.logging.RequestLog; -import com.yahoo.container.logging.RequestLogEntry; - -import java.util.List; -import java.util.concurrent.CopyOnWriteArrayList; - -/** - * @author bjorncs - */ -public class InMemoryRequestLog implements RequestLog { - - private final List<RequestLogEntry> entries = new CopyOnWriteArrayList<>(); - - @Override public void log(RequestLogEntry entry) { entries.add(entry); } - - List<RequestLogEntry> entries() { return entries; } -} diff --git a/jdisc_http_service/src/test/java/com/yahoo/jdisc/http/server/jetty/JDiscHttpServletTest.java b/jdisc_http_service/src/test/java/com/yahoo/jdisc/http/server/jetty/JDiscHttpServletTest.java deleted file mode 100644 index 230f59cbb34..00000000000 --- a/jdisc_http_service/src/test/java/com/yahoo/jdisc/http/server/jetty/JDiscHttpServletTest.java +++ /dev/null @@ -1,80 +0,0 @@ -// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. -package com.yahoo.jdisc.http.server.jetty; - -import com.yahoo.jdisc.Request; -import com.yahoo.jdisc.Response; -import com.yahoo.jdisc.handler.AbstractRequestHandler; -import com.yahoo.jdisc.handler.ContentChannel; -import com.yahoo.jdisc.handler.RequestHandler; -import com.yahoo.jdisc.handler.ResponseHandler; -import org.apache.http.client.methods.HttpDelete; -import org.apache.http.client.methods.HttpGet; -import org.apache.http.client.methods.HttpHead; -import org.apache.http.client.methods.HttpOptions; -import org.apache.http.client.methods.HttpPatch; -import org.apache.http.client.methods.HttpPost; -import org.apache.http.client.methods.HttpPut; -import org.apache.http.client.methods.HttpRequestBase; -import org.apache.http.client.methods.HttpTrace; -import org.junit.Test; - -import java.io.IOException; -import java.net.URI; - -import static com.yahoo.jdisc.Response.Status.METHOD_NOT_ALLOWED; -import static com.yahoo.jdisc.Response.Status.OK; -import static org.hamcrest.CoreMatchers.is; -import static org.hamcrest.MatcherAssert.assertThat; - -/** - * @author Simon Thoresen Hult - */ -public class JDiscHttpServletTest { - - @Test - public void requireThatServerRespondsToAllMethods() throws Exception { - final TestDriver driver = TestDrivers.newInstance(newEchoHandler()); - final URI uri = driver.client().newUri("/status.html"); - driver.client().execute(new HttpGet(uri)) - .expectStatusCode(is(OK)); - driver.client().execute(new HttpPost(uri)) - .expectStatusCode(is(OK)); - driver.client().execute(new HttpHead(uri)) - .expectStatusCode(is(OK)); - driver.client().execute(new HttpPut(uri)) - .expectStatusCode(is(OK)); - driver.client().execute(new HttpDelete(uri)) - .expectStatusCode(is(OK)); - driver.client().execute(new HttpOptions(uri)) - .expectStatusCode(is(OK)); - driver.client().execute(new HttpTrace(uri)) - .expectStatusCode(is(OK)); - driver.client().execute(new HttpPatch(uri)) - .expectStatusCode(is(OK)); - assertThat(driver.close(), is(true)); - } - - @Test - public void requireThatServerResponds405ToUnknownMethods() throws IOException { - TestDriver driver = TestDrivers.newInstance(newEchoHandler()); - final URI uri = driver.client().newUri("/status.html"); - driver.client().execute(new UnknownMethodHttpRequest(uri)) - .expectStatusCode(is(METHOD_NOT_ALLOWED)); - assertThat(driver.close(), is(true)); - } - - private static RequestHandler newEchoHandler() { - return new AbstractRequestHandler() { - - @Override - public ContentChannel handleRequest(final Request request, final ResponseHandler handler) { - return handler.handleResponse(new Response(OK)); - } - }; - } - - private static class UnknownMethodHttpRequest extends HttpRequestBase { - UnknownMethodHttpRequest(URI uri) { setURI(uri); } - @Override public String getMethod() { return "UNKNOWN_METHOD"; } - } -} diff --git a/jdisc_http_service/src/test/java/com/yahoo/jdisc/http/server/jetty/MetricConsumerMock.java b/jdisc_http_service/src/test/java/com/yahoo/jdisc/http/server/jetty/MetricConsumerMock.java deleted file mode 100644 index f839d83a800..00000000000 --- a/jdisc_http_service/src/test/java/com/yahoo/jdisc/http/server/jetty/MetricConsumerMock.java +++ /dev/null @@ -1,28 +0,0 @@ -// Copyright Verizon Media. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. -package com.yahoo.jdisc.http.server.jetty; - -import com.google.inject.Module; -import com.yahoo.jdisc.Metric; -import com.yahoo.jdisc.application.MetricConsumer; - -import static org.mockito.ArgumentMatchers.anyMap; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.when; - -/** - * @author bjorncs - */ -class MetricConsumerMock { - - static final Metric.Context STATIC_CONTEXT = new Metric.Context() {}; - - private final MetricConsumer mockitoMock = mock(MetricConsumer.class); - - MetricConsumerMock() { - when(mockitoMock.createContext(anyMap())).thenReturn(STATIC_CONTEXT); - } - - MetricConsumer mockitoMock() { return mockitoMock; } - Module asGuiceModule() { return binder -> binder.bind(MetricConsumer.class).toInstance(mockitoMock); } - -} diff --git a/jdisc_http_service/src/test/java/com/yahoo/jdisc/http/server/jetty/SimpleHttpClient.java b/jdisc_http_service/src/test/java/com/yahoo/jdisc/http/server/jetty/SimpleHttpClient.java deleted file mode 100644 index f1d710bd10f..00000000000 --- a/jdisc_http_service/src/test/java/com/yahoo/jdisc/http/server/jetty/SimpleHttpClient.java +++ /dev/null @@ -1,202 +0,0 @@ -// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. -package com.yahoo.jdisc.http.server.jetty; - -import com.yahoo.jdisc.Request; -import org.apache.http.Header; -import org.apache.http.HttpEntity; -import org.apache.http.HttpResponse; -import org.apache.http.client.entity.GzipCompressingEntity; -import org.apache.http.client.methods.CloseableHttpResponse; -import org.apache.http.client.methods.HttpGet; -import org.apache.http.client.methods.HttpPost; -import org.apache.http.client.methods.HttpUriRequest; -import org.apache.http.config.Registry; -import org.apache.http.config.RegistryBuilder; -import org.apache.http.conn.socket.ConnectionSocketFactory; -import org.apache.http.conn.ssl.DefaultHostnameVerifier; -import org.apache.http.conn.ssl.SSLConnectionSocketFactory; -import org.apache.http.entity.ByteArrayEntity; -import org.apache.http.entity.StringEntity; -import org.apache.http.entity.mime.FormBodyPart; -import org.apache.http.entity.mime.MultipartEntityBuilder; -import org.apache.http.impl.client.CloseableHttpClient; -import org.apache.http.impl.client.HttpClientBuilder; -import org.apache.http.impl.conn.BasicHttpClientConnectionManager; -import org.apache.http.util.EntityUtils; -import org.hamcrest.Matcher; -import org.hamcrest.MatcherAssert; - -import javax.net.ssl.SSLContext; -import java.io.IOException; -import java.net.URI; -import java.nio.charset.StandardCharsets; -import java.util.Arrays; -import java.util.List; - -import static org.hamcrest.CoreMatchers.is; -import static org.hamcrest.CoreMatchers.not; -import static org.hamcrest.CoreMatchers.nullValue; -import static org.hamcrest.MatcherAssert.assertThat; -import static org.junit.Assert.assertNotNull; - -/** - * A simple http client for testing - * - * @author Simon Thoresen Hult - * @author bjorncs - */ -public class SimpleHttpClient implements AutoCloseable { - - private final CloseableHttpClient delegate; - private final String scheme; - private final int listenPort; - - public SimpleHttpClient(SSLContext sslContext, int listenPort, boolean useCompression) { - this(sslContext, null, null, listenPort, useCompression); - } - - public SimpleHttpClient(SSLContext sslContext, List<String> enabledProtocols, List<String> enabledCiphers, - int listenPort, boolean useCompression) { - HttpClientBuilder builder = HttpClientBuilder.create(); - if (!useCompression) { - builder.disableContentCompression(); - } - if (sslContext != null) { - SSLConnectionSocketFactory sslConnectionFactory = new SSLConnectionSocketFactory( - sslContext, - toArray(enabledProtocols), - toArray(enabledCiphers), - new DefaultHostnameVerifier()); - builder.setSSLSocketFactory(sslConnectionFactory); - - Registry<ConnectionSocketFactory> registry = RegistryBuilder.<ConnectionSocketFactory>create() - .register("https", sslConnectionFactory) - .build(); - builder.setConnectionManager(new BasicHttpClientConnectionManager(registry)); - scheme = "https"; - } else { - scheme = "http"; - } - this.delegate = builder.build(); - this.listenPort = listenPort; - } - - private static String[] toArray(List<String> list) { - return list != null ? list.toArray(new String[0]) : null; - } - - public URI newUri(final String path) { - return URI.create(scheme + "://localhost:" + listenPort + path); - } - - public RequestExecutor newGet(String path) { - return newRequest(new HttpGet(newUri(path))); - } - - public RequestExecutor newPost(String path) { - return newRequest(new HttpPost(newUri(path))); - } - - public RequestExecutor newRequest(HttpUriRequest request) { - return new RequestExecutor().setRequest(request); - } - - public ResponseValidator execute(HttpUriRequest request) throws IOException { - return newRequest(request).execute(); - } - - public ResponseValidator get(String path) throws IOException { - return newGet(path).execute(); - } - - @Override - public void close() throws IOException { - delegate.close(); - } - - public class RequestExecutor { - - private HttpUriRequest request; - private HttpEntity entity; - - public RequestExecutor setRequest(final HttpUriRequest request) { - this.request = request; - return this; - } - - public RequestExecutor addHeader(final String name, final String value) { - this.request.addHeader(name, value); - return this; - } - - public RequestExecutor setContent(final String content) { - this.entity = new StringEntity(content, StandardCharsets.UTF_8); - return this; - } - - public RequestExecutor setGzipContent(String content) { - this.entity = new GzipCompressingEntity(new StringEntity(content, StandardCharsets.UTF_8)); - return this; - } - - public RequestExecutor setBinaryContent(final byte[] content) { - this.entity = new ByteArrayEntity(content); - return this; - } - - public RequestExecutor setMultipartContent(final FormBodyPart... parts) { - MultipartEntityBuilder builder = MultipartEntityBuilder.create(); - Arrays.stream(parts).forEach(part -> builder.addPart(part.getName(), part.getBody())); - this.entity = builder.build(); - return this; - } - - public ResponseValidator execute() throws IOException { - if (entity != null) { - ((HttpPost)request).setEntity(entity); - } - try (CloseableHttpResponse response = delegate.execute(request)){ - return new ResponseValidator(response); - } - } - } - - public static class ResponseValidator { - - private final HttpResponse response; - private final String content; - - public ResponseValidator(HttpResponse response) throws IOException { - this.response = response; - - HttpEntity entity = response.getEntity(); - this.content = entity == null ? null : EntityUtils.toString(entity, StandardCharsets.UTF_8); - } - - public ResponseValidator expectStatusCode(Matcher<Integer> matcher) { - MatcherAssert.assertThat(response.getStatusLine().getStatusCode(), matcher); - return this; - } - - public ResponseValidator expectHeader(String headerName, Matcher<String> matcher) { - Header firstHeader = response.getFirstHeader(headerName); - String headerValue = firstHeader != null ? firstHeader.getValue() : null; - MatcherAssert.assertThat(headerValue, matcher); - assertNotNull(firstHeader); - return this; - } - - public ResponseValidator expectNoHeader(String headerName) { - Header firstHeader = response.getFirstHeader(headerName); - assertThat(firstHeader, is(nullValue())); - return this; - } - - public ResponseValidator expectContent(final Matcher<String> matcher) { - MatcherAssert.assertThat(content, matcher); - return this; - } - - } - -} diff --git a/jdisc_http_service/src/test/java/com/yahoo/jdisc/http/server/jetty/SslHandshakeFailedListenerTest.java b/jdisc_http_service/src/test/java/com/yahoo/jdisc/http/server/jetty/SslHandshakeFailedListenerTest.java deleted file mode 100644 index 20f050d715d..00000000000 --- a/jdisc_http_service/src/test/java/com/yahoo/jdisc/http/server/jetty/SslHandshakeFailedListenerTest.java +++ /dev/null @@ -1,42 +0,0 @@ -// Copyright Verizon Media. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. -package com.yahoo.jdisc.http.server.jetty; - -import com.yahoo.jdisc.Metric; -import org.eclipse.jetty.io.ssl.SslHandshakeListener; -import org.junit.Test; - -import javax.net.ssl.SSLEngine; -import javax.net.ssl.SSLHandshakeException; -import java.util.Map; - -import static org.mockito.ArgumentMatchers.eq; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.when; - -/** - * @author mortent - */ -public class SslHandshakeFailedListenerTest { - - private Metric metrics = mock(Metric.class); - SslHandshakeFailedListener listener = new SslHandshakeFailedListener(metrics, "connector", 1234); - - @Test - public void includes_client_ip_dimension_present_when_peer_available() { - listener.handshakeFailed(handshakeEvent(true), new SSLHandshakeException("Empty server certificate chain")); - verify(metrics).createContext(eq(Map.of("clientIp", "127.0.0.1", "serverName", "connector", "serverPort", 1234))); - } - - @Test - public void does_not_include_client_ip_dimension_present_when_peer_unavailable() { - listener.handshakeFailed(handshakeEvent(false), new SSLHandshakeException("Empty server certificate chain")); - verify(metrics).createContext(eq(Map.of("serverName", "connector", "serverPort", 1234))); - } - - private SslHandshakeListener.Event handshakeEvent(boolean includePeer) { - var sslEngine = mock(SSLEngine.class); - if(includePeer) when(sslEngine.getPeerHost()).thenReturn("127.0.0.1"); - return new SslHandshakeListener.Event(sslEngine); - } -} diff --git a/jdisc_http_service/src/test/java/com/yahoo/jdisc/http/server/jetty/TestDriver.java b/jdisc_http_service/src/test/java/com/yahoo/jdisc/http/server/jetty/TestDriver.java deleted file mode 100644 index 875889ed5ce..00000000000 --- a/jdisc_http_service/src/test/java/com/yahoo/jdisc/http/server/jetty/TestDriver.java +++ /dev/null @@ -1,79 +0,0 @@ -// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. -package com.yahoo.jdisc.http.server.jetty; - -import com.google.inject.Module; -import com.yahoo.jdisc.application.ContainerBuilder; -import com.yahoo.jdisc.handler.RequestHandler; -import com.yahoo.jdisc.http.ConnectorConfig; -import com.yahoo.security.SslContextBuilder; - -import javax.net.ssl.SSLContext; -import java.nio.file.Paths; - -import static com.yahoo.yolean.Exceptions.uncheck; - -/** - * This class is based on the class by the same name in the jdisc_http_service module. - * It provides functionality for setting up a jdisc container with an HTTP server and handlers. - * - * @author Simon Thoresen Hult - * @author bakksjo - */ -public class TestDriver { - - private final com.yahoo.jdisc.test.TestDriver driver; - private final JettyHttpServer server; - private final SimpleHttpClient client; - - private TestDriver(com.yahoo.jdisc.test.TestDriver driver, JettyHttpServer server, SimpleHttpClient client) { - this.driver = driver; - this.server = server; - this.client = client; - } - - public static TestDriver newInstance(Class<? extends JettyHttpServer> serverClass, - RequestHandler requestHandler, - Module testConfig) { - com.yahoo.jdisc.test.TestDriver driver = - com.yahoo.jdisc.test.TestDriver.newSimpleApplicationInstance(testConfig); - ContainerBuilder builder = driver.newContainerBuilder(); - JettyHttpServer server = builder.getInstance(serverClass); - builder.serverProviders().install(server); - builder.serverBindings().bind("http://*/*", requestHandler); - driver.activateContainer(builder); - server.start(); - - SimpleHttpClient client = new SimpleHttpClient(newSslContext(builder), server.getListenPort(), false); - return new TestDriver(driver, server, client); - } - - public boolean close() { - server.close(); - server.release(); - uncheck(client::close); - return driver.close(); - } - - public JettyHttpServer server() { return server; } - - public SimpleHttpClient client() { return client; } - - public SimpleHttpClient newClient(final boolean useCompression) { - return new SimpleHttpClient(newSslContext(), server.getListenPort(), useCompression); - } - - public SSLContext newSslContext() { - return newSslContext(driver.newContainerBuilder()); - } - - private static SSLContext newSslContext(ContainerBuilder builder) { - ConnectorConfig.Ssl sslConfig = builder.getInstance(ConnectorConfig.class).ssl(); - if (!sslConfig.enabled()) return null; - - return new SslContextBuilder() - .withKeyStore(Paths.get(sslConfig.privateKeyFile()), Paths.get(sslConfig.certificateFile())) - .withTrustStore(Paths.get(sslConfig.caCertificateFile())) - .build(); - } - -} diff --git a/jdisc_http_service/src/test/java/com/yahoo/jdisc/http/server/jetty/TestDrivers.java b/jdisc_http_service/src/test/java/com/yahoo/jdisc/http/server/jetty/TestDrivers.java deleted file mode 100644 index 7d7530c32e0..00000000000 --- a/jdisc_http_service/src/test/java/com/yahoo/jdisc/http/server/jetty/TestDrivers.java +++ /dev/null @@ -1,94 +0,0 @@ -// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. -package com.yahoo.jdisc.http.server.jetty; - -import com.google.inject.AbstractModule; -import com.google.inject.Module; -import com.google.inject.util.Modules; -import com.yahoo.container.logging.ConnectionLog; -import com.yahoo.container.logging.RequestLog; -import com.yahoo.jdisc.handler.RequestHandler; -import com.yahoo.jdisc.http.ConnectorConfig; -import com.yahoo.jdisc.http.ServerConfig; -import com.yahoo.jdisc.http.ServletPathsConfig; -import com.yahoo.jdisc.http.guiceModules.ConnectorFactoryRegistryModule; -import com.yahoo.jdisc.http.guiceModules.ServletModule; - -import java.nio.file.Path; - -/** - * @author Simon Thoresen Hult - * @author bjorncs - */ -public class TestDrivers { - - public static TestDriver newConfiguredInstance(RequestHandler requestHandler, - ServerConfig.Builder serverConfig, - ConnectorConfig.Builder connectorConfig, - Module... guiceModules) { - return TestDriver.newInstance( - JettyHttpServer.class, - requestHandler, - newConfigModule(serverConfig, connectorConfig, guiceModules)); - } - - public static TestDriver newInstance(RequestHandler requestHandler, Module... guiceModules) { - return TestDriver.newInstance( - JettyHttpServer.class, - requestHandler, - newConfigModule( - new ServerConfig.Builder(), - new ConnectorConfig.Builder(), - guiceModules - )); - } - - public enum TlsClientAuth { NEED, WANT } - - public static TestDriver newInstanceWithSsl(RequestHandler requestHandler, - Path certificateFile, - Path privateKeyFile, - TlsClientAuth tlsClientAuth, - Module... guiceModules) { - return TestDriver.newInstance( - JettyHttpServer.class, - requestHandler, - newConfigModule( - new ServerConfig.Builder().connectionLog(new ServerConfig.ConnectionLog.Builder().enabled(true)), - new ConnectorConfig.Builder() - .tlsClientAuthEnforcer( - new ConnectorConfig.TlsClientAuthEnforcer.Builder() - .enable(true) - .pathWhitelist("/status.html")) - .ssl(new ConnectorConfig.Ssl.Builder() - .enabled(true) - .clientAuth(tlsClientAuth == TlsClientAuth.NEED - ? ConnectorConfig.Ssl.ClientAuth.Enum.NEED_AUTH - : ConnectorConfig.Ssl.ClientAuth.Enum.WANT_AUTH) - .privateKeyFile(privateKeyFile.toString()) - .certificateFile(certificateFile.toString()) - .caCertificateFile(certificateFile.toString())), - guiceModules)); - } - - private static Module newConfigModule(ServerConfig.Builder serverConfig, - ConnectorConfig.Builder connectorConfigBuilder, - Module... guiceModules) { - return Modules.override( - Modules.combine( - new AbstractModule() { - @Override - protected void configure() { - bind(ServletPathsConfig.class).toInstance(new ServletPathsConfig(new ServletPathsConfig.Builder())); - bind(ServerConfig.class).toInstance(new ServerConfig(serverConfig)); - bind(ConnectorConfig.class).toInstance(new ConnectorConfig(connectorConfigBuilder)); - bind(FilterBindings.class).toInstance(new FilterBindings.Builder().build()); - bind(ConnectionLog.class).toInstance(new VoidConnectionLog()); - bind(RequestLog.class).toInstance(new VoidRequestLog()); - } - }, - new ConnectorFactoryRegistryModule(connectorConfigBuilder), - new ServletModule())) - .with(guiceModules); - } - -} diff --git a/jdisc_http_service/src/test/java/com/yahoo/jdisc/http/server/jetty/servlet/JDiscFilterForServletTest.java b/jdisc_http_service/src/test/java/com/yahoo/jdisc/http/server/jetty/servlet/JDiscFilterForServletTest.java deleted file mode 100644 index 16969a47b84..00000000000 --- a/jdisc_http_service/src/test/java/com/yahoo/jdisc/http/server/jetty/servlet/JDiscFilterForServletTest.java +++ /dev/null @@ -1,166 +0,0 @@ -// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. -package com.yahoo.jdisc.http.server.jetty.servlet; - -import com.google.inject.AbstractModule; -import com.google.inject.Module; -import com.google.inject.util.Modules; -import com.yahoo.jdisc.AbstractResource; -import com.yahoo.jdisc.Request; -import com.yahoo.jdisc.Response; -import com.yahoo.jdisc.handler.ContentChannel; -import com.yahoo.jdisc.handler.ResponseHandler; -import com.yahoo.jdisc.http.HttpRequest; -import com.yahoo.jdisc.http.filter.RequestFilter; -import com.yahoo.jdisc.http.filter.ResponseFilter; -import com.yahoo.jdisc.http.server.jetty.FilterBindings; -import com.yahoo.jdisc.http.server.jetty.FilterInvoker; -import com.yahoo.jdisc.http.server.jetty.SimpleHttpClient.ResponseValidator; -import com.yahoo.jdisc.http.server.jetty.TestDriver; -import com.yahoo.jdisc.http.server.jetty.TestDrivers; -import org.junit.Test; - -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; -import java.io.IOException; -import java.net.URI; -import java.nio.ByteBuffer; -import java.nio.charset.StandardCharsets; - -import static org.hamcrest.CoreMatchers.containsString; -import static org.hamcrest.CoreMatchers.is; - -/** - * @author Tony Vaagenes - * @author bjorncs - */ -public class JDiscFilterForServletTest extends ServletTestBase { - @Test - public void request_filter_can_return_response() throws IOException, InterruptedException { - TestDriver testDriver = requestFilterTestDriver(); - ResponseValidator response = httpGet(testDriver, TestServlet.PATH).execute(); - - response.expectContent(containsString(TestRequestFilter.responseContent)); - } - - @Test - public void request_can_be_forwarded_through_request_filter_to_servlet() throws IOException { - TestDriver testDriver = requestFilterTestDriver(); - ResponseValidator response = httpGet(testDriver, TestServlet.PATH). - addHeader(TestRequestFilter.BYPASS_FILTER_HEADER, Boolean.TRUE.toString()). - execute(); - - response.expectContent(containsString(TestServlet.RESPONSE_CONTENT)); - } - - @Test - public void response_filter_can_modify_response() throws IOException { - TestDriver testDriver = responseFilterTestDriver(); - ResponseValidator response = httpGet(testDriver, TestServlet.PATH).execute(); - - response.expectHeader(TestResponseFilter.INVOKED_HEADER, is(Boolean.TRUE.toString())); - } - - @Test - public void response_filter_is_run_on_empty_sync_response() throws IOException { - TestDriver testDriver = responseFilterTestDriver(); - ResponseValidator response = httpGet(testDriver, NoContentTestServlet.PATH).execute(); - - response.expectHeader(TestResponseFilter.INVOKED_HEADER, is(Boolean.TRUE.toString())); - } - - @Test - public void response_filter_is_run_on_empty_async_response() throws IOException { - TestDriver testDriver = responseFilterTestDriver(); - ResponseValidator response = httpGet(testDriver, NoContentTestServlet.PATH). - addHeader(NoContentTestServlet.HEADER_ASYNC, Boolean.TRUE.toString()). - execute(); - - response.expectHeader(TestResponseFilter.INVOKED_HEADER, is(Boolean.TRUE.toString())); - } - - private TestDriver requestFilterTestDriver() throws IOException { - FilterBindings filterBindings = new FilterBindings.Builder() - .addRequestFilter("my-request-filter", new TestRequestFilter()) - .addRequestFilterBinding("my-request-filter", "http://*/*") - .build(); - return TestDrivers.newInstance(dummyRequestHandler, bindings(filterBindings)); - } - - private TestDriver responseFilterTestDriver() throws IOException { - FilterBindings filterBindings = new FilterBindings.Builder() - .addResponseFilter("my-response-filter", new TestResponseFilter()) - .addResponseFilterBinding("my-response-filter", "http://*/*") - .build(); - return TestDrivers.newInstance(dummyRequestHandler, bindings(filterBindings)); - } - - - - private Module bindings(FilterBindings filterBindings) { - return Modules.combine( - new AbstractModule() { - @Override - protected void configure() { - bind(FilterBindings.class).toInstance(filterBindings); - bind(FilterInvoker.class).toInstance(new FilterInvoker() { - @Override - public HttpServletRequest invokeRequestFilterChain( - RequestFilter requestFilter, - URI uri, - HttpServletRequest httpRequest, - ResponseHandler responseHandler) { - TestRequestFilter filter = (TestRequestFilter) requestFilter; - filter.runAsSecurityFilter(httpRequest, responseHandler); - return httpRequest; - } - - @Override - public void invokeResponseFilterChain( - ResponseFilter responseFilter, - URI uri, - HttpServletRequest request, - HttpServletResponse response) { - - TestResponseFilter filter = (TestResponseFilter) responseFilter; - filter.runAsSecurityFilter(request, response); - } - }); - } - }, - guiceModule()); - } - - static class TestRequestFilter extends AbstractResource implements RequestFilter { - static final String simpleName = TestRequestFilter.class.getSimpleName(); - static final String responseContent = "Rejected by " + simpleName; - static final String BYPASS_FILTER_HEADER = "BYPASS_HEADER" + simpleName; - - @Override - public void filter(HttpRequest request, ResponseHandler handler) { - throw new UnsupportedOperationException(); - } - - public void runAsSecurityFilter(HttpServletRequest request, ResponseHandler responseHandler) { - if (Boolean.parseBoolean(request.getHeader(BYPASS_FILTER_HEADER))) - return; - - ContentChannel contentChannel = responseHandler.handleResponse(new Response(500)); - contentChannel.write(ByteBuffer.wrap(responseContent.getBytes(StandardCharsets.UTF_8)), null); - contentChannel.close(null); - } - } - - - static class TestResponseFilter extends AbstractResource implements ResponseFilter { - static final String INVOKED_HEADER = TestResponseFilter.class.getSimpleName() + "_INVOKED_HEADER"; - - @Override - public void filter(Response response, Request request) { - throw new UnsupportedClassVersionError(); - } - - public void runAsSecurityFilter(HttpServletRequest request, HttpServletResponse response) { - response.addHeader(INVOKED_HEADER, Boolean.TRUE.toString()); - } - } -} diff --git a/jdisc_http_service/src/test/java/com/yahoo/jdisc/http/server/jetty/servlet/ServletAccessLoggingTest.java b/jdisc_http_service/src/test/java/com/yahoo/jdisc/http/server/jetty/servlet/ServletAccessLoggingTest.java deleted file mode 100644 index a533a447f6a..00000000000 --- a/jdisc_http_service/src/test/java/com/yahoo/jdisc/http/server/jetty/servlet/ServletAccessLoggingTest.java +++ /dev/null @@ -1,64 +0,0 @@ -// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. -package com.yahoo.jdisc.http.server.jetty.servlet; - -import com.google.inject.AbstractModule; -import com.google.inject.Module; -import com.google.inject.util.Modules; -import com.yahoo.container.logging.AccessLog; -import com.yahoo.container.logging.RequestLog; -import com.yahoo.container.logging.RequestLogEntry; -import com.yahoo.jdisc.http.server.jetty.TestDriver; -import com.yahoo.jdisc.http.server.jetty.TestDrivers; -import org.junit.Test; -import org.mockito.verification.VerificationMode; - -import java.io.IOException; -import java.util.concurrent.TimeUnit; - -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.timeout; -import static org.mockito.Mockito.verify; - -/** - * @author bakksjo - * @author bjorncs - */ -public class ServletAccessLoggingTest extends ServletTestBase { - private static final long MAX_LOG_WAIT_TIME_MILLIS = TimeUnit.SECONDS.toMillis(60); - - @Test - public void accessLogIsInvokedForNonJDiscServlet() throws Exception { - final AccessLog accessLog = mock(AccessLog.class); - final TestDriver testDriver = newTestDriver(accessLog); - httpGet(testDriver, TestServlet.PATH).execute(); - verifyCallsLog(accessLog, timeout(MAX_LOG_WAIT_TIME_MILLIS).times(1)); - } - - @Test - public void accessLogIsInvokedForJDiscServlet() throws Exception { - final AccessLog accessLog = mock(AccessLog.class); - final TestDriver testDriver = newTestDriver(accessLog); - testDriver.client().newGet("/status.html").execute(); - verifyCallsLog(accessLog, timeout(MAX_LOG_WAIT_TIME_MILLIS).times(1)); - } - - private void verifyCallsLog(RequestLog requestLog, final VerificationMode verificationMode) { - verify(requestLog, verificationMode).log(any(RequestLogEntry.class)); - } - - private TestDriver newTestDriver(RequestLog requestLog) throws IOException { - return TestDrivers.newInstance(dummyRequestHandler, bindings(requestLog)); - } - - private Module bindings(RequestLog requestLog) { - return Modules.combine( - new AbstractModule() { - @Override - protected void configure() { - bind(RequestLog.class).toInstance(requestLog); - } - }, - guiceModule()); - } -} diff --git a/jdisc_http_service/src/test/java/com/yahoo/jdisc/http/server/jetty/servlet/ServletTestBase.java b/jdisc_http_service/src/test/java/com/yahoo/jdisc/http/server/jetty/servlet/ServletTestBase.java deleted file mode 100644 index 54bfe8c026d..00000000000 --- a/jdisc_http_service/src/test/java/com/yahoo/jdisc/http/server/jetty/servlet/ServletTestBase.java +++ /dev/null @@ -1,132 +0,0 @@ -// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. -package com.yahoo.jdisc.http.server.jetty.servlet; - -import com.google.inject.AbstractModule; -import com.google.inject.Module; -import com.google.inject.TypeLiteral; -import com.yahoo.component.ComponentId; -import com.yahoo.component.provider.ComponentRegistry; -import com.yahoo.jdisc.Request; -import com.yahoo.jdisc.handler.AbstractRequestHandler; -import com.yahoo.jdisc.handler.ContentChannel; -import com.yahoo.jdisc.handler.RequestHandler; -import com.yahoo.jdisc.handler.ResponseHandler; -import com.yahoo.jdisc.http.ServletPathsConfig; -import com.yahoo.jdisc.http.ServletPathsConfig.Servlets.Builder; -import com.yahoo.jdisc.http.server.jetty.SimpleHttpClient.RequestExecutor; -import com.yahoo.jdisc.http.server.jetty.TestDriver; -import org.eclipse.jetty.servlet.ServletHolder; - -import javax.servlet.ServletException; -import javax.servlet.annotation.WebServlet; -import javax.servlet.http.HttpServlet; -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; -import java.io.IOException; -import java.io.PrintWriter; -import java.util.List; - -/** - * @author Tony Vaagenes - * @author bakksjo - */ -public class ServletTestBase { - - private static class ServletInstance { - final ComponentId componentId; final String path; final HttpServlet instance; - - ServletInstance(ComponentId componentId, String path, HttpServlet instance) { - this.componentId = componentId; - this.path = path; - this.instance = instance; - } - } - - private final List<ServletInstance> servlets = List.of( - new ServletInstance(TestServlet.ID, TestServlet.PATH, new TestServlet()), - new ServletInstance(NoContentTestServlet.ID, NoContentTestServlet.PATH, new NoContentTestServlet())); - - protected RequestExecutor httpGet(TestDriver testDriver, String path) { - return testDriver.client().newGet("/" + path); - } - - protected ServletPathsConfig createServletPathConfig() { - ServletPathsConfig.Builder configBuilder = new ServletPathsConfig.Builder(); - - servlets.forEach(servlet -> - configBuilder.servlets( - servlet.componentId.stringValue(), - new Builder().path(servlet.path))); - - return new ServletPathsConfig(configBuilder); - } - - protected ComponentRegistry<ServletHolder> servlets() { - ComponentRegistry<ServletHolder> result = new ComponentRegistry<>(); - - servlets.forEach(servlet -> - result.register(servlet.componentId, new ServletHolder(servlet.instance))); - - result.freeze(); - return result; - } - - protected Module guiceModule() { - return new AbstractModule() { - @Override - protected void configure() { - bind(new TypeLiteral<ComponentRegistry<ServletHolder>>(){}).toInstance(servlets()); - bind(ServletPathsConfig.class).toInstance(createServletPathConfig()); - } - }; - } - - protected static class TestServlet extends HttpServlet { - static final String PATH = "servlet/test-servlet"; - static final ComponentId ID = ComponentId.fromString("test-servlet"); - static final String RESPONSE_CONTENT = "Response from " + TestServlet.class.getSimpleName(); - - @Override - protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { - response.setContentType("text/plain"); - PrintWriter writer = response.getWriter(); - writer.write(RESPONSE_CONTENT); - writer.close(); - } - } - - @WebServlet(asyncSupported = true) - protected static class NoContentTestServlet extends HttpServlet { - static final String HEADER_ASYNC = "HEADER_ASYNC"; - - static final String PATH = "servlet/no-content-test-servlet"; - static final ComponentId ID = ComponentId.fromString("no-content-test-servlet"); - - @Override - protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { - if (request.getHeader(HEADER_ASYNC) != null) { - asyncGet(request); - } - } - - private void asyncGet(HttpServletRequest request) { - request.startAsync().start(() -> { - try { - Thread.sleep(100); - } catch (InterruptedException e) { - log("Interrupted", e); - } finally { - request.getAsyncContext().complete(); - } - }); - } - } - - - protected static final RequestHandler dummyRequestHandler = new AbstractRequestHandler() { - @Override - public ContentChannel handleRequest(Request request, ResponseHandler handler) { - throw new UnsupportedOperationException(); - } - }; -} |