8381316: HttpClient / Http3: poor exception messages on SSL handshake errors

Reviewed-by: dfuchs
This commit is contained in:
Daniel Jeliński 2026-04-07 11:48:01 +00:00
parent 69c4a211ee
commit 547ebe7236
3 changed files with 42 additions and 29 deletions

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 2021, 2025, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2021, 2026, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
@ -660,7 +660,7 @@ public final class QuicTLSEngineImpl implements QuicTLSEngine, SSLTransport {
}
Alert alert = ((QuicEngineOutputRecord)
conContext.outputRecord).getAlert();
throw new QuicTransportException(alert.description, keySpace, 0,
throw new QuicTransportException(e.getMessage(), keySpace, 0,
BASE_CRYPTO_ERROR + alert.id, e);
} catch (IOException e) {
throw new RuntimeException(e);

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 2024, 2025, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2024, 2026, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
@ -128,6 +128,9 @@ public abstract sealed class TerminationCause {
? new IOException("connection terminated")
: new IOException(fallbackExceptionMsg);
} else if (original instanceof QuicTransportException qte) {
if (qte.getCause() instanceof IOException ioe) {
return ioe;
}
return new IOException(qte.getMessage());
} else if (original instanceof IOException ioe) {
return ioe;

View File

@ -23,10 +23,12 @@
/*
* @test
* @bug 8381316
* @summary Test to ensure the HTTP client throws an appropriate SSL exception
* when SSL context is not valid.
* @library /test/lib
* @library /test/lib /test/jdk/java/net/httpclient/lib
* @build jdk.test.lib.net.SimpleSSLContext
* jdk.httpclient.test.lib.common.HttpServerAdapters
* @run junit/othervm -Djdk.internal.httpclient.debug=true InvalidSSLContextTest
*/
@ -44,14 +46,18 @@ import javax.net.ssl.SSLServerSocket;
import javax.net.ssl.SSLSocket;
import java.net.http.HttpClient;
import java.net.http.HttpClient.Version;
import java.net.http.HttpOption;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.net.http.HttpResponse.BodyHandlers;
import jdk.httpclient.test.lib.common.HttpServerAdapters.HttpTestServer;
import jdk.test.lib.net.SimpleSSLContext;
import static java.net.http.HttpClient.Builder.NO_PROXY;
import static java.net.http.HttpClient.Version.HTTP_1_1;
import static java.net.http.HttpClient.Version.HTTP_2;
import static java.net.http.HttpClient.Version.*;
import static java.net.http.HttpOption.Http3DiscoveryMode.HTTP_3_URI_ONLY;
import static jdk.httpclient.test.lib.common.HttpServerAdapters.createClientBuilderForH3;
import org.junit.jupiter.api.AfterAll;
import org.junit.jupiter.api.Assertions;
@ -64,25 +70,29 @@ public class InvalidSSLContextTest {
private static final SSLContext sslContext = SimpleSSLContext.findSSLContext();
static volatile SSLServerSocket sslServerSocket;
static volatile String uri;
private static HttpTestServer h3Server;
private static String h3Uri;
public static Object[][] versions() {
return new Object[][]{
{ HTTP_1_1 },
{ HTTP_2 }
{ HTTP_1_1, uri },
{ HTTP_2 , uri },
{ HTTP_3 , h3Uri }
};
}
@ParameterizedTest
@MethodSource("versions")
public void testSync(Version version) throws Exception {
public void testSync(Version version, String uri) throws Exception {
// client-side uses a different context to that of the server-side
HttpClient client = HttpClient.newBuilder()
HttpClient client = createClientBuilderForH3()
.proxy(NO_PROXY)
.sslContext(SSLContext.getDefault())
.build();
HttpRequest request = HttpRequest.newBuilder(URI.create(uri))
.version(version)
.setOption(HttpOption.H3_DISCOVERY, HTTP_3_URI_ONLY)
.build();
try {
@ -90,21 +100,22 @@ public class InvalidSSLContextTest {
Assertions.fail("UNEXPECTED response" + response);
} catch (IOException ex) {
System.out.println("Caught expected: " + ex);
assertExceptionOrCause(SSLException.class, ex);
assertException(SSLException.class, ex);
}
}
@ParameterizedTest
@MethodSource("versions")
public void testAsync(Version version) throws Exception {
public void testAsync(Version version, String uri) throws Exception {
// client-side uses a different context to that of the server-side
HttpClient client = HttpClient.newBuilder()
HttpClient client = createClientBuilderForH3()
.proxy(NO_PROXY)
.sslContext(SSLContext.getDefault())
.build();
HttpRequest request = HttpRequest.newBuilder(URI.create(uri))
.version(version)
.setOption(HttpOption.H3_DISCOVERY, HTTP_3_URI_ONLY)
.build();
assertExceptionally(SSLException.class,
@ -123,26 +134,20 @@ public class InvalidSSLContextTest {
if (cause == null) {
Assertions.fail("Unexpected null cause: " + error);
}
assertExceptionOrCause(clazz, cause);
System.out.println("Caught expected: " + cause);
assertException(clazz, cause);
} else {
assertExceptionOrCause(clazz, error);
System.out.println("Caught expected: " + error);
assertException(clazz, error);
}
return null;
}).join();
}
static void assertExceptionOrCause(Class<? extends Throwable> clazz, Throwable t) {
if (t == null) {
Assertions.fail("Expected " + clazz + ", caught nothing");
}
final Throwable original = t;
do {
if (clazz.isInstance(t)) {
return; // found
}
} while ((t = t.getCause()) != null);
original.printStackTrace(System.out);
Assertions.fail("Expected " + clazz + "in " + original);
static void assertException(Class<? extends Throwable> clazz, Throwable t) {
Assertions.assertInstanceOf(clazz, t);
Assertions.assertTrue(t.getMessage().contains("unable to find valid certification path to requested target"),
"Unexpected exception message: " + t);
}
@BeforeAll
@ -159,7 +164,7 @@ public class InvalidSSLContextTest {
Thread t = new Thread("SSL-Server-Side") {
@Override
public void run() {
while (true) {
while (!sslServerSocket.isClosed()) {
try {
SSLSocket s = (SSLSocket) sslServerSocket.accept();
System.out.println("SERVER: accepted: " + s);
@ -177,7 +182,6 @@ public class InvalidSSLContextTest {
if (!sslServerSocket.isClosed()) {
throw new UncheckedIOException(e);
}
break;
} catch (InterruptedException ie) {
throw new RuntimeException(ie);
}
@ -185,10 +189,16 @@ public class InvalidSSLContextTest {
}
};
t.start();
h3Server = HttpTestServer.create(HTTP_3_URI_ONLY, sslContext);
h3Server.addHandler((exchange) -> exchange.sendResponseHeaders(200, 0), "/hello");
h3Server.start();
h3Uri = "https://" + h3Server.serverAuthority() + "/hello";
}
@AfterAll
public static void teardown() throws Exception {
h3Server.stop();
sslServerSocket.close();
}
}