diff options
author | Bjørn Christian Seime <bjorn.christian@seime.no> | 2021-04-12 09:47:31 +0200 |
---|---|---|
committer | GitHub <noreply@github.com> | 2021-04-12 09:47:31 +0200 |
commit | dcf5a1b725facbaff14ba7659254ea4b72895dbb (patch) | |
tree | 66c99c65b619df66f8a34305ac23069fdbdd48e1 /container-core/src/test/java/com/yahoo | |
parent | 015ade7cd232f217dd964da037ab202731b37cef (diff) |
Revert "Revert "Bjorncs/jdisc http2 preps [run-systemtest]""
Diffstat (limited to 'container-core/src/test/java/com/yahoo')
10 files changed, 393 insertions, 179 deletions
diff --git a/container-core/src/test/java/com/yahoo/jdisc/http/filter/ServletFilterRequestTest.java b/container-core/src/test/java/com/yahoo/jdisc/http/filter/ServletFilterRequestTest.java index 3052902f174..ed4c9b66068 100644 --- a/container-core/src/test/java/com/yahoo/jdisc/http/filter/ServletFilterRequestTest.java +++ b/container-core/src/test/java/com/yahoo/jdisc/http/filter/ServletFilterRequestTest.java @@ -3,12 +3,11 @@ package com.yahoo.jdisc.http.filter; import com.yahoo.jdisc.http.Cookie; import com.yahoo.jdisc.http.HttpHeaders; +import com.yahoo.jdisc.http.server.jetty.JettyMockRequestBuilder; import com.yahoo.jdisc.http.servlet.ServletRequest; -import org.eclipse.jetty.server.HttpConnection; +import org.eclipse.jetty.server.Request; import org.junit.Before; import org.junit.Test; -import org.mockito.Mockito; -import org.springframework.mock.web.MockHttpServletRequest; import java.net.URI; import java.util.Arrays; @@ -18,7 +17,6 @@ import java.util.List; import static com.yahoo.jdisc.http.HttpRequest.Version; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertTrue; -import static org.mockito.Mockito.when; /** * Test the parts of the DiscFilterRequest API that are implemented @@ -26,7 +24,6 @@ import static org.mockito.Mockito.when; * {@link com.yahoo.jdisc.http.servlet.ServletRequest}. * * @author gjoranv - * @since 5.27 */ public class ServletFilterRequestTest { @@ -54,18 +51,14 @@ public class ServletFilterRequestTest { parentRequest = ((ServletFilterRequest)filterRequest).getServletRequest(); } - private ServletRequest newServletRequest() throws Exception { - MockHttpServletRequest parent = new MockHttpServletRequest("GET", uri.toString()); - parent.setProtocol(Version.HTTP_1_1.toString()); - parent.setRemoteHost(host); - parent.setRemotePort(port); - parent.setParameter(paramName, paramValue); - parent.setParameter(listParamName, listParamValue); - parent.addHeader(headerName, headerValue); - parent.setAttribute(attributeName, attributeValue); - HttpConnection connection = Mockito.mock(HttpConnection.class); - when(connection.getCreatedTimeStamp()).thenReturn(System.currentTimeMillis()); - parent.setAttribute("org.eclipse.jetty.server.HttpConnection", connection); + private ServletRequest newServletRequest() { + Request parent = JettyMockRequestBuilder.newBuilder() + .remote("1.2.3.4", host, port) + .header(headerName, List.of(headerValue)) + .parameter(paramName, List.of(paramValue)) + .parameter(listParamName, List.of(listParamValue)) + .attribute(attributeName, attributeValue) + .build(); return new ServletRequest(parent, uri); } diff --git a/container-core/src/test/java/com/yahoo/jdisc/http/server/jetty/AccessLogRequestLogTest.java b/container-core/src/test/java/com/yahoo/jdisc/http/server/jetty/AccessLogRequestLogTest.java index e472f954afc..c45d17a4ff8 100644 --- a/container-core/src/test/java/com/yahoo/jdisc/http/server/jetty/AccessLogRequestLogTest.java +++ b/container-core/src/test/java/com/yahoo/jdisc/http/server/jetty/AccessLogRequestLogTest.java @@ -4,12 +4,7 @@ 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.HttpInput; import org.eclipse.jetty.server.Request; import org.eclipse.jetty.server.Response; import org.junit.Test; @@ -23,8 +18,6 @@ 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 @@ -33,9 +26,9 @@ import static org.mockito.Mockito.when; public class AccessLogRequestLogTest { @Test public void requireThatQueryWithUnquotedSpecialCharactersIsHandled() { - final Request jettyRequest = createRequestMock(); - when(jettyRequest.getRequestURI()).thenReturn("/search/"); - when(jettyRequest.getQueryString()).thenReturn("query=year:>2010"); + Request jettyRequest = createRequestBuilder() + .uri("http", "localhost", 12345, "/search/", "query=year:>2010") + .build(); InMemoryRequestLog requestLog = new InMemoryRequestLog(); doAccessLoggingOfRequest(requestLog, jettyRequest); @@ -47,11 +40,11 @@ public class AccessLogRequestLogTest { @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); + String path = "/search/"; + String query = "query=year%252010+%3B&customParameter=something"; + Request jettyRequest = createRequestBuilder() + .uri("http", "localhost", 12345, path, query) + .build(); InMemoryRequestLog requestLog = new InMemoryRequestLog(); doAccessLoggingOfRequest(requestLog, jettyRequest); @@ -64,11 +57,11 @@ public class AccessLogRequestLogTest { @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); + Request jettyRequest = createRequestBuilder() + .uri("http", "localhost", 12345, rawPath, rawQuery) + .build(); InMemoryRequestLog requestLog = new InMemoryRequestLog(); doAccessLoggingOfRequest(requestLog, jettyRequest); @@ -81,11 +74,11 @@ public class AccessLogRequestLogTest { @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"); + Request jettyRequest = createRequestBuilder() + .uri("http", "localhost", 12345, "//search/", "q=%%2") + .header("x-forwarded-for", List.of("1.2.3.4")) + .header("y-ra", List.of("2.3.4.5")) + .build(); InMemoryRequestLog requestLog = new InMemoryRequestLog(); doAccessLoggingOfRequest(requestLog, jettyRequest); @@ -95,11 +88,11 @@ public class AccessLogRequestLogTest { @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"); + Request jettyRequest = createRequestBuilder() + .uri("http", "localhost", 12345, "//search/", "q=%%2") + .header("X-Forwarded-Port", List.of("80")) + .header("y-rp", List.of("8080")) + .build(); InMemoryRequestLog requestLog = new InMemoryRequestLog(); doAccessLoggingOfRequest(requestLog, jettyRequest); @@ -109,10 +102,12 @@ public class AccessLogRequestLogTest { @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); + Request jettyRequest = createRequestBuilder() + .uri("http", "localhost", 12345, "/search/", null) + .header("X-Forwarded-Port", List.of("8o8o")) + .header("y-rp", List.of("8o8o")) + .remote("2.3.4.5", "localhost", 80) + .build(); InMemoryRequestLog requestLog = new InMemoryRequestLog(); doAccessLoggingOfRequest(requestLog, jettyRequest); @@ -129,32 +124,14 @@ public class AccessLogRequestLogTest { 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); - HttpInput httpInput = mock(HttpInput.class); - when(httpInput.getContentReceived()).thenReturn(2345L); - when(request.getHttpInput()).thenReturn(httpInput); - return request; + private static JettyMockRequestBuilder createRequestBuilder() { + return JettyMockRequestBuilder.newBuilder() + .attribute(JDiscHttpServlet.ATTRIBUTE_NAME_ACCESS_LOG_ENTRY, new AccessLogEntry()) + .remote("2.3.4.5", "localhost", 12345) + .localPort(1234); } 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; + return JettyMockResponseBuilder.newBuilder().build(); } } diff --git a/container-core/src/test/java/com/yahoo/jdisc/http/server/jetty/FilterTestCase.java b/container-core/src/test/java/com/yahoo/jdisc/http/server/jetty/FilterTestCase.java index a67656dd5ca..e117ef7f723 100644 --- a/container-core/src/test/java/com/yahoo/jdisc/http/server/jetty/FilterTestCase.java +++ b/container-core/src/test/java/com/yahoo/jdisc/http/server/jetty/FilterTestCase.java @@ -99,7 +99,7 @@ public class FilterTestCase { final MyRequestHandler requestHandler = new MyRequestHandler(); final TestDriver testDriver = newDriver(requestHandler, filterBindings); - testDriver.client().get("status.html"); + testDriver.client().get("/status.html"); assertThat(requestHandler.awaitInvocation(), is(true)); assertThat(requestHandler.getHeaderMap().get("foo").get(0), is("bar")); diff --git a/container-core/src/test/java/com/yahoo/jdisc/http/server/jetty/HttpRequestFactoryTest.java b/container-core/src/test/java/com/yahoo/jdisc/http/server/jetty/HttpRequestFactoryTest.java index 9c1348004ee..fbbf3074839 100644 --- a/container-core/src/test/java/com/yahoo/jdisc/http/server/jetty/HttpRequestFactoryTest.java +++ b/container-core/src/test/java/com/yahoo/jdisc/http/server/jetty/HttpRequestFactoryTest.java @@ -7,10 +7,8 @@ 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; @@ -22,8 +20,6 @@ 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 @@ -141,27 +137,15 @@ public class HttpRequestFactoryTest { 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 HttpServletRequest createMockRequest(String scheme, String host, String path, String query) { + return JettyMockRequestBuilder.newBuilder() + .uri(scheme, host, LOCAL_PORT, path, query) + .remote("127.0.0.1", "localhost", 1234) + .localPort(LOCAL_PORT) + .build(); } + private static final class MockContainer implements CurrentContainer { @Override diff --git a/container-core/src/test/java/com/yahoo/jdisc/http/server/jetty/HttpServerTest.java b/container-core/src/test/java/com/yahoo/jdisc/http/server/jetty/HttpServerTest.java index d8e94d13813..f5d77b53f12 100644 --- a/container-core/src/test/java/com/yahoo/jdisc/http/server/jetty/HttpServerTest.java +++ b/container-core/src/test/java/com/yahoo/jdisc/http/server/jetty/HttpServerTest.java @@ -35,10 +35,19 @@ 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.apache.hc.client5.http.async.methods.SimpleHttpRequests; +import org.apache.hc.client5.http.async.methods.SimpleHttpResponse; +import org.apache.hc.client5.http.entity.mime.FormBodyPart; +import org.apache.hc.client5.http.entity.mime.FormBodyPartBuilder; +import org.apache.hc.client5.http.entity.mime.StringBody; +import org.apache.hc.client5.http.impl.async.CloseableHttpAsyncClient; +import org.apache.hc.client5.http.impl.async.HttpAsyncClientBuilder; +import org.apache.hc.client5.http.impl.nio.PoolingAsyncClientConnectionManagerBuilder; +import org.apache.hc.client5.http.ssl.ClientTlsStrategyBuilder; +import org.apache.hc.client5.http.ssl.NoopHostnameVerifier; +import org.apache.hc.core5.http.ContentType; +import org.apache.hc.core5.http.nio.ssl.TlsStrategy; +import org.apache.hc.core5.http2.HttpVersionPolicy; import org.assertj.core.api.Assertions; import org.eclipse.jetty.client.HttpClient; import org.eclipse.jetty.client.ProxyProtocolClientConnectionFactory.V1; @@ -107,6 +116,7 @@ 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.assertNull; import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; import static org.junit.Assume.assumeTrue; @@ -400,8 +410,8 @@ public class HttpServerTest { final ResponseValidator response = driver.client().newPost("/status.html") .setMultipartContent( - newFileBody("", "start.txt", startTxtContent), - newFileBody("", "updater.conf", updaterConfContent)) + newFileBody("start.txt", startTxtContent), + newFileBody("updater.conf", updaterConfContent)) .execute(); response.expectStatusCode(is(OK)) .expectContent(containsString(startTxtContent)) @@ -505,11 +515,27 @@ public class HttpServerTest { } @Test - public void requireThatTlsClientAuthenticationEnforcerRejectsRequestsForNonWhitelistedPaths() throws IOException { + public void requireThatServerCanRespondToHttp2Request() throws Exception { Path privateKeyFile = tmpFolder.newFile().toPath(); Path certificateFile = tmpFolder.newFile().toPath(); generatePrivateKeyAndCertificate(privateKeyFile, certificateFile); + TestDriver driver = TestDrivers.newInstanceWithSsl(new EchoRequestHandler(), certificateFile, privateKeyFile, TlsClientAuth.WANT); + try (CloseableHttpAsyncClient client = createHttp2Client(certificateFile, privateKeyFile)) { + String uri = "https://localhost:" + driver.server().getListenPort() + "/status.html"; + SimpleHttpResponse response = client.execute(SimpleHttpRequests.get(uri), null).get(); + assertNull(response.getBodyText()); + assertEquals(OK, response.getCode()); + } + assertTrue(driver.close()); + } + + @Test + public void requireThatTlsClientAuthenticationEnforcerRejectsRequestsForNonWhitelistedPaths() throws IOException { + Path privateKeyFile = tmpFolder.newFile().toPath(); + Path certificateFile = tmpFolder.newFile().toPath(); + generatePrivateKeyAndCertificate(privateKeyFile, certificateFile); + TestDriver driver = createSslWithTlsClientAuthenticationEnforcer(certificateFile, privateKeyFile); SSLContext trustStoreOnlyCtx = new SslContextBuilder() .withTrustStore(certificateFile) @@ -918,6 +944,21 @@ public class HttpServerTest { return client; } + private static CloseableHttpAsyncClient createHttp2Client(Path certificateFile, Path privateKeyFile) { + TestDriver driver = TestDrivers.newInstanceWithSsl(new EchoRequestHandler(), certificateFile, privateKeyFile, TlsClientAuth.WANT); + TlsStrategy tlsStrategy = ClientTlsStrategyBuilder.create() + .setSslContext(driver.newSslContext()) + .build(); + var client = HttpAsyncClientBuilder.create() + .setVersionPolicy(HttpVersionPolicy.FORCE_HTTP_2) + .disableConnectionState() + .disableAutomaticRetries() + .setConnectionManager(PoolingAsyncClientConnectionManagerBuilder.create().setTlsStrategy(tlsStrategy).build()) + .build(); + client.start(); + return client; + } + private static void assertLogEntryHasRemote(RequestLogEntry entry, String expectedAddress, int expectedPort) { assertEquals(expectedAddress, entry.peerAddress().get()); if (expectedPort > 0) { @@ -969,6 +1010,25 @@ public class HttpServerTest { }); } + private static TestDriver createSslWithTlsClientAuthenticationEnforcer(Path certificateFile, Path privateKeyFile) { + ConnectorConfig.Builder connectorConfig = new ConnectorConfig.Builder() + .tlsClientAuthEnforcer( + new ConnectorConfig.TlsClientAuthEnforcer.Builder() + .enable(true) + .pathWhitelist("/status.html")) + .ssl(new ConnectorConfig.Ssl.Builder() + .enabled(true) + .clientAuth(ConnectorConfig.Ssl.ClientAuth.Enum.WANT_AUTH) + .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 -> {}); + } + private static TestDriver createSslTestDriver( Path serverCertificateFile, Path serverPrivateKeyFile, MetricConsumerMock metricConsumer, InMemoryConnectionLog connectionLog) throws IOException { Module extraModule = binder -> { @@ -1049,30 +1109,16 @@ public class HttpServerTest { 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 FormBodyPart newFileBody(final String fileName, final String fileContent) { + return FormBodyPartBuilder.create() + .setBody( + new StringBody(fileContent, ContentType.TEXT_PLAIN) { + @Override public String getFilename() { return fileName; } + @Override public String getMimeType() { return ""; } + @Override public String getCharset() { return null; } + }) + .setName(fileName) + .build(); } private static class ConnectedAtRequestHandler extends AbstractRequestHandler { diff --git a/container-core/src/test/java/com/yahoo/jdisc/http/server/jetty/JDiscHttpServletTest.java b/container-core/src/test/java/com/yahoo/jdisc/http/server/jetty/JDiscHttpServletTest.java index 230f59cbb34..23c229e2ec5 100644 --- a/container-core/src/test/java/com/yahoo/jdisc/http/server/jetty/JDiscHttpServletTest.java +++ b/container-core/src/test/java/com/yahoo/jdisc/http/server/jetty/JDiscHttpServletTest.java @@ -7,15 +7,15 @@ 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.apache.hc.client5.http.classic.methods.HttpDelete; +import org.apache.hc.client5.http.classic.methods.HttpGet; +import org.apache.hc.client5.http.classic.methods.HttpHead; +import org.apache.hc.client5.http.classic.methods.HttpOptions; +import org.apache.hc.client5.http.classic.methods.HttpPatch; +import org.apache.hc.client5.http.classic.methods.HttpPost; +import org.apache.hc.client5.http.classic.methods.HttpPut; +import org.apache.hc.client5.http.classic.methods.HttpTrace; +import org.apache.hc.client5.http.classic.methods.HttpUriRequestBase; import org.junit.Test; import java.io.IOException; @@ -73,8 +73,7 @@ public class JDiscHttpServletTest { }; } - private static class UnknownMethodHttpRequest extends HttpRequestBase { - UnknownMethodHttpRequest(URI uri) { setURI(uri); } - @Override public String getMethod() { return "UNKNOWN_METHOD"; } + private static class UnknownMethodHttpRequest extends HttpUriRequestBase { + UnknownMethodHttpRequest(URI uri) { super("UNKNOWN_METHOD", uri); } } } diff --git a/container-core/src/test/java/com/yahoo/jdisc/http/server/jetty/JettyMockRequestBuilder.java b/container-core/src/test/java/com/yahoo/jdisc/http/server/jetty/JettyMockRequestBuilder.java new file mode 100644 index 00000000000..4bf6afeb3f1 --- /dev/null +++ b/container-core/src/test/java/com/yahoo/jdisc/http/server/jetty/JettyMockRequestBuilder.java @@ -0,0 +1,176 @@ +// 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.http.ConnectorConfig; +import org.eclipse.jetty.server.HttpChannel; +import org.eclipse.jetty.server.HttpConnection; +import org.eclipse.jetty.server.HttpInput; +import org.eclipse.jetty.server.Request; +import org.mockito.stubbing.Answer; + +import java.io.UnsupportedEncodingException; +import java.util.Collections; +import java.util.Enumeration; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.atomic.AtomicReference; + +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.Mockito.doAnswer; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +/** + * Builder for creating a mock instance of Jetty's {@link Request} type. + * + * @author bjorncs + */ +public class JettyMockRequestBuilder { + + private final Map<String, List<String>> parameters = new HashMap<>(); + private final Map<String, List<String>> headers = new HashMap<>(); + private final Map<String, Object> attributes = new HashMap<>(); + private Integer localPort; + private String uriScheme; + private String uriServerName; + private Integer uriPort; + private String uriPath; + private String uriQuery; + private String remoteAddress; + private String remoteHost; + private Integer remotePort; + + private JettyMockRequestBuilder() {} + + public static JettyMockRequestBuilder newBuilder() { return new JettyMockRequestBuilder(); } + + public JettyMockRequestBuilder localPort(int localPort) { this.localPort = localPort; return this; } + + public JettyMockRequestBuilder remote(String address, String host, int port) { + this.remoteAddress = address; + this.remoteHost = host; + this.remotePort = port; + return this; + } + + public JettyMockRequestBuilder uri(String scheme, String serverName, int port, String path, String query) { + this.uriScheme = scheme; + this.uriServerName = serverName; + this.uriPort = port; + this.uriPath = path; + this.uriQuery = query; + return this; + } + + public JettyMockRequestBuilder parameter(String name, List<String> values) { this.parameters.put(name, List.copyOf(values)); return this; } + + public JettyMockRequestBuilder header(String name, List<String> values) { this.headers.put(name, List.copyOf(values)); return this; } + + public JettyMockRequestBuilder attribute(String name, Object value) { this.attributes.put(name, value); return this; } + + public Request build() { + int localPort = this.localPort != null ? this.localPort : 8080; + String scheme = this.uriScheme != null ? this.uriScheme : "http"; + String serverName = this.uriServerName != null ? this.uriServerName : "localhost"; + int uriPort = this.uriPort != null ? this.uriPort : 8080; + String path = this.uriPath; + String query = this.uriQuery; + String remoteAddress = this.remoteAddress != null ? this.remoteAddress : "1.2.3.4"; + String remoteHost = this.remoteHost != null ? this.remoteHost : "remotehost"; + Integer remotePort = this.remotePort != null ? this.remotePort : 12345; + + HttpChannel channel = mock(HttpChannel.class); + HttpConnection connection = mock(HttpConnection.class); + JDiscServerConnector connector = mock(JDiscServerConnector.class); + when(connector.connectorConfig()).thenReturn(new ConnectorConfig(new ConnectorConfig.Builder().listenPort(localPort))); + when(connector.getLocalPort()).thenReturn(localPort); + when(connection.getCreatedTimeStamp()).thenReturn(System.currentTimeMillis()); + when(connection.getConnector()).thenReturn(connector); + when(connection.getHttpChannel()).thenReturn(channel); + when(channel.getConnector()).thenReturn(connector); + when(channel.getConnection()).thenReturn(connection); + + HttpInput httpInput = mock(HttpInput.class); + when(httpInput.getContentReceived()).thenReturn(2345L); + + Request request = mock(Request.class); + when(request.getHttpChannel()).thenReturn(channel); + when(request.getHttpInput()).thenReturn(httpInput); + when(request.getProtocol()).thenReturn("HTTP/1.1"); + when(request.getScheme()).thenReturn(scheme); + when(request.getServerName()).thenReturn(serverName); + when(request.getRemoteAddr()).thenReturn(remoteAddress); + when(request.getRemotePort()).thenReturn(remotePort); + when(request.getRemoteHost()).thenReturn(remoteHost); + when(request.getLocalPort()).thenReturn(uriPort); + when(request.getMethod()).thenReturn("GET"); + when(request.getQueryString()).thenReturn(query); + when(request.getRequestURI()).thenReturn(path); + + mockCharacterEncodingHandling(request); + mockHeaderHandling(request); + mockParameterHandling(request); + mockAttributeHandling(request); + + return request; + } + + private void mockCharacterEncodingHandling(Request request) { + try { + AtomicReference<String> characterEncoding = new AtomicReference<>(""); + when(request.getCharacterEncoding()).thenAnswer((Answer<String>) ignored -> characterEncoding.get()); + doAnswer((Answer<Void>) invocation -> { + String value = invocation.getArgument(0); + characterEncoding.set(value); + return null; + }).when(request).setCharacterEncoding(anyString()); + } catch (UnsupportedEncodingException e) { + throw new RuntimeException(e); + } + } + + private void mockHeaderHandling(Request request) { + Map<String, List<String>> headers = new ConcurrentHashMap<>(this.headers); + when(request.getHeaderNames()).thenReturn(Collections.enumeration(headers.keySet())); + when(request.getHeaders(anyString())).thenAnswer((Answer<Enumeration<String>>) invocation -> { + String key = invocation.getArgument(0); + List<String> values = headers.get(key); + return values != null ? Collections.enumeration(values) : Collections.enumeration(List.of()); + }); + when(request.getHeader(anyString())).thenAnswer((Answer<String>) invocation -> { + String name = invocation.getArgument(0); + List<String> values = headers.get(name); + if (values == null || values.isEmpty()) return null; + return values.get(0); + }); + } + + private void mockParameterHandling(Request request) { + Map<String, String[]> parameters = new ConcurrentHashMap<>(); + this.parameters.forEach((key, values) -> parameters.put(key, values.toArray(String[]::new))); + when(request.getParameterMap()).thenReturn(parameters); + } + + private void mockAttributeHandling(Request request) { + Map<String, Object> attributes = new ConcurrentHashMap<>(this.attributes); + + when(request.getAttribute(any())).thenAnswer(invocation -> { + String attributeName = invocation.getArgument(0); + return attributes.get(attributeName); + }); + doAnswer((Answer<Void>) invocation -> { + String attributeName = invocation.getArgument(0); + Object attributeValue = invocation.getArgument(1); + attributes.put(attributeName, attributeValue); + return null; + }).when(request).setAttribute(anyString(), any()); + doAnswer((Answer<Void>) invocation -> { + String attributeName = invocation.getArgument(0); + attributes.remove(attributeName); + return null; + }).when(request).removeAttribute(anyString()); + } +} diff --git a/container-core/src/test/java/com/yahoo/jdisc/http/server/jetty/JettyMockResponseBuilder.java b/container-core/src/test/java/com/yahoo/jdisc/http/server/jetty/JettyMockResponseBuilder.java new file mode 100644 index 00000000000..6addb966208 --- /dev/null +++ b/container-core/src/test/java/com/yahoo/jdisc/http/server/jetty/JettyMockResponseBuilder.java @@ -0,0 +1,29 @@ +// 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 org.eclipse.jetty.http.MetaData; +import org.eclipse.jetty.server.HttpChannel; +import org.eclipse.jetty.server.Response; + +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +/** + * Builder for creating a mock instance of Jetty's {@link Response} type. + * + * @author bjorncs + */ +public class JettyMockResponseBuilder { + + private JettyMockResponseBuilder() {} + + public static JettyMockResponseBuilder newBuilder() { return new JettyMockResponseBuilder(); } + + public Response build() { + 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/container-core/src/test/java/com/yahoo/jdisc/http/server/jetty/SimpleHttpClient.java b/container-core/src/test/java/com/yahoo/jdisc/http/server/jetty/SimpleHttpClient.java index eea8d7e3072..161f48d847d 100644 --- a/container-core/src/test/java/com/yahoo/jdisc/http/server/jetty/SimpleHttpClient.java +++ b/container-core/src/test/java/com/yahoo/jdisc/http/server/jetty/SimpleHttpClient.java @@ -1,33 +1,36 @@ // 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.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.apache.hc.client5.http.SystemDefaultDnsResolver; +import org.apache.hc.client5.http.classic.methods.HttpGet; +import org.apache.hc.client5.http.classic.methods.HttpPost; +import org.apache.hc.client5.http.classic.methods.HttpUriRequest; +import org.apache.hc.client5.http.entity.GzipCompressingEntity; +import org.apache.hc.client5.http.entity.mime.FormBodyPart; +import org.apache.hc.client5.http.entity.mime.MultipartEntityBuilder; +import org.apache.hc.client5.http.impl.classic.CloseableHttpClient; +import org.apache.hc.client5.http.impl.classic.CloseableHttpResponse; +import org.apache.hc.client5.http.impl.classic.HttpClientBuilder; +import org.apache.hc.client5.http.impl.io.PoolingHttpClientConnectionManager; +import org.apache.hc.client5.http.impl.io.PoolingHttpClientConnectionManagerBuilder; +import org.apache.hc.client5.http.ssl.DefaultHostnameVerifier; +import org.apache.hc.client5.http.ssl.SSLConnectionSocketFactory; +import org.apache.hc.core5.http.ContentType; +import org.apache.hc.core5.http.Header; +import org.apache.hc.core5.http.HttpEntity; +import org.apache.hc.core5.http.HttpResponse; +import org.apache.hc.core5.http.ParseException; +import org.apache.hc.core5.http.io.entity.ByteArrayEntity; +import org.apache.hc.core5.http.io.entity.EntityUtils; +import org.apache.hc.core5.http.io.entity.StringEntity; import org.hamcrest.Matcher; import org.hamcrest.MatcherAssert; import javax.net.ssl.SSLContext; import java.io.IOException; +import java.net.InetAddress; import java.net.URI; +import java.net.UnknownHostException; import java.nio.charset.StandardCharsets; import java.util.Arrays; import java.util.List; @@ -55,8 +58,9 @@ public class SimpleHttpClient implements AutoCloseable { public SimpleHttpClient(SSLContext sslContext, List<String> enabledProtocols, List<String> enabledCiphers, int listenPort, boolean useCompression) { - HttpClientBuilder builder = HttpClientBuilder.create(); - builder.disableConnectionState(); // Reuse SSL connection when client authentication is enabled + HttpClientBuilder builder = HttpClientBuilder.create() + .disableAutomaticRetries() + .disableConnectionState(); // Reuse SSL connection when client authentication is enabled if (!useCompression) { builder.disableContentCompression(); } @@ -66,12 +70,17 @@ public class SimpleHttpClient implements AutoCloseable { toArray(enabledProtocols), toArray(enabledCiphers), new DefaultHostnameVerifier()); - builder.setSSLSocketFactory(sslConnectionFactory); - - Registry<ConnectionSocketFactory> registry = RegistryBuilder.<ConnectionSocketFactory>create() - .register("https", sslConnectionFactory) + PoolingHttpClientConnectionManager connManager = PoolingHttpClientConnectionManagerBuilder.create() + .setSSLSocketFactory(sslConnectionFactory) + .setDnsResolver(new SystemDefaultDnsResolver() { + @Override + public InetAddress[] resolve(String host) throws UnknownHostException { + // Returns single address instead of multiple (to avoid multiple connection attempts) + return new InetAddress[] { InetAddress.getByName(host) }; + } + }) .build(); - builder.setConnectionManager(new BasicHttpClientConnectionManager(registry)); + builder.setConnectionManager(connManager); scheme = "https"; } else { scheme = "http"; @@ -139,7 +148,7 @@ public class SimpleHttpClient implements AutoCloseable { } public RequestExecutor setBinaryContent(final byte[] content) { - this.entity = new ByteArrayEntity(content); + this.entity = new ByteArrayEntity(content, ContentType.DEFAULT_BINARY); return this; } @@ -152,7 +161,7 @@ public class SimpleHttpClient implements AutoCloseable { public ResponseValidator execute() throws IOException { if (entity != null) { - ((HttpPost)request).setEntity(entity); + request.setEntity(entity); } try (CloseableHttpResponse response = delegate.execute(request)){ return new ResponseValidator(response); @@ -165,15 +174,19 @@ public class SimpleHttpClient implements AutoCloseable { private final HttpResponse response; private final String content; - public ResponseValidator(HttpResponse response) throws IOException { - this.response = response; + public ResponseValidator(CloseableHttpResponse response) throws IOException { + try { + this.response = response; - HttpEntity entity = response.getEntity(); - this.content = entity == null ? null : EntityUtils.toString(entity, StandardCharsets.UTF_8); + HttpEntity entity = response.getEntity(); + this.content = entity == null ? null : EntityUtils.toString(entity, StandardCharsets.UTF_8); + } catch (ParseException e) { + throw new IOException(e); + } } public ResponseValidator expectStatusCode(Matcher<Integer> matcher) { - MatcherAssert.assertThat(response.getStatusLine().getStatusCode(), matcher); + MatcherAssert.assertThat(response.getCode(), matcher); return this; } diff --git a/container-core/src/test/java/com/yahoo/jdisc/http/server/jetty/TestDrivers.java b/container-core/src/test/java/com/yahoo/jdisc/http/server/jetty/TestDrivers.java index 7d7530c32e0..75fc0948da9 100644 --- a/container-core/src/test/java/com/yahoo/jdisc/http/server/jetty/TestDrivers.java +++ b/container-core/src/test/java/com/yahoo/jdisc/http/server/jetty/TestDrivers.java @@ -55,10 +55,7 @@ public class TestDrivers { newConfigModule( new ServerConfig.Builder().connectionLog(new ServerConfig.ConnectionLog.Builder().enabled(true)), new ConnectorConfig.Builder() - .tlsClientAuthEnforcer( - new ConnectorConfig.TlsClientAuthEnforcer.Builder() - .enable(true) - .pathWhitelist("/status.html")) + .http2Enabled(true) .ssl(new ConnectorConfig.Ssl.Builder() .enabled(true) .clientAuth(tlsClientAuth == TlsClientAuth.NEED |