diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 26e97dc..dec163b 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -17,7 +17,7 @@ jobs: fail-fast: false matrix: os: [ ubuntu-latest, macos-latest, windows-latest ] - java: [ 11, 17, 21, 23] + java: [ 11, 17, 21, 22, 23] experimental: [ false ] include: - java: 24-ea diff --git a/monitored-httpclient/src/test/java/com/github/nhenneaux/resilienthttpclient/monitoredclientpool/HttpClientPoolTest.java b/monitored-httpclient/src/test/java/com/github/nhenneaux/resilienthttpclient/monitoredclientpool/HttpClientPoolTest.java index dd4dddf..4ea6973 100644 --- a/monitored-httpclient/src/test/java/com/github/nhenneaux/resilienthttpclient/monitoredclientpool/HttpClientPoolTest.java +++ b/monitored-httpclient/src/test/java/com/github/nhenneaux/resilienthttpclient/monitoredclientpool/HttpClientPoolTest.java @@ -44,9 +44,18 @@ class HttpClientPoolTest { private static final Set NOT_ERROR = Set.of(HealthCheckResult.HealthStatus.OK, HealthCheckResult.HealthStatus.WARNING); public static final List PUBLIC_HOST_TO_TEST = List.of( - //"nicolas.henneaux.io", - "openjdk.org", "github.com", "twitter.com", "cloudflare.com", "facebook.com", "amazon.com", "en.wikipedia.org" - // , "travis-ci.com", "google.com" //failing on Java22 + "openjdk.org", + "github.com", + "twitter.com", + "cloudflare.com", + "facebook.com", + "amazon.com", + "en.wikipedia.org" + ); + public static final List PUBLIC_HOST_TO_TEST_WITH_SNI = List.of( + "nicolas.henneaux.io", //failing on Java23 + "travis-ci.com", //failing on Java22 + "google.com" //failing on Java22 ); static { @@ -59,6 +68,10 @@ public static List publicHosts() { return PUBLIC_HOST_TO_TEST; } + public static List publicSpecificHosts() { + return PUBLIC_HOST_TO_TEST_WITH_SNI; + } + @BeforeEach void setUp(TestInfo testInfo) { var testClass = testInfo.getTestClass().orElseThrow(); @@ -73,10 +86,10 @@ void tearDown(TestInfo testInfo) { System.out.println(testClass.getSimpleName() + "::" + testMethod.getName() + " test has finished."); } - public + @ParameterizedTest @MethodSource("publicHosts") - void getNextHttpClient(String hostname) throws MalformedURLException, URISyntaxException { + void getNextHttpClient(String hostname) throws URISyntaxException { final ServerConfiguration serverConfiguration = new ServerConfiguration(hostname); try (HttpClientPool httpClientPool = HttpClientPool.newHttpClientPool(serverConfiguration)) { @@ -89,7 +102,7 @@ void getNextHttpClient(String hostname) throws MalformedURLException, URISyntaxE httpClient = singleIpHttpClient.getHttpClient(); } final int statusCode = httpClient.sendAsync(HttpRequest.newBuilder() - .uri(new URL("https", hostname, -1, serverConfiguration.getHealthPath()).toURI()) + .uri(getUri(hostname, serverConfiguration)) .build(), HttpResponse.BodyHandlers.ofString()) .thenApply(HttpResponse::statusCode) @@ -99,6 +112,42 @@ void getNextHttpClient(String hostname) throws MalformedURLException, URISyntaxE } + @ParameterizedTest + @Timeout(60L) + @MethodSource("publicSpecificHosts") + void specificPublicEndpoints(String hostname) throws URISyntaxException { + if (List.of(22, 23).contains(Runtime.version().feature())) { + // Failing in Java 22-23, regression in JDK https://bugs.openjdk.org/browse/JDK-8346705 + return; + } + + final ServerConfiguration serverConfiguration = new ServerConfiguration(hostname); + try (HttpClientPool httpClientPool = HttpClientPool.newHttpClientPool(serverConfiguration)) { + waitOneMinute(hostname) + .until(httpClientPool::getNextHttpClient, Optional::isPresent); + + final Optional nextHttpClient = httpClientPool.getNextHttpClient(); + try (SingleIpHttpClient singleIpHttpClient = nextHttpClient.orElseThrow()) { + + final HttpClient httpClient = singleIpHttpClient.getHttpClient(); + System.out.println("Calling " + hostname + " " + singleIpHttpClient.getInetAddress() + " healthiness " + singleIpHttpClient.getHealthy()); + + final int statusCode = httpClient.sendAsync(HttpRequest.newBuilder() + .uri(getUri(hostname, serverConfiguration)) + .build(), + HttpResponse.BodyHandlers.discarding()) + .thenApply(HttpResponse::statusCode) + .join(); + assertThat(statusCode, allOf(Matchers.greaterThanOrEqualTo(200), Matchers.lessThanOrEqualTo(499))); + } + } + + } + + private static URI getUri(String hostname, ServerConfiguration serverConfiguration) throws URISyntaxException { + return new URI("https", hostname, serverConfiguration.getHealthPath(),null); + } + @ParameterizedTest @MethodSource("publicHosts") void resilientClient(String hostname) throws MalformedURLException, URISyntaxException { @@ -254,6 +303,7 @@ void checkHttp() { private static ConditionFactory waitOneMinute(String hostname) { return await("waiting " + hostname + " being available") + .pollDelay(1, TimeUnit.SECONDS) .atMost(1, TimeUnit.MINUTES); } diff --git a/pom.xml b/pom.xml index f166972..7799f10 100644 --- a/pom.xml +++ b/pom.xml @@ -54,7 +54,6 @@ - org.apache.maven.plugins maven-enforcer-plugin 3.5.0 @@ -92,7 +91,6 @@ - org.apache.maven.plugins maven-source-plugin @@ -199,14 +197,14 @@ maven-failsafe-plugin - 3.2.5 + 3.5.2 false maven-surefire-plugin - 3.2.5 + 3.5.2 false diff --git a/single-host-httpclient/src/main/java/com/github/nhenneaux/resilienthttpclient/singlehostclient/SingleHostHttpClientBuilder.java b/single-host-httpclient/src/main/java/com/github/nhenneaux/resilienthttpclient/singlehostclient/SingleHostHttpClientBuilder.java index bde7c09..a9fff02 100644 --- a/single-host-httpclient/src/main/java/com/github/nhenneaux/resilienthttpclient/singlehostclient/SingleHostHttpClientBuilder.java +++ b/single-host-httpclient/src/main/java/com/github/nhenneaux/resilienthttpclient/singlehostclient/SingleHostHttpClientBuilder.java @@ -50,7 +50,8 @@ private SingleHostHttpClientBuilder(String hostname, InetAddress hostAddress, Ht * The returned java.net.http.HttpClient is wrapped to force the HTTP header Host with the given hostname. */ public static HttpClient newHttpClient(String hostname, InetAddress hostAddress) { - return builder(hostname, hostAddress, HttpClient.newBuilder().connectTimeout(Duration.ofSeconds(2L))) + return builder(hostname, hostAddress, HttpClient.newBuilder() + .connectTimeout(Duration.ofSeconds(2L))) .withTlsNameMatching() .withSni() .buildWithHostHeader(); diff --git a/single-host-httpclient/src/test/java/com/github/nhenneaux/resilienthttpclient/singlehostclient/SingleHostHttpClientBuilderIT.java b/single-host-httpclient/src/test/java/com/github/nhenneaux/resilienthttpclient/singlehostclient/SingleHostHttpClientBuilderTest.java similarity index 85% rename from single-host-httpclient/src/test/java/com/github/nhenneaux/resilienthttpclient/singlehostclient/SingleHostHttpClientBuilderIT.java rename to single-host-httpclient/src/test/java/com/github/nhenneaux/resilienthttpclient/singlehostclient/SingleHostHttpClientBuilderTest.java index b843b3f..d642ea0 100644 --- a/single-host-httpclient/src/test/java/com/github/nhenneaux/resilienthttpclient/singlehostclient/SingleHostHttpClientBuilderIT.java +++ b/single-host-httpclient/src/test/java/com/github/nhenneaux/resilienthttpclient/singlehostclient/SingleHostHttpClientBuilderTest.java @@ -24,23 +24,25 @@ import java.util.concurrent.ExecutionException; import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.allOf; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.junit.jupiter.api.Assertions.assertSame; import static org.junit.jupiter.api.Assertions.assertThrows; -class SingleHostHttpClientBuilderIT { +class SingleHostHttpClientBuilderTest { public static final List PUBLIC_HOST_TO_TEST = List.of( - "nicolas.henneaux.io", "openjdk.org", "github.com", "twitter.com", "cloudflare.com", "facebook.com", "amazon.com", - "google.com", - "travis-ci.com", "en.wikipedia.org"); + public static final List PUBLIC_HOST_TO_TEST_WITH_SNI = List.of( + "nicolas.henneaux.io", + "google.com", + "travis-ci.com"); static { // Force properties @@ -52,6 +54,10 @@ public static List publicHosts() { return PUBLIC_HOST_TO_TEST; } + public static List publicSpecificHosts() { + return PUBLIC_HOST_TO_TEST_WITH_SNI; + } + @ParameterizedTest @Timeout(61) @MethodSource("publicHosts") @@ -77,6 +83,35 @@ void shouldBuildSingleIpHttpClientAndWorksWithPublicWebsite(String hostname) { assertNotNull(response); } + @ParameterizedTest + @Timeout(61) + @MethodSource("publicSpecificHosts") + void shouldBuildSingleIpHttpClientAndWorksWithSpecificPublicWebsite(String hostname) { + if (List.of(22, 23).contains(Runtime.version().feature())) { + // Failing in Java 22-23, regression in JDK https://bugs.openjdk.org/browse/JDK-8346705 + return; + } + // Given + System.out.println("Validate " + hostname); + final InetAddress ip = new DnsLookupWrapper().getInetAddressesByDnsLookUp(hostname).iterator().next(); + + final HttpClient client = SingleHostHttpClientBuilder.newHttpClient(hostname, ip); + + + HttpRequest request = HttpRequest.newBuilder() + .uri(URI.create("https://" + ip)) + .build(); + + + // When + final int statusCode = client.sendAsync(request, HttpResponse.BodyHandlers.ofString()) + .thenApply(HttpResponse::statusCode) + .join(); + + // Then + assertThat(statusCode, allOf(Matchers.greaterThanOrEqualTo(200), Matchers.lessThanOrEqualTo(499))); + } + @ParameterizedTest @Timeout(61) @MethodSource("publicHosts") @@ -185,6 +220,10 @@ void shouldBuildSingleIpHttpClientAndWorksWithNullTruststore() { @Test @Timeout(61) void shouldBuildSingleIpHttpClientWithMutualTls() throws Exception { + if (List.of(22, 23).contains(Runtime.version().feature())) { + // Failing in Java 22-23, regression in JDK https://bugs.openjdk.org/browse/JDK-8346705 + return; + } // Given final var hostname = "client.badssl.com"; final InetAddress ip = new DnsLookupWrapper().getInetAddressesByDnsLookUp(hostname).iterator().next(); @@ -219,6 +258,10 @@ void shouldBuildSingleIpHttpClientWithMutualTls() throws Exception { @Test @Timeout(61) void shouldBuildSingleIpHttpClientWithMutualTlsCertMissing() throws Exception { + if (List.of(22, 23).contains(Runtime.version().feature())) { + // Failing in Java 22-23, regression in JDK https://bugs.openjdk.org/browse/JDK-8346705 + return; + } // Given final var hostname = "client-cert-missing.badssl.com"; final InetAddress ip = new DnsLookupWrapper().getInetAddressesByDnsLookUp(hostname).iterator().next(); @@ -253,6 +296,10 @@ void shouldBuildSingleIpHttpClientWithMutualTlsCertMissing() throws Exception { @Test @Timeout(61) void shouldTestWithSni() { + if (List.of(22, 23).contains(Runtime.version().feature())) { + // Failing in Java 22-23, regression in JDK https://bugs.openjdk.org/browse/JDK-8346705 + return; + } // Given // Domain is not working when sni is not working correctly final var hostname = "24max.de"; @@ -277,55 +324,15 @@ void shouldTestWithSni() { } - @Test - @Timeout(61) - void shouldValidateWrongHost() { - // Given - String hostname = "wrong.host.badssl.com"; - final InetAddress ip = new DnsLookupWrapper().getInetAddressesByDnsLookUp(hostname).iterator().next(); - - final HttpClient client = SingleHostHttpClientBuilder.newHttpClient(hostname, ip); - - - HttpRequest request = HttpRequest.newBuilder() - .uri(URI.create("https://" + ip)) - .build(); - - final CompletableFuture stringCompletableFuture = client.sendAsync(request, HttpResponse.BodyHandlers.ofString()).thenApply(HttpResponse::body); - - // When - final CompletionException completionException = assertThrows(CompletionException.class, stringCompletableFuture::join); - // Then - assertEquals(SSLHandshakeException.class, completionException.getCause().getClass()); - } - - @Test - @Timeout(61) - void shouldValidateWith1000SAN() { - // Given - String hostname = "1000-sans.badssl.com"; - final InetAddress ip = new DnsLookupWrapper().getInetAddressesByDnsLookUp(hostname).iterator().next(); - - final HttpClient client = SingleHostHttpClientBuilder.builder(hostname, ip, HttpClient.newBuilder().connectTimeout(Duration.ofSeconds(2L))).withTlsNameMatching().buildWithHostHeader(); - - - HttpRequest request = HttpRequest.newBuilder() - .uri(URI.create("https://" + ip)) - .build(); - - final CompletableFuture stringCompletableFuture = client.sendAsync(request, HttpResponse.BodyHandlers.ofString()).thenApply(HttpResponse::body); - - // When - final CompletionException completionException = assertThrows(CompletionException.class, stringCompletableFuture::join); - // Then - assertEquals(SSLHandshakeException.class, completionException.getCause().getClass()); + public static List hostValidateCertificates() { + return List.of("wrong.host.badssl.com", "1000-sans.badssl.com", "no-subject.badssl.com", "no-common-name.badssl.com"); } - @Test + @ParameterizedTest + @MethodSource("hostValidateCertificates") @Timeout(61) - void shouldValidateNoSubject() { + void shouldValidateCertificate(String hostname) { // Given - String hostname = "no-subject.badssl.com"; final InetAddress ip = new DnsLookupWrapper().getInetAddressesByDnsLookUp(hostname).iterator().next(); final HttpClient client = SingleHostHttpClientBuilder.builder(hostname, ip, HttpClient.newBuilder().connectTimeout(Duration.ofSeconds(2L))).withTlsNameMatching().buildWithHostHeader(); @@ -343,27 +350,6 @@ void shouldValidateNoSubject() { assertEquals(SSLHandshakeException.class, completionException.getCause().getClass()); } - @Test - @Timeout(61) - void shouldValidateNoCommonName() { - // Given - String hostname = "no-common-name.badssl.com"; - final InetAddress ip = new DnsLookupWrapper().getInetAddressesByDnsLookUp(hostname).iterator().next(); - - final HttpClient client = SingleHostHttpClientBuilder.builder(hostname, ip, HttpClient.newBuilder().connectTimeout(Duration.ofSeconds(2L))).withTlsNameMatching().buildWithHostHeader(); - - - HttpRequest request = HttpRequest.newBuilder() - .uri(URI.create("https://" + ip)) - .build(); - final CompletableFuture stringCompletableFuture = client.sendAsync(request, HttpResponse.BodyHandlers.ofString()).thenApply(HttpResponse::body); - - // When - final CompletionException completionException = assertThrows(CompletionException.class, stringCompletableFuture::join); - // Then - assertEquals(SSLHandshakeException.class, completionException.getCause().getClass()); - } - @Test @Timeout(61) void unknownHost() { diff --git a/single-host-httpclient/src/test/resources/truststore.p12 b/single-host-httpclient/src/test/resources/truststore.p12 index 223f5f6..792daea 100644 Binary files a/single-host-httpclient/src/test/resources/truststore.p12 and b/single-host-httpclient/src/test/resources/truststore.p12 differ