From f079e3c85663844a8bc4402726d5923bd9715782 Mon Sep 17 00:00:00 2001 From: Konrad Windszus Date: Mon, 6 Jul 2026 13:57:39 +0200 Subject: [PATCH 1/8] Support HTTP/3 in Jetty and JRE HTTP Client Consolidate http version configuration among HTTP transporters Default to HTTP/2 in all transporters except Apache HTTP Client 4.x Still WIP --- .../aether/ConfigurationProperties.java | 33 +++++++ maven-resolver-test-http/pom.xml | 8 ++ .../internal/test/util/http/HttpServer.java | 90 ++++++++++++++----- .../test/util/http/HttpTransporterTest.java | 50 ++++++++--- .../src/main/resources/configuration.md.vm | 1 + .../aether/transport/jdk/JdkTransporter.java | 58 ++++++++++-- .../jdk/JdkTransporterConfigurationKeys.java | 2 + .../transport/jdk/JdkTransporterTest.java | 31 +++++++ maven-resolver-transport-jetty/pom.xml | 8 ++ .../transport/jetty/JettyTransporter.java | 88 ++++++++++-------- .../transport/jetty/JettyTransporterTest.java | 10 +++ .../org/eclipse/aether/util/ConfigUtils.java | 45 ++++++++++ .../transport/http/HttpTransporterUtils.java | 10 +++ .../eclipse/aether/util/ConfigUtilsTest.java | 25 ++++++ 14 files changed, 384 insertions(+), 75 deletions(-) diff --git a/maven-resolver-api/src/main/java/org/eclipse/aether/ConfigurationProperties.java b/maven-resolver-api/src/main/java/org/eclipse/aether/ConfigurationProperties.java index d9d9ce391d..97af6cb179 100644 --- a/maven-resolver-api/src/main/java/org/eclipse/aether/ConfigurationProperties.java +++ b/maven-resolver-api/src/main/java/org/eclipse/aether/ConfigurationProperties.java @@ -514,6 +514,39 @@ public final class ConfigurationProperties { */ public static final String HTTPS_SECURITY_MODE_INSECURE = "insecure"; + public enum HttpVersion { + /** + * The default HTTP version supported by the respective transporter (the most recent stable one, usually HTTP/2) + */ + DEFAULT, + HTTP_1_1, + HTTP_2, + HTTP_3, + /** + * The maximum HTTP version supported by the respective transporter (may be unstable). + */ + MAXIMUM; + } + + /** + * The maximum and preferred HTTP version. It transparently falls back to lower version if remote server does not support it. + * Value must be a {@link HttpVersion} enum value or its String representation. Default is {@link #DEFAULT_HTTP_VERSION}. + * + * @configurationSource {@link RepositorySystemSession#getConfigProperties()} + * @configurationType {@link ConfigurationProperties.HttpVersion} + * @configurationDefaultValue {@link #DEFAULT_HTTP_VERSION} + * @configurationRepoIdSuffix Yes + * @since NEXT + */ + public static final String HTTP_VERSION = PREFIX_TRANSPORT_HTTP + "version"; + + /** + * Default value if {@link #HTTP_VERSION} is not set. + * + * @since NEXT + */ + public static final HttpVersion DEFAULT_HTTP_VERSION = HttpVersion.DEFAULT; + /** * A flag indicating which visitor should be used to "flatten" the dependency graph into list. In Maven 4 * the default is new "levelOrder", while Maven 3 used "preOrder". This property accepts values diff --git a/maven-resolver-test-http/pom.xml b/maven-resolver-test-http/pom.xml index 6845de4442..6bafd11ba2 100644 --- a/maven-resolver-test-http/pom.xml +++ b/maven-resolver-test-http/pom.xml @@ -77,6 +77,14 @@ org.eclipse.jetty.http2 jetty-http2-server + + org.eclipse.jetty.http3 + jetty-http3-server + + + org.eclipse.jetty.quic + jetty-quic-quiche-server + org.eclipse.jetty.compression jetty-compression-server diff --git a/maven-resolver-test-http/src/main/java/org/eclipse/aether/internal/test/util/http/HttpServer.java b/maven-resolver-test-http/src/main/java/org/eclipse/aether/internal/test/util/http/HttpServer.java index ecffdd118a..85584865d2 100644 --- a/maven-resolver-test-http/src/main/java/org/eclipse/aether/internal/test/util/http/HttpServer.java +++ b/maven-resolver-test-http/src/main/java/org/eclipse/aether/internal/test/util/http/HttpServer.java @@ -51,12 +51,17 @@ import org.eclipse.jetty.http.HttpHeader; import org.eclipse.jetty.http.HttpMethod; import org.eclipse.jetty.http.HttpURI; +import org.eclipse.jetty.http.HttpVersion; import org.eclipse.jetty.http.pathmap.MatchedResource; import org.eclipse.jetty.http.pathmap.PathMappings; import org.eclipse.jetty.http.pathmap.PathSpec; import org.eclipse.jetty.http2.server.HTTP2ServerConnectionFactory; +import org.eclipse.jetty.http3.server.HTTP3ServerConnectionFactory; +import org.eclipse.jetty.http3.server.HTTP3ServerQuicConfiguration; import org.eclipse.jetty.io.ByteBufferPool; import org.eclipse.jetty.io.Content; +import org.eclipse.jetty.quic.quiche.server.QuicheServerConnector; +import org.eclipse.jetty.quic.quiche.server.QuicheServerQuicConfiguration; import org.eclipse.jetty.server.Handler; import org.eclipse.jetty.server.HttpConfiguration; import org.eclipse.jetty.server.HttpConnectionFactory; @@ -75,6 +80,7 @@ public class HttpServer { public static class LogEntry { + private final HttpVersion version; private final String method; @@ -86,12 +92,17 @@ public static class LogEntry { CountDownLatch responseHeadersAvailableSignal = new CountDownLatch(1); - public LogEntry(String method, String path, Map requestHeaders) { + public LogEntry(HttpVersion version, String method, String path, Map requestHeaders) { + this.version = version; this.method = method; this.path = path; this.requestHeaders = requestHeaders; } + public HttpVersion getVersion() { + return version; + } + public String getMethod() { return method; } @@ -127,7 +138,7 @@ public void setResponseHeaders(Map responseHeaders) { @Override public String toString() { - return method + " " + path; + return version + " " + method + " " + path; } } @@ -188,6 +199,10 @@ public int getHttpsPort() { return httpsConnector != null ? httpsConnector.getLocalPort() : -1; } + public int getHttp3Port() { + return http3Connector != null ? http3Connector.getLocalPort() : -1; + } + public String getHttpUrl() { return "http://" + getHost() + ":" + getHttpPort(); } @@ -196,6 +211,10 @@ public String getHttpsUrl() { return "https://" + getHost() + ":" + getHttpsPort(); } + public String getHttp3Url() { + return "https://" + getHost() + ":" + getHttp3Port(); + } + public HttpServer addSslConnector() { return addSslConnector(true, true); } @@ -210,23 +229,7 @@ public HttpServer addSelfSignedSslConnectorHttp2Only() { private HttpServer addSslConnector(boolean needClientAuth, boolean needHttp11) { if (httpsConnector == null) { - SslContextFactory.Server ssl = new SslContextFactory.Server(); - ssl.setNeedClientAuth(needClientAuth); - if (!needClientAuth) { - ssl.setKeyStorePath(HttpTransporterTest.KEY_STORE_SELF_SIGNED_PATH - .toAbsolutePath() - .toString()); - ssl.setKeyStorePassword("server-pwd"); - ssl.setSniRequired(false); - } else { - ssl.setKeyStorePath( - HttpTransporterTest.KEY_STORE_PATH.toAbsolutePath().toString()); - ssl.setKeyStorePassword("server-pwd"); - ssl.setTrustStorePath( - HttpTransporterTest.TRUST_STORE_PATH.toAbsolutePath().toString()); - ssl.setTrustStorePassword("client-pwd"); - ssl.setSniRequired(false); - } + SslContextFactory.Server ssl = createSslContextFactory(needClientAuth); HttpConfiguration httpsConfig = new HttpConfiguration(); SecureRequestCustomizer customizer = new SecureRequestCustomizer(); @@ -259,6 +262,48 @@ private HttpServer addSslConnector(boolean needClientAuth, boolean needHttp11) { return this; } + private SslContextFactory.Server createSslContextFactory(boolean needClientAuth) { + SslContextFactory.Server ssl = new SslContextFactory.Server(); + ssl.setNeedClientAuth(needClientAuth); + if (!needClientAuth) { + ssl.setKeyStorePath(HttpTransporterTest.KEY_STORE_SELF_SIGNED_PATH + .toAbsolutePath() + .toString()); + ssl.setKeyStorePassword("server-pwd"); + ssl.setSniRequired(false); + } else { + ssl.setKeyStorePath( + HttpTransporterTest.KEY_STORE_PATH.toAbsolutePath().toString()); + ssl.setKeyStorePassword("server-pwd"); + ssl.setTrustStorePath( + HttpTransporterTest.TRUST_STORE_PATH.toAbsolutePath().toString()); + ssl.setTrustStorePassword("client-pwd"); + ssl.setSniRequired(false); + } + return ssl; + } + + public HttpServer addHttp3Connector(boolean needClientAuth) { + if (http3Connector == null) { + QuicheServerQuicConfiguration serverQuicConfig = HTTP3ServerQuicConfiguration.configure( + new QuicheServerQuicConfiguration(HttpTransporterTest.PEM_QUICHE_SERVER_PATH)); + http3Connector = new QuicheServerConnector( + server, + createSslContextFactory(needClientAuth), + serverQuicConfig, + new HTTP3ServerConnectionFactory()); + // TODO: should share same port as https connector, so that the client can use the same port for both + // TCP/UDP + server.addConnector(http3Connector); + try { + http3Connector.start(); + } catch (Exception e) { + throw new IllegalStateException(e); + } + } + return this; + } + public List getLogEntries() { return logEntries; } @@ -484,14 +529,15 @@ private class LogHandler extends Handler.Wrapper { public boolean handle(Request req, Response response, Callback callback) throws Exception { LOGGER.info( - "{} {}{}", + "{} {} {}{}", + req.getConnectionMetaData().getHttpVersion(), req.getMethod(), req.getHttpURI().getDecodedPath(), req.getHttpURI().getQuery() != null ? "?" + req.getHttpURI().getQuery() : ""); Map requestHeaders = toUnmodifiableMap(req.getHeaders()); // capture request headers before other handlers modify them - LogEntry logEntry = new LogEntry(req.getMethod(), req.getHttpURI().getPathQuery(), requestHeaders); + LogEntry logEntry = new LogEntry(req.getConnectionMetaData().getHttpVersion(), req.getMethod(), req.getHttpURI().getPathQuery(), requestHeaders); logEntries.add(logEntry); // prevent closing the response before logging (assume all writes are synchronous for simplicity) boolean result = super.handle(req, response, callback); @@ -515,6 +561,8 @@ Map toUnmodifiableMap(HttpFields headers) { private static final Pattern SIMPLE_RANGE = Pattern.compile("bytes=([0-9])+-"); + private QuicheServerConnector http3Connector; + private class RepoHandler extends Handler.Abstract { @Override public boolean handle(Request req, Response response, Callback callback) throws Exception { diff --git a/maven-resolver-test-http/src/main/java/org/eclipse/aether/internal/test/util/http/HttpTransporterTest.java b/maven-resolver-test-http/src/main/java/org/eclipse/aether/internal/test/util/http/HttpTransporterTest.java index 00bbbc2425..3f4ec970b2 100644 --- a/maven-resolver-test-http/src/main/java/org/eclipse/aether/internal/test/util/http/HttpTransporterTest.java +++ b/maven-resolver-test-http/src/main/java/org/eclipse/aether/internal/test/util/http/HttpTransporterTest.java @@ -29,7 +29,6 @@ import java.io.UncheckedIOException; import java.net.ServerSocket; import java.net.URI; -import java.net.URL; import java.nio.charset.StandardCharsets; import java.nio.file.Files; import java.nio.file.Path; @@ -69,6 +68,7 @@ import org.eclipse.aether.transfer.NoTransporterException; import org.eclipse.aether.transfer.TransferCancelledException; import org.eclipse.aether.util.repository.AuthenticationBuilder; +import org.eclipse.jetty.http.HttpVersion; import org.junit.jupiter.api.AfterAll; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeAll; @@ -100,6 +100,8 @@ public abstract class HttpTransporterTest { protected static final Path TRUST_STORE_PATH = Paths.get("target/trustStore"); + protected static final Path PEM_QUICHE_SERVER_PATH = Paths.get("target/pems"); + protected static SSLContext defaultSslContext; static { @@ -110,18 +112,22 @@ public abstract class HttpTransporterTest { @BeforeAll protected static void beforeAll() throws NoSuchAlgorithmException { // populate custom keystore and truststore - URL keyStoreUrl = HttpTransporterTest.class.getClassLoader().getResource("ssl/server-store"); - URL keyStoreSelfSignedUrl = - HttpTransporterTest.class.getClassLoader().getResource("ssl/server-store-selfsigned"); - URL trustStoreUrl = HttpTransporterTest.class.getClassLoader().getResource("ssl/client-store"); - try { - try (InputStream keyStoreStream = keyStoreUrl.openStream(); - InputStream keyStoreSelfSignedStream = keyStoreSelfSignedUrl.openStream(); - InputStream trustStoreStream = trustStoreUrl.openStream()) { + try (InputStream keyStoreStream = + HttpTransporterTest.class.getClassLoader().getResourceAsStream("ssl/server-store"); + InputStream keyStoreSelfSignedStream = HttpTransporterTest.class + .getClassLoader() + .getResourceAsStream("ssl/server-store-selfsigned"); + InputStream trustStoreStream = + HttpTransporterTest.class.getClassLoader().getResourceAsStream("ssl/client-store"); + InputStream serverPemStream = + HttpTransporterTest.class.getClassLoader().getResourceAsStream("ssl/server.pem"); + InputStream serverSelfsignedPemStream = + HttpTransporterTest.class.getClassLoader().getResourceAsStream("ssl/selfsigned.pem")) { Files.copy(keyStoreStream, KEY_STORE_PATH, StandardCopyOption.REPLACE_EXISTING); Files.copy(keyStoreSelfSignedStream, KEY_STORE_SELF_SIGNED_PATH, StandardCopyOption.REPLACE_EXISTING); Files.copy(trustStoreStream, TRUST_STORE_PATH, StandardCopyOption.REPLACE_EXISTING); + Files.createDirectories(PEM_QUICHE_SERVER_PATH); } } catch (IOException e) { throw new UncheckedIOException(e); @@ -805,8 +811,6 @@ protected void testGet_HTTPS_Insecure_SecurityMode() throws Exception { @Test protected void testGet_HTTPS_HTTP2Only_Insecure_SecurityMode() throws Exception { - // here we use alternate server-store-selfigned key (as the key set it static initializer is probably already - // used to init SSLContext/SSLSocketFactory/etc enableHttp2Protocol(); session.setConfigProperty( ConfigurationProperties.HTTPS_SECURITY_MODE, ConfigurationProperties.HTTPS_SECURITY_MODE_INSECURE); @@ -821,10 +825,34 @@ protected void testGet_HTTPS_HTTP2Only_Insecure_SecurityMode() throws Exception assertEquals(1, listener.getStartedCount()); assertTrue(listener.getProgressedCount() > 0, "Count: " + listener.getProgressedCount()); assertEquals(task.getDataString(), listener.getBaos().toString(StandardCharsets.UTF_8)); + httpServer.getLogEntries().forEach(log -> { + assertEquals(HttpVersion.HTTP_2, log.getVersion()); + }); } protected void enableHttp2Protocol() {} + @Test + protected void testGet_HTTP3() throws Exception { + // self-signed cert should be accepted through default SSL context + session.setConfigProperty(ConfigurationProperties.HTTP_VERSION, ConfigurationProperties.HttpVersion.HTTP_3); + httpServer.addHttp3Connector(false); + httpServer.start(); + newTransporter(httpServer.getHttp3Url()); + RecordingTransportListener listener = new RecordingTransportListener(); + GetTask task = new GetTask(URI.create("repo/file.txt")).setListener(listener); + transporter.get(task); + assertEquals("test", task.getDataString()); + assertEquals(0L, listener.getDataOffset()); + assertEquals(4L, listener.getDataLength()); + assertEquals(1, listener.getStartedCount()); + assertTrue(listener.getProgressedCount() > 0, "Count: " + listener.getProgressedCount()); + assertEquals(task.getDataString(), listener.getBaos().toString(StandardCharsets.UTF_8)); + httpServer.getLogEntries().forEach(log -> { + assertEquals(HttpVersion.HTTP_3, log.getVersion()); + }); + } + @Test protected void testGet_Redirect() throws Exception { httpServer.addSslConnector(); diff --git a/maven-resolver-tools/src/main/resources/configuration.md.vm b/maven-resolver-tools/src/main/resources/configuration.md.vm index 2b5669e898..9efd0a5aeb 100644 --- a/maven-resolver-tools/src/main/resources/configuration.md.vm +++ b/maven-resolver-tools/src/main/resources/configuration.md.vm @@ -68,6 +68,7 @@ From | To | With `String` | `int` | [`Integer.parseInt(...)`](https://docs.oracle.com/javase/7/docs/api/java/lang/Integer.html#parseInt(java.lang.String)) `String` | `long` | [`Long.parseLong(...)`](https://docs.oracle.com/javase/7/docs/api/java/lang/Long.html#parseLong(java.lang.String)) `String` | `float` | [`Float.parseFloat(...)`](https://docs.oracle.com/javase/7/docs/api/java/lang/Float.html#parseFloat(java.lang.String)) +`String` | `T extends Enum` | [`Enum.valueOf(...)`](https://docs.oracle.com/javase/7/docs/api/java/lang/Enum.html#valueOf(java.lang.Class,%20java.lang.String)) ## Set Configuration from Apache Maven diff --git a/maven-resolver-transport-jdk-parent/maven-resolver-transport-jdk11/src/main/java/org/eclipse/aether/transport/jdk/JdkTransporter.java b/maven-resolver-transport-jdk-parent/maven-resolver-transport-jdk11/src/main/java/org/eclipse/aether/transport/jdk/JdkTransporter.java index 0cb866af96..14b2ce99f5 100644 --- a/maven-resolver-transport-jdk-parent/maven-resolver-transport-jdk11/src/main/java/org/eclipse/aether/transport/jdk/JdkTransporter.java +++ b/maven-resolver-transport-jdk-parent/maven-resolver-transport-jdk11/src/main/java/org/eclipse/aether/transport/jdk/JdkTransporter.java @@ -71,6 +71,7 @@ import com.github.mizosoft.methanol.RetryInterceptor; import com.github.mizosoft.methanol.RetryInterceptor.Context; import org.eclipse.aether.ConfigurationProperties; +import org.eclipse.aether.ConfigurationProperties.HttpVersion; import org.eclipse.aether.RepositorySystemSession; import org.eclipse.aether.repository.AuthenticationContext; import org.eclipse.aether.repository.RemoteRepository; @@ -105,7 +106,6 @@ import static org.eclipse.aether.spi.connector.transport.http.HttpConstants.USER_AGENT; import static org.eclipse.aether.transport.jdk.JdkTransporterConfigurationKeys.CONFIG_PROP_HTTP_VERSION; import static org.eclipse.aether.transport.jdk.JdkTransporterConfigurationKeys.CONFIG_PROP_MAX_CONCURRENT_REQUESTS; -import static org.eclipse.aether.transport.jdk.JdkTransporterConfigurationKeys.DEFAULT_HTTP_VERSION; import static org.eclipse.aether.transport.jdk.JdkTransporterConfigurationKeys.DEFAULT_MAX_CONCURRENT_REQUESTS; /** @@ -468,6 +468,53 @@ protected void implClose() { } } + HttpClient.Version getHttpVersion(RepositorySystemSession session, RemoteRepository repository) { + HttpVersion httpVersion = HttpTransporterUtils.getHttpVersion(session, repository); + if (httpVersion == ConfigurationProperties.DEFAULT_HTTP_VERSION) { + // Fall back to legacy JDK Transporter specific property when it is explicitly configured. + String configuredLegacyHttpVersion = ConfigUtils.getString( + session, null, CONFIG_PROP_HTTP_VERSION + "." + repository.getId(), CONFIG_PROP_HTTP_VERSION); + if (configuredLegacyHttpVersion != null) { + return resolveHttpVersion(configuredLegacyHttpVersion); + } + return HttpClient.Version.HTTP_2; + } else { + switch (httpVersion) { + case MAXIMUM: + return getMaximumSupportedHttpVersion(); + case HTTP_1_1: + return HttpClient.Version.HTTP_1_1; + case HTTP_2: + case DEFAULT: + return HttpClient.Version.HTTP_2; + case HTTP_3: + return resolveHttpVersion("HTTP_3"); + default: + throw new IllegalArgumentException("Unsupported HTTP version: " + httpVersion); + } + } + } + + private HttpClient.Version resolveHttpVersion(String requestedVersion) { + HttpClient.Version maximumHttpVersion = getMaximumSupportedHttpVersion(); + HttpClient.Version resolved; + try { + resolved = HttpClient.Version.valueOf(requestedVersion); + } catch (IllegalArgumentException e) { + LOGGER.warn( + "HTTP version '{}' is not supported by the running JRE, using '{}' instead", + requestedVersion, + maximumHttpVersion); + return maximumHttpVersion; + } + return resolved; + } + + HttpClient.Version getMaximumSupportedHttpVersion() { + HttpClient.Version[] values = HttpClient.Version.values(); + return values[values.length - 1]; + } + private HttpClient createClient(RepositorySystemSession session, RemoteRepository repository, boolean insecure) throws RuntimeException { @@ -488,6 +535,9 @@ private HttpClient createClient(RepositorySystemSession session, RemoteRepositor try { if (insecure) { sslContext = SSLContext.getInstance("TLS"); + // custom trust manager not supported for HTTP/3 (Quic) + // (https://github.com/openjdk/jdk/blob/631b675d7949a0e6312d8d6f45e2515d53b12f05/src/java.base/share/classes/sun/security/ssl/SSLContextImpl.java#L529) + // https://openjdk.org/jeps/517 X509ExtendedTrustManager tm = new X509ExtendedTrustManager() { @Override public void checkClientTrusted(X509Certificate[] chain, String authType) {} @@ -526,11 +576,7 @@ public X509Certificate[] getAcceptedIssuers() { } Methanol.Builder builder = Methanol.newBuilder() - .version(HttpClient.Version.valueOf(ConfigUtils.getString( - session, - DEFAULT_HTTP_VERSION, - CONFIG_PROP_HTTP_VERSION + "." + repository.getId(), - CONFIG_PROP_HTTP_VERSION))) + .version(getHttpVersion(session, repository)) .followRedirects(HttpClient.Redirect.NORMAL) .connectTimeout(Duration.ofMillis(connectTimeout)) // this only considers the time until the response header is received, see diff --git a/maven-resolver-transport-jdk-parent/maven-resolver-transport-jdk11/src/main/java/org/eclipse/aether/transport/jdk/JdkTransporterConfigurationKeys.java b/maven-resolver-transport-jdk-parent/maven-resolver-transport-jdk11/src/main/java/org/eclipse/aether/transport/jdk/JdkTransporterConfigurationKeys.java index 83b72fa3d9..8b6c5e4574 100644 --- a/maven-resolver-transport-jdk-parent/maven-resolver-transport-jdk11/src/main/java/org/eclipse/aether/transport/jdk/JdkTransporterConfigurationKeys.java +++ b/maven-resolver-transport-jdk-parent/maven-resolver-transport-jdk11/src/main/java/org/eclipse/aether/transport/jdk/JdkTransporterConfigurationKeys.java @@ -39,7 +39,9 @@ private JdkTransporterConfigurationKeys() {} * @configurationType {@link java.lang.String} * @configurationDefaultValue {@link #DEFAULT_HTTP_VERSION} * @configurationRepoIdSuffix Yes + * @deprecated Use {@link ConfigProperties#CONFIG_PROP_HTTP_VERSION} instead. This property is kept for backward compatibility and will be removed in future versions. */ + @Deprecated public static final String CONFIG_PROP_HTTP_VERSION = CONFIG_PROPS_PREFIX + "httpVersion"; public static final String DEFAULT_HTTP_VERSION = "HTTP_1_1"; diff --git a/maven-resolver-transport-jdk-parent/maven-resolver-transport-jdk11/src/test/java/org/eclipse/aether/transport/jdk/JdkTransporterTest.java b/maven-resolver-transport-jdk-parent/maven-resolver-transport-jdk11/src/test/java/org/eclipse/aether/transport/jdk/JdkTransporterTest.java index c570fa5b0d..87810bd200 100644 --- a/maven-resolver-transport-jdk-parent/maven-resolver-transport-jdk11/src/test/java/org/eclipse/aether/transport/jdk/JdkTransporterTest.java +++ b/maven-resolver-transport-jdk-parent/maven-resolver-transport-jdk11/src/test/java/org/eclipse/aether/transport/jdk/JdkTransporterTest.java @@ -20,8 +20,11 @@ import java.net.ConnectException; import java.net.URI; +import java.net.http.HttpClient; +import java.net.http.HttpClient.Version; import java.util.stream.Stream; +import org.eclipse.aether.RepositorySystemSession; import org.eclipse.aether.internal.impl.DefaultPathProcessor; import org.eclipse.aether.internal.test.util.TestUtils; import org.eclipse.aether.internal.test.util.http.HttpTransporterTest; @@ -29,7 +32,10 @@ import org.eclipse.aether.spi.connector.transport.PeekTask; import org.eclipse.aether.spi.connector.transport.Transporter; import org.junit.jupiter.api.Disabled; +import org.junit.jupiter.api.MethodOrderer; import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.TestMethodOrder; +import org.junit.jupiter.api.condition.EnabledForJreRange; import org.junit.jupiter.api.condition.JRE; import static org.junit.jupiter.api.Assertions.assertEquals; @@ -40,6 +46,7 @@ * JDK Transporter UT. * Related: No TLS proxy supported. */ +@TestMethodOrder(MethodOrderer.MethodName.class) class JdkTransporterTest extends HttpTransporterTest { private boolean isBetweenJava17and21() { @@ -95,6 +102,13 @@ protected void testRetryHandler_defaultCount_negative() throws Exception { } } + @Override + @EnabledForJreRange(min = JRE.JAVA_26) + @Test + protected void testGet_HTTP3() throws Exception { + super.testGet_HTTP3(); + } + public JdkTransporterTest() { super(() -> new JdkTransporterFactory(standardChecksumExtractor(), new DefaultPathProcessor())); } @@ -126,4 +140,21 @@ void testGetBasicAuthValue() { "Basic QWxhZGRpbjpvcGVuIHNlc2FtZQ==", JdkTransporter.getBasicAuthValue("Aladdin", "open sesame".toCharArray())); } + + @Test + void testMaximumHttpVersionAtRuntime() throws Exception { + RepositorySystemSession session = TestUtils.newSession(); + RemoteRepository remoteRepository = + new RemoteRepository.Builder("central", "default", "https://repo.maven.apache.org/maven2/").build(); + JdkTransporterFactory factory = new JdkTransporterFactory(s -> null, new DefaultPathProcessor()); + + try (Transporter transporter = factory.newInstance(session, remoteRepository)) { + JdkTransporter jdkTransporter = (JdkTransporter) transporter; + HttpClient.Version[] versions = HttpClient.Version.values(); + HttpClient.Version maximumSupported = versions[versions.length - 1]; + assertEquals(maximumSupported, jdkTransporter.getMaximumSupportedHttpVersion()); + // default should be returned which is HTTP_2 for all JRE versions + assertEquals(Version.HTTP_2, jdkTransporter.getHttpVersion(session, remoteRepository)); + } + } } diff --git a/maven-resolver-transport-jetty/pom.xml b/maven-resolver-transport-jetty/pom.xml index 87f8454548..1e78e32ed6 100644 --- a/maven-resolver-transport-jetty/pom.xml +++ b/maven-resolver-transport-jetty/pom.xml @@ -72,6 +72,14 @@ org.eclipse.jetty.http2 jetty-http2-client-transport + + org.eclipse.jetty.http3 + jetty-http3-client-transport + + + org.eclipse.jetty.quic + jetty-quic-quiche-client + org.eclipse.jetty.compression diff --git a/maven-resolver-transport-jetty/src/main/java/org/eclipse/aether/transport/jetty/JettyTransporter.java b/maven-resolver-transport-jetty/src/main/java/org/eclipse/aether/transport/jetty/JettyTransporter.java index 2b9e8248d5..7a8f4c50ac 100644 --- a/maven-resolver-transport-jetty/src/main/java/org/eclipse/aether/transport/jetty/JettyTransporter.java +++ b/maven-resolver-transport-jetty/src/main/java/org/eclipse/aether/transport/jetty/JettyTransporter.java @@ -19,7 +19,6 @@ package org.eclipse.aether.transport.jetty; import javax.net.ssl.SSLContext; -import javax.net.ssl.X509TrustManager; import java.io.IOException; import java.io.InputStream; @@ -28,7 +27,9 @@ import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.StandardCopyOption; -import java.security.cert.X509Certificate; +import java.security.NoSuchAlgorithmException; +import java.util.ArrayList; +import java.util.Collection; import java.util.HashMap; import java.util.Map; import java.util.concurrent.ExecutionException; @@ -39,6 +40,7 @@ import java.util.regex.Matcher; import org.eclipse.aether.ConfigurationProperties; +import org.eclipse.aether.ConfigurationProperties.HttpVersion; import org.eclipse.aether.RepositorySystemSession; import org.eclipse.aether.repository.AuthenticationContext; import org.eclipse.aether.repository.RemoteRepository; @@ -67,7 +69,13 @@ import org.eclipse.jetty.http.HttpHeader; import org.eclipse.jetty.http2.client.HTTP2Client; import org.eclipse.jetty.http2.client.transport.ClientConnectionFactoryOverHTTP2; +import org.eclipse.jetty.http3.client.HTTP3Client; +import org.eclipse.jetty.http3.client.HTTP3ClientQuicConfiguration; +import org.eclipse.jetty.http3.client.transport.ClientConnectionFactoryOverHTTP3; +import org.eclipse.jetty.io.ClientConnectionFactory; import org.eclipse.jetty.io.ClientConnector; +import org.eclipse.jetty.quic.quiche.client.QuicheClientQuicConfiguration; +import org.eclipse.jetty.quic.quiche.client.QuicheTransport; import org.eclipse.jetty.util.ssl.SslContextFactory; import static org.eclipse.aether.spi.connector.transport.http.HttpConstants.ACCEPT_ENCODING; @@ -388,40 +396,27 @@ private HttpClient createClient() throws RuntimeException { } } - if (sslContext == null) { + SslContextFactory.Client sslContextFactory = new SslContextFactory.Client(); + if (insecure) { + // this is also passed on to Quiche for HTTP/3 + sslContextFactory.setEndpointIdentificationAlgorithm(null); + sslContextFactory.setHostnameVerifier((name, context) -> true); + sslContextFactory.setTrustAll(true); + } else { try { - if (insecure) { - sslContext = SSLContext.getInstance("TLS"); - X509TrustManager tm = new X509TrustManager() { - @Override - public void checkClientTrusted(X509Certificate[] chain, String authType) {} - - @Override - public void checkServerTrusted(X509Certificate[] chain, String authType) {} - - @Override - public X509Certificate[] getAcceptedIssuers() { - return new X509Certificate[0]; - } - }; - sslContext.init(null, new X509TrustManager[] {tm}, null); - } else { + if (sslContext == null) { + // use the JVM's default SSL context (potentially with custom keystores/truststores) + // https://github.com/jetty/jetty.project/issues/15378 sslContext = SSLContext.getDefault(); } - } catch (Exception e) { - if (e instanceof RuntimeException) { - throw (RuntimeException) e; - } else { - throw new IllegalStateException("SSL Context setup failure", e); - } + } catch (NoSuchAlgorithmException e) { + throw new IllegalStateException("SSL Context setup failure", e); } } - SslContextFactory.Client sslContextFactory = new SslContextFactory.Client(); - sslContextFactory.setSslContext(sslContext); - if (insecure) { - sslContextFactory.setEndpointIdentificationAlgorithm(null); - sslContextFactory.setHostnameVerifier((name, context) -> true); + if (sslContext != null) { + // not properly supported by Quiche for HTTP/3, but Jetty will use it for HTTP/2 and HTTP/1.1 + sslContextFactory.setSslContext(sslContext); } ClientConnector clientConnector = new ClientConnector(); @@ -430,16 +425,35 @@ public X509Certificate[] getAcceptedIssuers() { HTTP2Client http2Client = new HTTP2Client(clientConnector); ClientConnectionFactoryOverHTTP2.HTTP2 http2 = new ClientConnectionFactoryOverHTTP2.HTTP2(http2Client); - HttpClientTransportDynamic transport; + QuicheClientQuicConfiguration clientQuicConfig = + HTTP3ClientQuicConfiguration.configure(new QuicheClientQuicConfiguration()); + HTTP3Client http3Client = new HTTP3Client(clientQuicConfig, clientConnector); + QuicheTransport transport = new QuicheTransport(clientQuicConfig); + ClientConnectionFactoryOverHTTP3.HTTP3 http3 = + new ClientConnectionFactoryOverHTTP3.HTTP3(http3Client, transport); + + Collection connectors = new ArrayList<>(); if ("https".equalsIgnoreCase(repository.getProtocol())) { - transport = new HttpClientTransportDynamic( - clientConnector, http2, HttpClientConnectionFactory.HTTP11); // HTTPS, prefer H2 - } else { - transport = new HttpClientTransportDynamic( - clientConnector, HttpClientConnectionFactory.HTTP11, http2); // plaintext HTTP, H2 cannot be used + HttpVersion httpVersion = HttpTransporterUtils.getHttpVersion(session, repository); + switch (httpVersion) { + case MAXIMUM: + case HTTP_3: + connectors.add(http3); + break; + // always support HTTP/2 as fallback for HTTP/3 + case HTTP_2: + case DEFAULT: + connectors.add(http2); + break; + default: + break; + } } + connectors.add(HttpClientConnectionFactory.HTTP11); // HTTP/1.1, always supported but has least priority - HttpClient httpClient = new HttpClient(transport); + HttpClientTransportDynamic dynamicTransport = new HttpClientTransportDynamic( + clientConnector, connectors.toArray(new ClientConnectionFactory.Info[0])); + HttpClient httpClient = new HttpClient(dynamicTransport); httpClient.setConnectTimeout(connectTimeout); httpClient.setIdleTimeout(requestTimeout); httpClient.setFollowRedirects(ConfigUtils.getBoolean( diff --git a/maven-resolver-transport-jetty/src/test/java/org/eclipse/aether/transport/jetty/JettyTransporterTest.java b/maven-resolver-transport-jetty/src/test/java/org/eclipse/aether/transport/jetty/JettyTransporterTest.java index 4917ad734a..bcecdc8f0f 100644 --- a/maven-resolver-transport-jetty/src/test/java/org/eclipse/aether/transport/jetty/JettyTransporterTest.java +++ b/maven-resolver-transport-jetty/src/test/java/org/eclipse/aether/transport/jetty/JettyTransporterTest.java @@ -20,6 +20,7 @@ import java.util.stream.Stream; +import org.eclipse.aether.ConfigurationProperties; import org.eclipse.aether.internal.test.util.http.HttpTransporterTest; import org.eclipse.aether.spi.io.PathProcessorSupport; import org.junit.jupiter.api.Disabled; @@ -75,6 +76,15 @@ protected void testRetryHandler_tooManyRequests_explicitCount_negative() {} @Test protected void testPut_Authenticated_ExpectContinueRejected_ExplicitlyConfiguredHeader() {} + @Override + @Test + protected void testGet_HTTP3() throws Exception { + // Jetty's HTTP/3 support is based on Quiche which does not consider the default SSL context (https://github.com/jetty/jetty.project/issues/15370) + session.setConfigProperty( + ConfigurationProperties.HTTPS_SECURITY_MODE, ConfigurationProperties.HTTPS_SECURITY_MODE_INSECURE); + super.testGet_HTTP3(); + } + public JettyTransporterTest() { super(() -> new JettyTransporterFactory(standardChecksumExtractor(), new PathProcessorSupport())); } diff --git a/maven-resolver-util/src/main/java/org/eclipse/aether/util/ConfigUtils.java b/maven-resolver-util/src/main/java/org/eclipse/aether/util/ConfigUtils.java index 523ffba147..ab1e9ca37d 100644 --- a/maven-resolver-util/src/main/java/org/eclipse/aether/util/ConfigUtils.java +++ b/maven-resolver-util/src/main/java/org/eclipse/aether/util/ConfigUtils.java @@ -368,4 +368,49 @@ public static List parseCommaSeparatedNames(String commaSeparatedNames) public static List parseCommaSeparatedUniqueNames(String commaSeparatedNames) { return parseCommaSeparatedNames(commaSeparatedNames).stream().distinct().collect(toList()); } + + /** + * Gets the specified configuration property. + * @param the enum type + * + * @param properties the configuration properties to read, must not be {@code null} + * @param enumClass the enum class to read, must not be {@code null} + * @param defaultValue the default value to return in case none of the property keys is set to a boolean + * @param keys the property keys to read, must not be {@code null}. The specified keys are read one after one until + * a enum type {@code T} or a string (parsed using {@link Enum#valueOf(Class, String)} is found. + * @return the property value or {@code defaultValue} if none found + */ + public static > T getEnum( + Map properties, Class enumClass, T defaultValue, String... keys) { + for (String key : keys) { + Object value = properties.get(key); + + if (value instanceof Enum) { + return (T) value; + } else if (value instanceof String) { + return Enum.valueOf(enumClass, (String) value); + } + } + return defaultValue; + } + + /** + * Gets the specified configuration property. + * + * @param session the repository system session from which to read the configuration property, must not be + * {@code null} + * @param the enum type + * + * @param session the repository system session from which to read the configuration property, must not be + * {@code null} + * @param enumClass the enum class to read, must not be {@code null} + * @param defaultValue the default value to return in case none of the property keys is set to a boolean + * @param keys the property keys to read, must not be {@code null}. The specified keys are read one after one until + * a enum type {@code T} or a string (parsed using {@link Enum#valueOf(Class, String)} is found. + * @return the property value or {@code defaultValue} if none found + */ + public static > T getEnum( + RepositorySystemSession session, Class enumClass, T defaultValue, String... keys) { + return getEnum(session.getConfigProperties(), enumClass, defaultValue, keys); + } } diff --git a/maven-resolver-util/src/main/java/org/eclipse/aether/util/connector/transport/http/HttpTransporterUtils.java b/maven-resolver-util/src/main/java/org/eclipse/aether/util/connector/transport/http/HttpTransporterUtils.java index a8fe4efedf..787d1ff3e9 100644 --- a/maven-resolver-util/src/main/java/org/eclipse/aether/util/connector/transport/http/HttpTransporterUtils.java +++ b/maven-resolver-util/src/main/java/org/eclipse/aether/util/connector/transport/http/HttpTransporterUtils.java @@ -315,6 +315,16 @@ public static Optional getHttpLocalAddress( return Optional.empty(); } + public static ConfigurationProperties.HttpVersion getHttpVersion( + RepositorySystemSession session, RemoteRepository repository) { + return ConfigUtils.getEnum( + session, + ConfigurationProperties.HttpVersion.class, + ConfigurationProperties.DEFAULT_HTTP_VERSION, + ConfigurationProperties.HTTP_VERSION + "." + repository.getId(), + ConfigurationProperties.HTTP_VERSION); + } + /** * Shared code to create "base {@link URI}" for most common HTTP remote repositories and all HTTP transports. * Note: this method just applies common validation and adjustments to URI, but it does not enforce protocol diff --git a/maven-resolver-util/src/test/java/org/eclipse/aether/util/ConfigUtilsTest.java b/maven-resolver-util/src/test/java/org/eclipse/aether/util/ConfigUtilsTest.java index d3797a314f..480fbafc8e 100644 --- a/maven-resolver-util/src/test/java/org/eclipse/aether/util/ConfigUtilsTest.java +++ b/maven-resolver-util/src/test/java/org/eclipse/aether/util/ConfigUtilsTest.java @@ -199,4 +199,29 @@ void testGetFloat_NumberConversion() { config.put("some-number", -1234f); assertEquals(-1234f, ConfigUtils.getFloat(config, 0, "some-number"), 0.1f); } + + private enum TestEnum { + FIRST, + SECOND + } + + @Test + void testGetEnum() { + config.put("some-enum", TestEnum.SECOND); + assertEquals(TestEnum.SECOND, ConfigUtils.getEnum(config, TestEnum.class, TestEnum.FIRST, "some-enum")); + } + + @Test + void testGetEnum_StringConversion() { + config.put("some-enum", "SECOND"); + assertEquals(TestEnum.SECOND, ConfigUtils.getEnum(config, TestEnum.class, TestEnum.FIRST, "some-enum")); + } + + @Test + void testGetEnum_StringInvalidValue() { + config.put("some-enum", "second"); + assertThrows( + IllegalArgumentException.class, + () -> ConfigUtils.getEnum(config, TestEnum.class, TestEnum.FIRST, "some-enum")); + } } From 08109e9a144dbcb1ccf47f58f2679ec352dd0447 Mon Sep 17 00:00:00 2001 From: Konrad Windszus Date: Mon, 6 Jul 2026 18:44:27 +0200 Subject: [PATCH 2/8] Temporarily force usage of Java 26 for resolver-transport-jdk11 --- .github/workflows/maven-verify.yml | 2 +- .../internal/test/util/http/HttpServer.java | 6 +++++- .../maven-resolver-transport-jdk11/pom.xml | 16 +++++++++++++++- .../aether/transport/jdk/JdkTransporter.java | 18 ++++++++++++++++-- .../transport/jdk/JdkTransporterTest.java | 2 ++ maven-resolver-transport-jetty/pom.xml | 19 +++++++++++++++++++ .../transport/jetty/JettyTransporterTest.java | 3 ++- 7 files changed, 60 insertions(+), 6 deletions(-) diff --git a/.github/workflows/maven-verify.yml b/.github/workflows/maven-verify.yml index 112a68ee43..7efea17bb3 100644 --- a/.github/workflows/maven-verify.yml +++ b/.github/workflows/maven-verify.yml @@ -30,4 +30,4 @@ jobs: ff-site-run: false maven4-enabled: true verify-fail-fast: false - jdk-matrix: '[ "21", "25" ]' + jdk-matrix: '[ "26" ]' diff --git a/maven-resolver-test-http/src/main/java/org/eclipse/aether/internal/test/util/http/HttpServer.java b/maven-resolver-test-http/src/main/java/org/eclipse/aether/internal/test/util/http/HttpServer.java index 85584865d2..bf04c419d8 100644 --- a/maven-resolver-test-http/src/main/java/org/eclipse/aether/internal/test/util/http/HttpServer.java +++ b/maven-resolver-test-http/src/main/java/org/eclipse/aether/internal/test/util/http/HttpServer.java @@ -537,7 +537,11 @@ public boolean handle(Request req, Response response, Callback callback) throws Map requestHeaders = toUnmodifiableMap(req.getHeaders()); // capture request headers before other handlers modify them - LogEntry logEntry = new LogEntry(req.getConnectionMetaData().getHttpVersion(), req.getMethod(), req.getHttpURI().getPathQuery(), requestHeaders); + LogEntry logEntry = new LogEntry( + req.getConnectionMetaData().getHttpVersion(), + req.getMethod(), + req.getHttpURI().getPathQuery(), + requestHeaders); logEntries.add(logEntry); // prevent closing the response before logging (assume all writes are synchronous for simplicity) boolean result = super.handle(req, response, callback); diff --git a/maven-resolver-transport-jdk-parent/maven-resolver-transport-jdk11/pom.xml b/maven-resolver-transport-jdk-parent/maven-resolver-transport-jdk11/pom.xml index 2a9369034c..1ff1e2ed12 100644 --- a/maven-resolver-transport-jdk-parent/maven-resolver-transport-jdk11/pom.xml +++ b/maven-resolver-transport-jdk-parent/maven-resolver-transport-jdk11/pom.xml @@ -33,7 +33,7 @@ Maven Artifact Transport JDK HTTP Client. - 11 + 26 @@ -74,6 +74,12 @@ slf4j-simple test + + org.slf4j + jul-to-slf4j + ${slf4jVersion} + test + org.apache.maven.resolver maven-resolver-test-util @@ -93,6 +99,14 @@ + + org.apache.maven.plugins + maven-surefire-plugin + + JdkTransporterTest#testGet_HTTP3 + -Djdk.internal.httpclient.debug=true + + org.eclipse.sisu sisu-maven-plugin diff --git a/maven-resolver-transport-jdk-parent/maven-resolver-transport-jdk11/src/main/java/org/eclipse/aether/transport/jdk/JdkTransporter.java b/maven-resolver-transport-jdk-parent/maven-resolver-transport-jdk11/src/main/java/org/eclipse/aether/transport/jdk/JdkTransporter.java index 14b2ce99f5..2fc5c508e7 100644 --- a/maven-resolver-transport-jdk-parent/maven-resolver-transport-jdk11/src/main/java/org/eclipse/aether/transport/jdk/JdkTransporter.java +++ b/maven-resolver-transport-jdk-parent/maven-resolver-transport-jdk11/src/main/java/org/eclipse/aether/transport/jdk/JdkTransporter.java @@ -44,6 +44,8 @@ import java.net.URISyntaxException; import java.net.UnknownHostException; import java.net.http.HttpClient; +import java.net.http.HttpOption; +import java.net.http.HttpOption.Http3DiscoveryMode; import java.net.http.HttpRequest; import java.net.http.HttpResponse; import java.nio.file.Files; @@ -443,6 +445,8 @@ private void prepare(HttpRequest.Builder requestBuilder) { getBasicAuthValue(proxyAuthentication.getUserName(), proxyAuthentication.getPassword())); } } + requestBuilder.setOption(HttpOption.H3_DISCOVERY, Http3DiscoveryMode.HTTP_3_URI_ONLY); + // requestBuilder.requestTimeout(Duration.ofMillis(requestTimeout)) } static String getBasicAuthValue(String username, char[] password) { @@ -575,6 +579,16 @@ public X509Certificate[] getAcceptedIssuers() { } } + HttpClient.Builder builder = HttpClient.newBuilder() + .version(getHttpVersion(session, repository)) + .followRedirects(HttpClient.Redirect.NORMAL) + .connectTimeout(Duration.ofMillis(connectTimeout)) + // this only considers the time until the response header is received, see + // https://bugs.openjdk.org/browse/JDK-8208693 + // but better than nothing + // .requestTimeout(Duration.ofMillis(requestTimeout)) + .sslContext(sslContext); + /* Methanol.Builder builder = Methanol.newBuilder() .version(getHttpVersion(session, repository)) .followRedirects(HttpClient.Redirect.NORMAL) @@ -583,7 +597,7 @@ public X509Certificate[] getAcceptedIssuers() { // https://bugs.openjdk.org/browse/JDK-8208693 // but better than nothing .requestTimeout(Duration.ofMillis(requestTimeout)) - .sslContext(sslContext); + .sslContext(sslContext);*/ if (insecure) { SSLParameters sslParameters = sslContext.getDefaultSSLParameters(); @@ -623,7 +637,7 @@ protected PasswordAuthentication getPasswordAuthentication() { }); } - configureRetryHandler(session, repository, builder); + // configureRetryHandler(session, repository, builder); return builder.build(); } diff --git a/maven-resolver-transport-jdk-parent/maven-resolver-transport-jdk11/src/test/java/org/eclipse/aether/transport/jdk/JdkTransporterTest.java b/maven-resolver-transport-jdk-parent/maven-resolver-transport-jdk11/src/test/java/org/eclipse/aether/transport/jdk/JdkTransporterTest.java index 87810bd200..1f12b27279 100644 --- a/maven-resolver-transport-jdk-parent/maven-resolver-transport-jdk11/src/test/java/org/eclipse/aether/transport/jdk/JdkTransporterTest.java +++ b/maven-resolver-transport-jdk-parent/maven-resolver-transport-jdk11/src/test/java/org/eclipse/aether/transport/jdk/JdkTransporterTest.java @@ -106,6 +106,8 @@ protected void testRetryHandler_defaultCount_negative() throws Exception { @EnabledForJreRange(min = JRE.JAVA_26) @Test protected void testGet_HTTP3() throws Exception { + // System.setProperty("jdk.httpclient.HttpClient.log", "ssl"); + // System.setProperty("jdk.internal.httpclient.debug", "out"); super.testGet_HTTP3(); } diff --git a/maven-resolver-transport-jetty/pom.xml b/maven-resolver-transport-jetty/pom.xml index 1e78e32ed6..d3011dd60c 100644 --- a/maven-resolver-transport-jetty/pom.xml +++ b/maven-resolver-transport-jetty/pom.xml @@ -122,6 +122,25 @@ + + org.apache.maven.plugins + maven-enforcer-plugin + + + enforce-bytecode-version + + + + + + org.eclipse.jetty.quic:jetty-quic-quiche-foreign + + + + + + + org.eclipse.sisu sisu-maven-plugin diff --git a/maven-resolver-transport-jetty/src/test/java/org/eclipse/aether/transport/jetty/JettyTransporterTest.java b/maven-resolver-transport-jetty/src/test/java/org/eclipse/aether/transport/jetty/JettyTransporterTest.java index bcecdc8f0f..74cd2e132c 100644 --- a/maven-resolver-transport-jetty/src/test/java/org/eclipse/aether/transport/jetty/JettyTransporterTest.java +++ b/maven-resolver-transport-jetty/src/test/java/org/eclipse/aether/transport/jetty/JettyTransporterTest.java @@ -79,7 +79,8 @@ protected void testPut_Authenticated_ExpectContinueRejected_ExplicitlyConfigured @Override @Test protected void testGet_HTTP3() throws Exception { - // Jetty's HTTP/3 support is based on Quiche which does not consider the default SSL context (https://github.com/jetty/jetty.project/issues/15370) + // Jetty's HTTP/3 support is based on Quiche which does not consider the default SSL context + // (https://github.com/jetty/jetty.project/issues/15370) session.setConfigProperty( ConfigurationProperties.HTTPS_SECURITY_MODE, ConfigurationProperties.HTTPS_SECURITY_MODE_INSECURE); super.testGet_HTTP3(); From 86e2598b9a43c470275aec7e38524cb76d703e1b Mon Sep 17 00:00:00 2001 From: Konrad Windszus Date: Thu, 9 Jul 2026 13:36:58 +0200 Subject: [PATCH 3/8] Fix Jetty HTTP/3 dependencies Clean up and regenerate key stores Cleanup server method names --- .../http/RFC9457/RFC9457Reporter.java | 7 ++ maven-resolver-test-http/pom.xml | 5 ++ .../internal/test/util/http/HttpServer.java | 44 ++++++------ .../test/util/http/HttpTransporterTest.java | 67 ++++++++---------- .../src/main/resources/ssl/README.txt | 8 ++- .../src/main/resources/ssl/client-store | Bin 2244 -> 3612 bytes .../src/main/resources/ssl/server-store | Bin 2246 -> 3612 bytes .../resources/ssl/server-store-selfsigned | Bin 2750 -> 0 bytes .../apache/ApacheTransporterTest.java | 4 +- .../maven-resolver-transport-jdk11/pom.xml | 10 +-- .../aether/transport/jdk/JdkTransporter.java | 44 ++++++------ .../transport/jdk/JdkTransporterTest.java | 5 -- 12 files changed, 90 insertions(+), 104 deletions(-) delete mode 100644 maven-resolver-test-http/src/main/resources/ssl/server-store-selfsigned diff --git a/maven-resolver-spi/src/main/java/org/eclipse/aether/spi/connector/transport/http/RFC9457/RFC9457Reporter.java b/maven-resolver-spi/src/main/java/org/eclipse/aether/spi/connector/transport/http/RFC9457/RFC9457Reporter.java index 7fb51a945d..784076e07e 100644 --- a/maven-resolver-spi/src/main/java/org/eclipse/aether/spi/connector/transport/http/RFC9457/RFC9457Reporter.java +++ b/maven-resolver-spi/src/main/java/org/eclipse/aether/spi/connector/transport/http/RFC9457/RFC9457Reporter.java @@ -19,6 +19,7 @@ package org.eclipse.aether.spi.connector.transport.http.RFC9457; import java.io.IOException; +import java.io.UncheckedIOException; /** * A reporter for RFC 9457 messages. @@ -92,6 +93,12 @@ public void generateException(T response, BiConsumerChecked throw new HttpRFC9457Exception(statusCode, reasonPhrase, rfc9457Payload); } throw new HttpRFC9457Exception(statusCode, reasonPhrase, RFC9457Payload.INSTANCE); + } else { + try { + System.err.println("Received " + statusCode + " with body :" + getBody(response)); + } catch (IOException e) { + throw new UncheckedIOException("Failed to read response body", e); + } } baseException.accept(statusCode, reasonPhrase); } diff --git a/maven-resolver-test-http/pom.xml b/maven-resolver-test-http/pom.xml index 6bafd11ba2..9521f6c748 100644 --- a/maven-resolver-test-http/pom.xml +++ b/maven-resolver-test-http/pom.xml @@ -85,6 +85,11 @@ org.eclipse.jetty.quic jetty-quic-quiche-server + + + org.eclipse.jetty.quic + jetty-quic-quiche-jna + org.eclipse.jetty.compression jetty-compression-server diff --git a/maven-resolver-test-http/src/main/java/org/eclipse/aether/internal/test/util/http/HttpServer.java b/maven-resolver-test-http/src/main/java/org/eclipse/aether/internal/test/util/http/HttpServer.java index bf04c419d8..44fd023400 100644 --- a/maven-resolver-test-http/src/main/java/org/eclipse/aether/internal/test/util/http/HttpServer.java +++ b/maven-resolver-test-http/src/main/java/org/eclipse/aether/internal/test/util/http/HttpServer.java @@ -171,6 +171,8 @@ public enum ChecksumHeader { private ServerConnector httpsConnector; + private QuicheServerConnector http3Connector; + private String username; private String password; @@ -215,21 +217,21 @@ public String getHttp3Url() { return "https://" + getHost() + ":" + getHttp3Port(); } - public HttpServer addSslConnector() { - return addSslConnector(true, true); + public HttpServer addHttp2ConnectorWithMutualTLS() { + return addHttp2Connector(true, true); } - public HttpServer addSelfSignedSslConnector() { - return addSslConnector(false, true); + public HttpServer addHttp2OnlyConnectorWithMutualTLS() { + return addHttp2Connector(false, true); } - public HttpServer addSelfSignedSslConnectorHttp2Only() { - return addSslConnector(false, false); + public HttpServer addHttp2OnlyConnector() { + return addHttp2Connector(false, false); } - private HttpServer addSslConnector(boolean needClientAuth, boolean needHttp11) { + private HttpServer addHttp2Connector(boolean needClientAuth, boolean needHttp11) { if (httpsConnector == null) { - SslContextFactory.Server ssl = createSslContextFactory(needClientAuth); + SslContextFactory.Server ssl = createServerSslContextFactory(needClientAuth); HttpConfiguration httpsConfig = new HttpConfiguration(); SecureRequestCustomizer customizer = new SecureRequestCustomizer(); @@ -262,23 +264,17 @@ private HttpServer addSslConnector(boolean needClientAuth, boolean needHttp11) { return this; } - private SslContextFactory.Server createSslContextFactory(boolean needClientAuth) { + private SslContextFactory.Server createServerSslContextFactory(boolean needClientAuth) { SslContextFactory.Server ssl = new SslContextFactory.Server(); + ssl.setSniRequired(false); + ssl.setKeyStorePath( + HttpTransporterTest.SERVER_STORE_PATH.toAbsolutePath().toString()); + ssl.setKeyStorePassword("server-pwd"); ssl.setNeedClientAuth(needClientAuth); - if (!needClientAuth) { - ssl.setKeyStorePath(HttpTransporterTest.KEY_STORE_SELF_SIGNED_PATH - .toAbsolutePath() - .toString()); - ssl.setKeyStorePassword("server-pwd"); - ssl.setSniRequired(false); - } else { - ssl.setKeyStorePath( - HttpTransporterTest.KEY_STORE_PATH.toAbsolutePath().toString()); - ssl.setKeyStorePassword("server-pwd"); + if (needClientAuth) { ssl.setTrustStorePath( - HttpTransporterTest.TRUST_STORE_PATH.toAbsolutePath().toString()); + HttpTransporterTest.CLIENT_STORE_PATH.toAbsolutePath().toString()); ssl.setTrustStorePassword("client-pwd"); - ssl.setSniRequired(false); } return ssl; } @@ -289,7 +285,7 @@ public HttpServer addHttp3Connector(boolean needClientAuth) { new QuicheServerQuicConfiguration(HttpTransporterTest.PEM_QUICHE_SERVER_PATH)); http3Connector = new QuicheServerConnector( server, - createSslContextFactory(needClientAuth), + createServerSslContextFactory(needClientAuth), serverQuicConfig, new HTTP3ServerConnectionFactory()); // TODO: should share same port as https connector, so that the client can use the same port for both @@ -367,6 +363,7 @@ public HttpServer start() throws Exception { server = new Server(); httpConnector = new ServerConnector(server); + // always add the HTTP 1.1 connector server.addConnector(httpConnector); server.setHandler(new LogHandler(new CompressionEnforcingHandler(new Handler.Sequence( @@ -565,8 +562,6 @@ Map toUnmodifiableMap(HttpFields headers) { private static final Pattern SIMPLE_RANGE = Pattern.compile("bytes=([0-9])+-"); - private QuicheServerConnector http3Connector; - private class RepoHandler extends Handler.Abstract { @Override public boolean handle(Request req, Response response, Callback callback) throws Exception { @@ -654,6 +649,7 @@ public boolean handle(Request req, Response response, Callback callback) throws Content.copy(req, Content.Sink.from(channel), fileWriteCallback); fileWriteCallback.block(); } catch (IOException e) { + LOGGER.warn("Failed to write file {}", file.getAbsolutePath(), e); file.delete(); throw e; } diff --git a/maven-resolver-test-http/src/main/java/org/eclipse/aether/internal/test/util/http/HttpTransporterTest.java b/maven-resolver-test-http/src/main/java/org/eclipse/aether/internal/test/util/http/HttpTransporterTest.java index 3f4ec970b2..2175736b2e 100644 --- a/maven-resolver-test-http/src/main/java/org/eclipse/aether/internal/test/util/http/HttpTransporterTest.java +++ b/maven-resolver-test-http/src/main/java/org/eclipse/aether/internal/test/util/http/HttpTransporterTest.java @@ -94,11 +94,9 @@ @SuppressWarnings({"checkstyle:MethodName"}) public abstract class HttpTransporterTest { - protected static final Path KEY_STORE_PATH = Paths.get("target/keystore"); + protected static final Path SERVER_STORE_PATH = Paths.get("target/server-store"); - protected static final Path KEY_STORE_SELF_SIGNED_PATH = Paths.get("target/keystore-self-signed"); - - protected static final Path TRUST_STORE_PATH = Paths.get("target/trustStore"); + protected static final Path CLIENT_STORE_PATH = Paths.get("target/client-store"); protected static final Path PEM_QUICHE_SERVER_PATH = Paths.get("target/pems"); @@ -115,18 +113,10 @@ protected static void beforeAll() throws NoSuchAlgorithmException { try { try (InputStream keyStoreStream = HttpTransporterTest.class.getClassLoader().getResourceAsStream("ssl/server-store"); - InputStream keyStoreSelfSignedStream = HttpTransporterTest.class - .getClassLoader() - .getResourceAsStream("ssl/server-store-selfsigned"); InputStream trustStoreStream = - HttpTransporterTest.class.getClassLoader().getResourceAsStream("ssl/client-store"); - InputStream serverPemStream = - HttpTransporterTest.class.getClassLoader().getResourceAsStream("ssl/server.pem"); - InputStream serverSelfsignedPemStream = - HttpTransporterTest.class.getClassLoader().getResourceAsStream("ssl/selfsigned.pem")) { - Files.copy(keyStoreStream, KEY_STORE_PATH, StandardCopyOption.REPLACE_EXISTING); - Files.copy(keyStoreSelfSignedStream, KEY_STORE_SELF_SIGNED_PATH, StandardCopyOption.REPLACE_EXISTING); - Files.copy(trustStoreStream, TRUST_STORE_PATH, StandardCopyOption.REPLACE_EXISTING); + HttpTransporterTest.class.getClassLoader().getResourceAsStream("ssl/client-store"); ) { + Files.copy(keyStoreStream, SERVER_STORE_PATH, StandardCopyOption.REPLACE_EXISTING); + Files.copy(trustStoreStream, CLIENT_STORE_PATH, StandardCopyOption.REPLACE_EXISTING); Files.createDirectories(PEM_QUICHE_SERVER_PATH); } } catch (IOException e) { @@ -135,7 +125,7 @@ protected static void beforeAll() throws NoSuchAlgorithmException { // override default SSLContext to include our custom keystore and truststore (which are "cross connected" with // HttpServer) defaultSslContext = SSLContext.getDefault(); - SSLContext.setDefault(createSSLContext()); + SSLContext.setDefault(createClientSSLContext()); } @AfterAll @@ -146,23 +136,23 @@ protected static void afterAll() { } /** - * Creates an {@link SSLContext} that extends the default keystore and truststore with the entries - * from {@link #KEY_STORE_PATH} (password {@code "server-pwd"}) and {@link #TRUST_STORE_PATH} + * Creates an {@link SSLContext} for the client that extends the default keystore and truststore with the entries + * from {@link #SERVER_STORE_PATH} (password {@code "server-pwd"}) and {@link #CLIENT_STORE_PATH} * (password {@code "client-pwd"}). * * @return an {@link SSLContext} combining default and custom key/trust material */ - protected static SSLContext createSSLContext() { + protected static SSLContext createClientSSLContext() { try { // Load custom key store (KEY_STORE_PATH acts as truststore in "cross connected" setup) - KeyStore customTrustStore = KeyStore.getInstance("jks"); - try (InputStream is = Files.newInputStream(KEY_STORE_PATH)) { + KeyStore customTrustStore = KeyStore.getInstance("pkcs12"); + try (InputStream is = Files.newInputStream(SERVER_STORE_PATH)) { customTrustStore.load(is, "server-pwd".toCharArray()); } // Load custom trust store (TRUST_STORE_PATH acts as keystore in "cross connected" setup) - KeyStore customKeyStore = KeyStore.getInstance("jks"); - try (InputStream is = Files.newInputStream(TRUST_STORE_PATH)) { + KeyStore customKeyStore = KeyStore.getInstance("pkcs12"); + try (InputStream is = Files.newInputStream(CLIENT_STORE_PATH)) { customKeyStore.load(is, "client-pwd".toCharArray()); } @@ -291,6 +281,7 @@ protected void setUp(TestInfo testInfo) throws Exception { TestFileUtils.writeString(resumable, "resumable"); resumable.setLastModified(System.currentTimeMillis() - 90 * 1000); httpServer = new HttpServer().setRepoDir(repoDir).start(); + // always create a transporter connecting to the Http URL newTransporter(httpServer.getHttpUrl()); } @@ -489,14 +480,14 @@ protected void testPeek_ProxyUnauthenticated() throws Exception { @Test protected void testPeek_SSL() throws Exception { - httpServer.addSslConnector(); + httpServer.addHttp2ConnectorWithMutualTLS(); newTransporter(httpServer.getHttpsUrl()); transporter.peek(new PeekTask(URI.create("repo/file.txt"))); } @Test protected void testPeek_Redirect() throws Exception { - httpServer.addSslConnector(); + httpServer.addHttp2ConnectorWithMutualTLS(); transporter.peek(new PeekTask(URI.create("redirect/file.txt"))); transporter.peek(new PeekTask(URI.create("redirect/file.txt?scheme=https"))); } @@ -743,7 +734,7 @@ protected void testGet_RFC9457Response_with_missing_fields() throws Exception { @Test protected void testGet_SSL() throws Exception { - httpServer.addSslConnector(); + httpServer.addHttp2ConnectorWithMutualTLS(); newTransporter(httpServer.getHttpsUrl()); RecordingTransportListener listener = new RecordingTransportListener(); GetTask task = new GetTask(URI.create("repo/file.txt")).setListener(listener); @@ -759,7 +750,7 @@ protected void testGet_SSL() throws Exception { @Test protected void testGet_SSL_WithServerErrors() throws Exception { httpServer.setServerErrorsBeforeWorks(1); - httpServer.addSslConnector(); + httpServer.addHttp2ConnectorWithMutualTLS(); newTransporter(httpServer.getHttpsUrl()); for (int i = 1; i < 3; i++) { try { @@ -781,7 +772,7 @@ protected void testGet_SSL_WithServerErrors() throws Exception { @Test protected void testGet_HTTPS_Unknown_SecurityMode() throws Exception { session.setConfigProperty(ConfigurationProperties.HTTPS_SECURITY_MODE, "unknown"); - httpServer.addSelfSignedSslConnector(); + httpServer.addHttp2OnlyConnectorWithMutualTLS(); try { newTransporter(httpServer.getHttpsUrl()); fail("Unsupported security mode"); @@ -792,11 +783,12 @@ protected void testGet_HTTPS_Unknown_SecurityMode() throws Exception { @Test protected void testGet_HTTPS_Insecure_SecurityMode() throws Exception { - // here we use alternate server-store-selfigned key (as the key set it static initializer is probably already - // used to init SSLContext/SSLSocketFactory/etc + // we have to reset the default ssl context to avoid the default truststore being used, which would make the + // test pass even if the security mode is not set to insecure + SSLContext.setDefault(defaultSslContext); session.setConfigProperty( ConfigurationProperties.HTTPS_SECURITY_MODE, ConfigurationProperties.HTTPS_SECURITY_MODE_INSECURE); - httpServer.addSelfSignedSslConnector(); + httpServer.addHttp2OnlyConnectorWithMutualTLS(); newTransporter(httpServer.getHttpsUrl()); RecordingTransportListener listener = new RecordingTransportListener(); GetTask task = new GetTask(URI.create("repo/file.txt")).setListener(listener); @@ -807,14 +799,16 @@ protected void testGet_HTTPS_Insecure_SecurityMode() throws Exception { assertEquals(1, listener.getStartedCount()); assertTrue(listener.getProgressedCount() > 0, "Count: " + listener.getProgressedCount()); assertEquals(task.getDataString(), listener.getBaos().toString(StandardCharsets.UTF_8)); + // restore the default SSL context used for all other tests + SSLContext.setDefault(createClientSSLContext()); } @Test protected void testGet_HTTPS_HTTP2Only_Insecure_SecurityMode() throws Exception { - enableHttp2Protocol(); + session.setConfigProperty(ConfigurationProperties.HTTP_VERSION, ConfigurationProperties.HttpVersion.HTTP_2); session.setConfigProperty( ConfigurationProperties.HTTPS_SECURITY_MODE, ConfigurationProperties.HTTPS_SECURITY_MODE_INSECURE); - httpServer.addSelfSignedSslConnectorHttp2Only(); + httpServer.addHttp2OnlyConnector(); newTransporter(httpServer.getHttpsUrl()); RecordingTransportListener listener = new RecordingTransportListener(); GetTask task = new GetTask(URI.create("repo/file.txt")).setListener(listener); @@ -830,11 +824,8 @@ protected void testGet_HTTPS_HTTP2Only_Insecure_SecurityMode() throws Exception }); } - protected void enableHttp2Protocol() {} - @Test protected void testGet_HTTP3() throws Exception { - // self-signed cert should be accepted through default SSL context session.setConfigProperty(ConfigurationProperties.HTTP_VERSION, ConfigurationProperties.HttpVersion.HTTP_3); httpServer.addHttp3Connector(false); httpServer.start(); @@ -855,7 +846,7 @@ protected void testGet_HTTP3() throws Exception { @Test protected void testGet_Redirect() throws Exception { - httpServer.addSslConnector(); + httpServer.addHttp2ConnectorWithMutualTLS(); RecordingTransportListener listener = new RecordingTransportListener(); GetTask task = new GetTask(URI.create("redirect/file.txt?scheme=https")).setListener(listener); transporter.get(task); @@ -1243,7 +1234,7 @@ protected void testPut_ProxyUnauthenticated() throws Exception { @Test protected void testPut_SSL() throws Exception { - httpServer.addSslConnector(); + httpServer.addHttp2ConnectorWithMutualTLS(); httpServer.setAuthentication("testuser", "testpass"); auth = new AuthenticationBuilder() .addUsername("testuser") diff --git a/maven-resolver-test-http/src/main/resources/ssl/README.txt b/maven-resolver-test-http/src/main/resources/ssl/README.txt index b1be71c2c7..d136fa4ac6 100644 --- a/maven-resolver-test-http/src/main/resources/ssl/README.txt +++ b/maven-resolver-test-http/src/main/resources/ssl/README.txt @@ -1,5 +1,9 @@ client-store generated via -> keytool -genkey -alias localhost -keypass client-pwd -keystore client-store -storepass client-pwd -validity 4096 -dname "cn=localhost, ou=None, L=Seattle, ST=Washington, o=ExampleOrg, c=US" -keyalg RSA +> keytool -genkey -alias localhost-client -keypass client-pwd -keystore client-store -storepass client-pwd -validity 4096 -dname "cn=localhost, ou=None, L=Seattle, ST=Washington, o=ExampleOrg, c=US" -keyalg RSA server-store generated via -> keytool -genkey -alias localhost -keypass server-pwd -keystore server-store -storepass server-pwd -validity 4096 -dname "cn=localhost, ou=None, L=Seattle, ST=Washington, o=ExampleOrg, c=US" -keyalg RSA +> keytool -genkey -alias localhost-server -keypass server-pwd -keystore server-store -storepass server-pwd -validity 4096 -dname "cn=localhost, ou=None, L=Seattle, ST=Washington, o=ExampleOrg, c=US" -keyalg RSA + +The server key has to have name "localhost" otherwise the hostname matching will fail (as the server is running on localhost as well). +Both certificates are self-signed i.e. have themselves as issuer and contain both public and private keys! +Although the latter is not necessary for trust stores it won't do any harm here. diff --git a/maven-resolver-test-http/src/main/resources/ssl/client-store b/maven-resolver-test-http/src/main/resources/ssl/client-store index fbfb39d829027267354266182f36cb8d3a2d9900..f8fe6bd7658d00077f5fdc04e980d6198394d9e6 100644 GIT binary patch literal 3612 zcma)9WmFX0)}9$+9Hc|K6%=8Zkr+x!7+Mez0qL%xONJM4K)ONc?vySmK@dT@V<_nu zkp=-T-}>%)-*tc8`{S&Ao@bwDul;L3`@k@ybRZxe3`5dI2;m7<3_c?Pf`Qo>l3IKW z$;Vr|42HP_`i~U&2pA^()fyA)K z_|zpmTpJu%y{Y1EGL~|2^Za{ZPYOXGZgLQi4Mt3O=l?5$AOrvyGaqn}k;j_bA&@&!tL)uYln|4^chV}_6F-|ODAbqrp##lH5%WttvTxs&T*g_i!Hn?N$OHaX}YSXa+DLWuLjBt+K zt2k?H#OOtznaxdI&fS`dW^B)vAkF;N6=fn1k#+;3P4~;bwrqy*^z8Uxr(So&2S%1T zP?JBrStr3TP} z#?CQ+>Q;FilJ~3AlI}LtgZmbeXH2#o@+9?)dOaF}E<`azk-Xj^Va4|b6FNfCRXvdY zt)QDs8{SmID9LrsFK>NdY)fr!vVU>k1I^AcTejm#uOvHJYKBp0;!oj&H(BUgG-(~+ zK615RxEr2DNOuhnigauo1&8;qo;8>2Lg$cnYy7@GF^1w5DaeC?#-$f%x^@rCw{nQh z$eQ(xr(KN?TCUN8+8W8);AyhIxbNMea z6q_?|1k@A9M2J|bHAB*jvgzf5|Lpsdvfio7r(KL)U})XShE*tks2-S;QoXl-0&8_f zf0xJ)EN+;a+CY?CZY?~i>%Q;lF*c=dzPd@G7 zC~vTCgUIO$O8!;{y|Y`|H>t4KUBhI`iAq$_dmHagFngbRV+ic)5G8?8A4ND%tz$SuI_TH=|tObOMUBmBKs1 zi&V-k!X8wb$x1b>zF}g2nl3+OsM;xcX2~!+A3mwzB+qO?zW)rW`8LWX;XB;VQo3(n zYhxzR)%qNFVlMt9f41?kC8<}g$%JW79lU(cx+(_!QJ{jyHz|`L`4}%wV`YS9h)MM(o)qPhsWLryVRPg;;pJZX zXU~A~(bv;IJ7q0;v%Tc)(%f+@*cTRA%E~YwIG;5?W{qd;>lHiM<5E17Hq{~S5gjL z^Evs@S~jf08#yKQL@u0e*Ow!9)$J1`fWW!-=!ob1YAG;m4SQ&cK+esq+FCPhWHNw1 z)MNVJC6+9jn{M8e#rv`wnw#lZMP*HNW;dII2}EaQ>EPKcl|nLsuTFY6r#NxN*5NM6`yTn4UOkAIm7EvCexuxxIHI@GU3qD_Ynrk_ z15`YDPSuU9`qfNN(=6ZFCXj6I3|y6K0L%N>tg9>2c~h=-$W2Iwi3r{mSP z!pT2DR3DReH`vRHHhKca&*}JCEa)$z34-!-XUQ1XakkMHeZ?9H+l=Nr_`^o>0nA+t zlD};Eomp%eZV=9&ANQ{}b@T|$s$m{^Ba@b!k|s$(moui6z4S(a0Yw~NX|`FFC| zN!rw=c|7=FVcNB0`L?V@~-IefSAM>*v!hmxFqKnSMT5}&Xt^$PO4yo zbd^w%ToPNE!_RPS82{gP3!#Q{u&@Cf08RjNfaz_txxKjo+yQ*I*a2V*umm^)++pnh z2x#Ec_>|hVC`&dtLPS7F1THKfC?E=lVF+^megr1W#t_8cQqdqF;5LN(lL7uW>u=ei zLi6ZPqsP|)1bmEQ1p%zoQ~!(gx3{<3QjC8k$L+6GqqOcz24D!tKNJm_pUO<-AqxZ^ zI4j-dc+3fCdu5Rmhn-fAQOwT04{fy(P7@q`CZ{lfprgwfFjxqOkQ>>Faiq$(%)z@% zqV|o8{8X|Hf4+TKsO#H#k-As#&__P|bOdZ-GShJ9b;F_4zRJ}P9&Jbm(b))`FHBA+ zTEBJFyO0FG*mA{O`CeaC&bg=O?<#}h<-*n2>Uc8F7}5;( zi$UlTU!--)svbvBVHmHD=p%72*YM7Bez$&J>$8JDR4oASuO1~siHI9$;njFxZWCF% z-{ejKu5Tq5v&6#qnERxaU6=PIfDv8E(YdL2Nw!{YD?}gpWokxGrFq`uXt^pC+6G;? zwz-ZBVcZxN)7i~>4=Ibvm~+6c9G-f4e!rPh1PWZYR`jtSX68%%w4YgfH1<7EBLSR} zYI4G$_lEc_&zX3+6zPpEu+2-{aEH)0s#-^WvRgdd^ir!7T8aH|@jC;G7H#_#3h?eD z)D~z5MXFuqTs%}>q{*h266<$_rgw5dxfkaHgdhu@xxZ2A5-8&29}m)M{j<~AEgLY% z#w2XSgg%CpdoJ+3U$0v^Q}o!mM_ zLeB-%TGBy;8i~&uZNRUiFa*K=&5}o)m}x^2j7AJci@q>6=bBF{T1$Fo%Jq{=kOtem zl-=6Zv(smrFl9p9LSz`b$%6T4qdsnwio0C^wFgFz6MFOFVBHe}dv_YmT1~|YhNQTr zr+bZrwkL_AIMR{Et|N>ND;D|I`SV-B>BK1|{wod=w3H0sr%*h(>L&5RKmlaQWkVNj zS9mFNTX>_3n^M-0@$Nlx;d8R)ODFWcd_@t)fo;J{DRI=Z;>W~jOuk=_2n(40%TLivlfRNWzYZ^t zaU@h!v7kS*C!FM_amnU3|y`-Y&#s%68J^?jlhZM-WxAGc6LZGGiT< z>5q=6QRK}~iS+}0g>nm$65Z&WfgSc|0*%9|8cv4|Unv=Bw9rtO`>EFqcfvK}q#lV7 z_MN(|`5aN;@&a+QuPVFsQEB#xL#uohH9<<2+RPvM2oam17@3NBp0JL16QXxioa%>Y?-H9D=X~*{c1A); ziiT812p!9D6k$kGws3T=@4LS97koe5Kiv2A{P0}&b6@wfzp}pqgTcT<0RMm^C>^>=L%flD zNme_@O0`#}WVtPzVVM7M)8dApOF2T?Ho4TL)$1#mUE>4}V+03xviPp>(#{k?e!Qz| zcIN|%3sZ7G{Mg4i(c)_^YX8~8@9=Bpb@F7w#ao6fXP)9sgFnx8^FHFp*x)P4=S z-1Yu}&xh;*a^i-HnvxqM%+pD=*{^?NXqyt7>L^o1WU#@Ynq#*n?m}(P3>}9hlPAmVJ8>bHc@-J8?bBE1d?& ziR>D?%T8V1kBF3;qF%RYY)BAZ=*XX?d3umDcD*^wZC&q66BTKRh(+T~PAUp6?iYhX z%Mk<=6jmB}4Ve~U5%1HPP9ku6UhPoKpivN!DAY9>$1$TT`olROQ2AQSCTix?A3&^y zDmuPuR!lf$F^^ZKjG9}&v{XeO(qIPKEX?L<;Hrv?Z?<03ZC-0bMP!k5b-$adKCOVp5jYTGd#ogbt0`>~;>g0Rk_{e9wC$1<< zo1d+E^n&1a+{Tz|Ye&M}>|s`tO-x^RCj8dER}z+O8)<&Tb|HMNO$N81>A&-=8#%># zo(DWK+}26S;dlHs&T?#x%0D4c82xoHb3gWBioC>BpyE;CDm#aRNrcXah{CLG+$cX7l~g#DY9$NrubiBGg9>g;Bg ze|Oo1yNc1+I7Qzy*=vb@7pejOIPQ9sCMs{$+4fvHlbsD=xq25L3gu7JQi6|v8n;V|RAYETn)t@umi zj9}G!vK8j!mRjA-+m+;()HVcSerID!J2<1+Ck5nmILc_dS3B{u&EV^ZYP`KpW{xO6U~BJne8cHwr2`}3o{GeFd^hW>&Bz{c({q&rzKXO-a{za{o;UZtk7=b>ps zOj#MKW=`G=HCeHEY_aV!>B+ewUw-F#eU|dmeet$Vist^l$Fz9bCWCrHll2?)w~#Ka zdshsYwcM{>Ohfb&wWOsbO!rVZuKc6)Sqs;U`@Qk+u+eo6q2vU^2(;{QoF z1vxFEalxTM1p6@Je?@jd&U3hM{{Rv(B7~&QcUZHBlWc-)js#ppL=Zt8332^YxCG&+ zLr4U50f_G>WfA;eQ$tcBNOes}TSHG>TT??5<0gXCz(9KHTK}Q{=WQr}?7!_ed=)Gd zfENZ)08Wqs05DpzG(#(0?z;H{tML0F^X&tiP+;H)V}m96i*K~is_ZfWk(fGB`eW{$ z&?~t(srWA>nI<(9yhJajpuhTI3sP9;2Bf*awOAv{ z8gqi}K^6c8l*>RzA<4r_O7g&k;ix$spSI*34}`T&{>oU^a&U9W@7@14@i0?|CIT>C zSrxRO&1A~GXWbE^3I+_V@%+N#BaRje(;I+~kz?cg0L>`NWuco!`=0xEr-(slkBa&p zt=71JVg7V5&(V1-4vz`%korZjT%L=#qz zaD&7zJ9%oKvGnTQN{*&ps;xrWh)M<3B$4%oW>8^FsQi>7vDW7iGM*)S`SOE^kx#=~ z7vc*1&P%bMqu$c8m4s`@ZIZ&sJ_B>QF}2Lf8gl;n6w+);zFUUhWW!)i;`ygF9`NUCH`Qt1IQpniXo)=!06-m?ctsQ=uwmY#O6#)eLH@ zQI(=*TQyqi-{<o(p~tMC53N@W%gH1lnEbQ97giDppFv=xsy}^AsWMxu1S+>3fHs zONBlm@&Y_*i8$SdzWDEBvL}m-b}5veo$Ti;N4vA1$h7T?o$yS1zSh?_cEr6Qa95zV zAjjL|JclX+oE(>L@gbkXSAti3Y)tam=tz|N+#?tt6eUHY96GRC+0{6v5HXw`aErq{ z6lNy(-QWXWS>-g%ZCNx3T0JD}+@Xo-yzmH8eZf!eh)kF_hff&{bJll|M= z1Efha^%;_p+C2d%b@Dj%mrN%ZxGEW7Wt(M>_a7UZDQ9EQpYOE2Mx9J@XVJxdq~=%I z^bFT(iy)N1JL30&+QBD}6R-=M65k)w?Gk+E1D!K`u$8q%iDz&An)I)tA3JH!IJfN0 z_x2tg8*JW>asf$Y`2ernU()_c3>7O5EOT5WKe(TWhsxXWQ9R_XkUlM`ZQ8jM6HL6U8CY-U0$aweNh1s!4^kO1?^mqBL zD2A)7o0_{Cr#aDf7j;#>{prcoYHfCE5$1#-$j@gdx3u9|1-T=7P_Xp3APhFu3n2;{ zKTZ7BebOhv=YuNpM)#5UP2_Oyq`C$D z@xK%bwL(b9<}3695JpDr3e)Zmq^aW%Uh7LZB5`$!#o&kZ&}?GU@o}#hz@~9?%`acHXQTb#hU^&}^nN;f%(ndu5BrfEjfh`JYukGyMC;h zi@lM$AA_Z6=L*bycU_F}5IUW&4f3^H2t?AJ#o}V?s;QO98IeuBqjySB(`s$=>BswkVv7||H?X4w|fmnH~VbP%M z7}sMV6mi5Kl7B z@y~jDx8{39qOxxw-rwxwFO7Ou7p_@r#jKC4lJ;zT5F@G?q4!EaqEzm)7g&!$Z>}yj#8a2KOlofn*5F9xD zRoiK=q&wzTI^2DirHmcdZ!12>u!h^}Kb zDe5svr7c2>zuD&KxIGGspNP>AcZE+NY%yOcv|DN{FC1f^DU{;QFDBLV&hG~k0sPP6 z5EgNQ*V!j*cCjZ8PQVA;2mnUyIp;yf3Ut;eTa3Po!0^~HwB7aB@!(K%Mxv0nL$+GO z_Ga514$}l<*($PdYuw~D*@1UqF6o%U_d=S3}u8Y|qqsggdoK!DqY#i>k0~ znRwdCuAbpMb@`}vS%=*8+^aTU2YN|VF%f2?R|Xmh zcidou^gLw;@EXJd{iwgLuvX>=Fn>W6=ZWt#3xcATLupBm&u0;%Gz=-0AzAZw{j68R zM`xU??)SFxn>M;c`mX6y&cd!O)brf10SN3el7)e;_Of2>9b-|E&Q3FXd*G56IW~_L{&nbGp_>Wc7b#_k)Vf;QrJ}>ovyVOWTQ)MWchQ3^+p<$3y7XV*z}h+(nH)7Fzb8;0 zo1Rkmu|VYa&g38@`Osv|-A&@s{t6`_BhQ2}S0+y%s>mERg>FVvjME7P{RuQL9qvk$i@}?dDa#HuIK&tNZ^){6avzp!_ znO6M}uJ!Vmiiv>rD0sZo((U!;3w^lpyd(rwRsP;fIwZdYsU1&k(ICu+T`M_zoVI}^ zLvxCEYteTI^u$Fq(Z%k1;1J)YROUr+ex zXq4U}d4s0Ah*=q735haQKxqX{i21g*Me5Wt%`tEWB_B>lU(f*4A6>9kvT^0u7?KEb z+RkA4Wn2>JLOaB-ee%r_zqD#rzAyumK2%bzMNM85yKZ$kD+Y-7?7`F@oM(!3Kd~*B zxyY@lc*a@9hJ05oqRK*;2{m`Kmq_VM;D@A)PJLQG-^ad@ROaW4zIk>+_j_WNTGB#Z ztL=qyjuQ3=&j&ewq-aKCFqW`y*HnZk8z<0l>QJ*X`Q-mTvdkfGJ&7prf9A+^;7e5N zFQ*Q<@$(R@pz>)jfyJ5*=!p~RkhnDaD>OCtikgpGfqhweY@O?<({j*2?kJVUUKnvg ztC%7&IOtT~PE+d%39~x0;Mc9^yd4d?E4gb)!7-l~eLhYQauAQUJHp40X^KhjS8_79 z0i5Ys-VwR$FFHkASgIQbEf31d!$yi3CXxfgXkgIn%he@@pZuKZr~bg%2%N8R?1fsAN@<0A9QzRT;{$@Y{e@-#w(DN(*NSbBlKc7o@GsF^bnT zEH?c;CCzZ<@LsK6DfOC|`f7^o^nTZeN6iSW4|)l_GVfNOQsyCY;wU-sms(KTW@f+x;T$e*Zf!I}@u%6`oUG5SjM~77#kNd3g#QY=@iLvebinM*rh=8U zI)j+4{r-F{&tW!aCnx8_UtiFeH)P&eSR|9wDggw{OyNOH#i($-9vvBeG*o{)WzM@o zFYCl`RBSs@8qD>2N*XjW5G4{AWoyjcI5$^vJ(B!+856=>eB;`Q8keqY|cJeZE?fWSEJEkN{j3P^gCS%{Tge(yW zQDKa%O+_;HWe~c0pL?JC{ss4k^TRpM=ZDX8KIb`~qvfMz5C{Z52Jko7{Q}+H{Ja7~ zamNap_$D_31Y!n|1jr$hiv=pr0zd#cFgE}&gCGRR;@;ObpU*yq1wUC6#1<9fC-eG&70ljXevqq!XOq93D3r*si8lFcLaTN@heE(=kVz z4tatZ(o=@Fy*Wr+hj#}DOeWT8_g$yI!p?kEIHYQ0gn(E3S~XAddZx*ylW#duGz~#t zy1wsSk#!R@>JEdsk4R-4ow9G+7Dq|M4q?*03Pxv4`D!u1`OnTab?M6X=648R1RPjS zhI!Xv%z%8apM5=vX=po%lPBhw|EB$%lF8OU^!cq(1opoqh7SIjn*4~oOvRc3OlNi0-dsYkUu+e zo}Xer9A(Z{Z#BQL_|&Ct++x{Z-+-}Bc0)9-&8wQ4zFZ4o#T;bF*!B)IzHp$u?=~D4n@?3MKl`ZF)1TTbbASC z{h?xK+p&}~9>(T5-I8t$q_nQF?2~oHV`|%(l1dMo7S43I zE6H8Q&Ez`b_Qqm5f95B*AD~7f6e?S`#NR$zXp@qoN-O(p<@Ql#OAl9iF+}%%9|a2r z-}chH*v}}hz0qA|-{P2&&}=o9g+IY=&oM0-5dSJ;}~cS$t66j%D)$8(ZiL)h_aLn&{wbNlH}d z>4}2afe*C%Ck(i*br|=-Q4TqtEhx!`bRTUcYIV^{@EI|sqH{a4f3fnXC6+M1;vaK2 zQe^~Hv!_{0PgY026+q15BH!!Z$6eJy=q5^)OIsqoV`YS#^<8;dl^tSzJxvYEPB9 zBWnPu-VFah!s$bPsF#SHU0@u>%3BzE%j}Cl+j3%Lr3oMD+kI8V1$@PoWqjp9NULa{ z@@<)I!#$Fou)f;6H@Os1#IIDKBSGTOL&yZ$lj4>4m*k z8d&e4V7TBnX_YG3YFZ&Z7eySRIW{aG(8mm)ERHx?r!oq4Q|s>OArd*3e{|djU5qs* zD|#k``kGGno-DroUFYUE*6Ab_wI~Y|I!^;0l-1RtGk^7<^5FvP@Iy>~>d^!~khP}( z;dKd8eRH@u2o$SmWAUXw<8I_zaUCvPxM-usI5{h`nrItTGI|XwhSQKyJz(RdJJ-+M zb`jl{S|m!e4hEOkT@?)H>xERlUfIkpe3w%YJ!P`LjFy%T_A5$c7I9sX>W7y>RKnq+ zQ#|dlAJ>Ispqt-Wb;O+qD%UlN?Dd`TWDYlK41Y8wJ3MC7T8jJcZe+GsI)~H*ftaF@ z1g4uv0{G7YCNKa7LmarT|3Y%Iu*=;sy2IcE02ng}>4$Z&Sg8jb>-Dr!hg)VcrA|MNBkK>VL}96t*t z0ssdg2>=vK002-qI@J7@I%>sl@p`?Nj%WLuAl8aqL0K08pjgkPD(=gMGj$J7y~%g7 z^Eu@FirU3^$)tetKLRLv%W-F&OxK&xakOFEu2LF0u7i0WyIp9|?TV|ac{x#R8ca_f zdpmt4C|&!J(f(aB8jm7+Plw`>YrS=YV?)yk0UF7Q@Rn!1PKW$m}a08Y`|lhb!3}i&I$|thfTCYCwyrgR;B}gS3Z6w>Vm_Kf`#W_-SI5 z6-P26g27Ax2zVxjJdG4NzN82jgdf7oy~|G-ZbDw@y0*(tp2{g9QZ(iNY2tCFj!gtW zh5V63a%d`#5uR&7p8(+@|ReCiD#@h4@67&ud_bA^okqp zT4iqU2Ipx|KRVM~CMQz&kiGBh(4tR;PFcrrl-UyIS2!my)Z>;CZ#GdOqFl*W&z`fP zTk8kwsy)1153hJGA+?pZN>Thy52+ct^GKm366<|^Ub*)}WVq_IJgK%h>~bY`DBh*t UZcvDb^+zsewcjB-7S6@}8xH*M+yDRo diff --git a/maven-resolver-test-http/src/main/resources/ssl/server-store-selfsigned b/maven-resolver-test-http/src/main/resources/ssl/server-store-selfsigned deleted file mode 100644 index caf6e52befe7d7638a11618ff1eaa116abe5eadf..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2750 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 WQ}C=`N@Ba@aGZzEIJ)Da@HXL diff --git a/maven-resolver-transport-apache/src/test/java/org/eclipse/aether/transport/apache/ApacheTransporterTest.java b/maven-resolver-transport-apache/src/test/java/org/eclipse/aether/transport/apache/ApacheTransporterTest.java index d081687737..e122385712 100644 --- a/maven-resolver-transport-apache/src/test/java/org/eclipse/aether/transport/apache/ApacheTransporterTest.java +++ b/maven-resolver-transport-apache/src/test/java/org/eclipse/aether/transport/apache/ApacheTransporterTest.java @@ -106,7 +106,7 @@ void testPut_WebDav() throws Exception { @Test void testConnectionReuse() throws Exception { - httpServer.addSslConnector(); + httpServer.addHttp2ConnectorWithMutualTLS(); session.setCache(new DefaultRepositoryCache()); for (int i = 0; i < 3; i++) { newTransporter(httpServer.getHttpsUrl()); @@ -122,7 +122,7 @@ void testConnectionReuse() throws Exception { @Test void testConnectionNoReuse() throws Exception { - httpServer.addSslConnector(); + httpServer.addHttp2ConnectorWithMutualTLS(); session.setCache(new DefaultRepositoryCache()); session.setConfigProperty(ConfigurationProperties.HTTP_REUSE_CONNECTIONS, false); for (int i = 0; i < 3; i++) { diff --git a/maven-resolver-transport-jdk-parent/maven-resolver-transport-jdk11/pom.xml b/maven-resolver-transport-jdk-parent/maven-resolver-transport-jdk11/pom.xml index 1ff1e2ed12..12e3cc8dcb 100644 --- a/maven-resolver-transport-jdk-parent/maven-resolver-transport-jdk11/pom.xml +++ b/maven-resolver-transport-jdk-parent/maven-resolver-transport-jdk11/pom.xml @@ -33,7 +33,7 @@ Maven Artifact Transport JDK HTTP Client. - 26 + 11 @@ -99,14 +99,6 @@ - - org.apache.maven.plugins - maven-surefire-plugin - - JdkTransporterTest#testGet_HTTP3 - -Djdk.internal.httpclient.debug=true - - org.eclipse.sisu sisu-maven-plugin diff --git a/maven-resolver-transport-jdk-parent/maven-resolver-transport-jdk11/src/main/java/org/eclipse/aether/transport/jdk/JdkTransporter.java b/maven-resolver-transport-jdk-parent/maven-resolver-transport-jdk11/src/main/java/org/eclipse/aether/transport/jdk/JdkTransporter.java index 2fc5c508e7..75c197df19 100644 --- a/maven-resolver-transport-jdk-parent/maven-resolver-transport-jdk11/src/main/java/org/eclipse/aether/transport/jdk/JdkTransporter.java +++ b/maven-resolver-transport-jdk-parent/maven-resolver-transport-jdk11/src/main/java/org/eclipse/aether/transport/jdk/JdkTransporter.java @@ -44,8 +44,7 @@ import java.net.URISyntaxException; import java.net.UnknownHostException; import java.net.http.HttpClient; -import java.net.http.HttpOption; -import java.net.http.HttpOption.Http3DiscoveryMode; +import java.net.http.HttpClient.Version; import java.net.http.HttpRequest; import java.net.http.HttpResponse; import java.nio.file.Files; @@ -445,8 +444,6 @@ private void prepare(HttpRequest.Builder requestBuilder) { getBasicAuthValue(proxyAuthentication.getUserName(), proxyAuthentication.getPassword())); } } - requestBuilder.setOption(HttpOption.H3_DISCOVERY, Http3DiscoveryMode.HTTP_3_URI_ONLY); - // requestBuilder.requestTimeout(Duration.ofMillis(requestTimeout)) } static String getBasicAuthValue(String username, char[] password) { @@ -500,18 +497,16 @@ HttpClient.Version getHttpVersion(RepositorySystemSession session, RemoteReposit } private HttpClient.Version resolveHttpVersion(String requestedVersion) { - HttpClient.Version maximumHttpVersion = getMaximumSupportedHttpVersion(); - HttpClient.Version resolved; try { - resolved = HttpClient.Version.valueOf(requestedVersion); + return HttpClient.Version.valueOf(requestedVersion); } catch (IllegalArgumentException e) { + HttpClient.Version maximumHttpVersion = getMaximumSupportedHttpVersion(); LOGGER.warn( "HTTP version '{}' is not supported by the running JRE, using '{}' instead", requestedVersion, maximumHttpVersion); return maximumHttpVersion; } - return resolved; } HttpClient.Version getMaximumSupportedHttpVersion() { @@ -535,13 +530,19 @@ private HttpClient createClient(RepositorySystemSession session, RemoteRepositor } } + Version httpVersion = getHttpVersion(session, repository); if (sslContext == null) { try { if (insecure) { + if (httpVersion.name().equals("HTTP_3")) { + // custom trust manager not supported for HTTP/3 (Quic) + // (https://github.com/openjdk/jdk/blob/631b675d7949a0e6312d8d6f45e2515d53b12f05/src/java.base/share/classes/sun/security/ssl/SSLContextImpl.java#L529) + // https://openjdk.org/jeps/517 + // https://mail.openjdk.org/archives/list/net-dev@openjdk.org/thread/LHSC7MWRFDJE2KGS2QMPFJPZX3XEQKOQ/ + throw new IllegalStateException( + "Insecure HTTPS connections are not supported for HTTP/3 (Quic)"); + } sslContext = SSLContext.getInstance("TLS"); - // custom trust manager not supported for HTTP/3 (Quic) - // (https://github.com/openjdk/jdk/blob/631b675d7949a0e6312d8d6f45e2515d53b12f05/src/java.base/share/classes/sun/security/ssl/SSLContextImpl.java#L529) - // https://openjdk.org/jeps/517 X509ExtendedTrustManager tm = new X509ExtendedTrustManager() { @Override public void checkClientTrusted(X509Certificate[] chain, String authType) {} @@ -577,27 +578,22 @@ public X509Certificate[] getAcceptedIssuers() { throw new IllegalStateException("SSL Context setup failure", e); } } + } else { + if (insecure) { + throw new IllegalStateException( + "Insecure HTTPS connections are not supported when a custom SSLContext is configured"); + } } - HttpClient.Builder builder = HttpClient.newBuilder() - .version(getHttpVersion(session, repository)) - .followRedirects(HttpClient.Redirect.NORMAL) - .connectTimeout(Duration.ofMillis(connectTimeout)) - // this only considers the time until the response header is received, see - // https://bugs.openjdk.org/browse/JDK-8208693 - // but better than nothing - // .requestTimeout(Duration.ofMillis(requestTimeout)) - .sslContext(sslContext); - /* Methanol.Builder builder = Methanol.newBuilder() - .version(getHttpVersion(session, repository)) + .version(httpVersion) .followRedirects(HttpClient.Redirect.NORMAL) .connectTimeout(Duration.ofMillis(connectTimeout)) // this only considers the time until the response header is received, see // https://bugs.openjdk.org/browse/JDK-8208693 // but better than nothing .requestTimeout(Duration.ofMillis(requestTimeout)) - .sslContext(sslContext);*/ + .sslContext(sslContext); if (insecure) { SSLParameters sslParameters = sslContext.getDefaultSSLParameters(); @@ -637,7 +633,7 @@ protected PasswordAuthentication getPasswordAuthentication() { }); } - // configureRetryHandler(session, repository, builder); + configureRetryHandler(session, repository, builder); return builder.build(); } diff --git a/maven-resolver-transport-jdk-parent/maven-resolver-transport-jdk11/src/test/java/org/eclipse/aether/transport/jdk/JdkTransporterTest.java b/maven-resolver-transport-jdk-parent/maven-resolver-transport-jdk11/src/test/java/org/eclipse/aether/transport/jdk/JdkTransporterTest.java index 1f12b27279..f79445831a 100644 --- a/maven-resolver-transport-jdk-parent/maven-resolver-transport-jdk11/src/test/java/org/eclipse/aether/transport/jdk/JdkTransporterTest.java +++ b/maven-resolver-transport-jdk-parent/maven-resolver-transport-jdk11/src/test/java/org/eclipse/aether/transport/jdk/JdkTransporterTest.java @@ -115,11 +115,6 @@ public JdkTransporterTest() { super(() -> new JdkTransporterFactory(standardChecksumExtractor(), new DefaultPathProcessor())); } - @Override - protected void enableHttp2Protocol() { - session.setConfigProperty(JdkTransporterConfigurationKeys.CONFIG_PROP_HTTP_VERSION, "HTTP_2"); - } - @Test void enhanceConnectExceptionMessages() { String uri = "https://localhost:12345/"; From 9766ddf4d1b45a3b359c3f421b118cbdddba2a77 Mon Sep 17 00:00:00 2001 From: Konrad Windszus Date: Thu, 9 Jul 2026 13:55:00 +0200 Subject: [PATCH 4/8] Disable testRetryHandler_defaultCount_negative on Java26+ --- .../transport/apache/ApacheRFC9457Reporter.java | 3 +++ .../transport/apache/ApacheTransporterTest.java | 8 +++++++- .../aether/transport/jdk/JdkTransporterTest.java | 10 ++++++---- src/site/markdown/transporter-known-issues.md | 14 ++++++++++++-- 4 files changed, 28 insertions(+), 7 deletions(-) diff --git a/maven-resolver-transport-apache/src/main/java/org/eclipse/aether/transport/apache/ApacheRFC9457Reporter.java b/maven-resolver-transport-apache/src/main/java/org/eclipse/aether/transport/apache/ApacheRFC9457Reporter.java index c9ff111683..41f43701bc 100644 --- a/maven-resolver-transport-apache/src/main/java/org/eclipse/aether/transport/apache/ApacheRFC9457Reporter.java +++ b/maven-resolver-transport-apache/src/main/java/org/eclipse/aether/transport/apache/ApacheRFC9457Reporter.java @@ -67,6 +67,9 @@ protected String getReasonPhrase(final CloseableHttpResponse response) { @Override protected String getBody(final CloseableHttpResponse response) throws IOException { + if (response.getEntity() == null) { + return ""; + } return EntityUtils.toString(response.getEntity(), StandardCharsets.UTF_8); } } diff --git a/maven-resolver-transport-apache/src/test/java/org/eclipse/aether/transport/apache/ApacheTransporterTest.java b/maven-resolver-transport-apache/src/test/java/org/eclipse/aether/transport/apache/ApacheTransporterTest.java index e122385712..eca051c3af 100644 --- a/maven-resolver-transport-apache/src/test/java/org/eclipse/aether/transport/apache/ApacheTransporterTest.java +++ b/maven-resolver-transport-apache/src/test/java/org/eclipse/aether/transport/apache/ApacheTransporterTest.java @@ -36,7 +36,8 @@ import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Test; -import static org.junit.jupiter.api.Assertions.*; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; /** * Apache Transporter UT. @@ -58,6 +59,11 @@ protected Stream supportedCompressionAlgorithms() { @Test protected void testGet_HTTPS_HTTP2Only_Insecure_SecurityMode() throws Exception {} + @Override + @Disabled + @Test + protected void testGet_HTTP3() {} + @Test void testGet_WebDav() throws Exception { httpServer.setWebDav(true); diff --git a/maven-resolver-transport-jdk-parent/maven-resolver-transport-jdk11/src/test/java/org/eclipse/aether/transport/jdk/JdkTransporterTest.java b/maven-resolver-transport-jdk-parent/maven-resolver-transport-jdk11/src/test/java/org/eclipse/aether/transport/jdk/JdkTransporterTest.java index f79445831a..11c20facd6 100644 --- a/maven-resolver-transport-jdk-parent/maven-resolver-transport-jdk11/src/test/java/org/eclipse/aether/transport/jdk/JdkTransporterTest.java +++ b/maven-resolver-transport-jdk-parent/maven-resolver-transport-jdk11/src/test/java/org/eclipse/aether/transport/jdk/JdkTransporterTest.java @@ -87,12 +87,14 @@ protected void testPut_Authenticated_ExpectContinueRejected_ExplicitlyConfigured @Override @Test + @EnabledForJreRange(max = JRE.JAVA_25) protected void testRetryHandler_defaultCount_negative() throws Exception { // internally JDK client uses its own retry mechanism with - // 2 attempts for ConnectionExpiredException (prior Java 26) and 5 (default for - // system property "jdk.httpclient.redirects.retrylimit") attempts after, - // therefore explicitly limit the internal retry count to 1 - System.setProperty("jdk.httpclient.redirects.retrylimit", "1"); // this only affects Java 26+ + // 2 attempts for ConnectionExpiredException (prior Java 26) + // afterwards it uses 5 (default for + // system property "jdk.httpclient.redirects.retrylimit") attempts, + // limit the internal retry count to 1 has global effect and is too late here anyway therefore we skip this test + // for Java 26+. // Compare with https://github.com/mizosoft/methanol/issues/174 httpServer.setConnectionsToClose(8); try { diff --git a/src/site/markdown/transporter-known-issues.md b/src/site/markdown/transporter-known-issues.md index c83d6eaa1d..57fbdbf775 100644 --- a/src/site/markdown/transporter-known-issues.md +++ b/src/site/markdown/transporter-known-issues.md @@ -25,7 +25,7 @@ This page lists known issues related to various transporters. Given this transporter uses the Java HttpClient (available since Java 11), it is the user's best interest to use latest patch version of Java, as HttpClient is getting bugfixes regularly. -Known issues: +### Known issues: * Does not properly support `aether.transport.http.requestTimeout` configuration prior Java 26, see [JDK-8208693](https://bugs.openjdk.org/browse/JDK-8208693) * No TLS Proxy support, see [here](https://dev.to/kdrakon/httpclient-can-t-connect-to-a-tls-proxy-118a) * No SOCKS proxy support, see [JDK-8214516](https://bugs.openjdk.org/browse/JDK-8214516) @@ -34,15 +34,20 @@ Known issues: Basic authentication disabled, see [here](https://www.oracle.com/java/technologies/javase/8u111-relnotes.html). To enable HTTP Basic authentication for Proxy TLS tunneling, one must set `jdk.http.auth.tunneling.disabledScheme` to empty string, e.g. by adding `-Djdk.http.auth.tunneling.disabledScheme=""` JVM argument. +* HTTP/3 is only supported with [Java 26 and above](https://inside.java/2025/10/22/http3-support/). Maven 4 uses this transport by default for HTTP(S) protocol. ## The `apache` (Apache HttpClient) Transporter -Transporter based on Apache HttpClient. +Transporter based on Apache HttpClient 4. To use this transporter in Maven 4, you need to specify `-Dmaven.resolver.transport=apache` user property. +### Known issues: + +* Does neither support HTTP/2 nor HTTP/3 + ## The `jetty` (Jetty HttpClient) Transporter Transporter based on Jetty HttpClient. @@ -50,3 +55,8 @@ Transporter based on Jetty HttpClient. In Maven 4 this transport is not available by default (is not bundled). To use it, you need to add `org.apache.maven.resolver.transport:transport-http-jetty` artifact with its runtime dependencies to `/lib` directory of Maven. Once added to core classpath, it will take over the role of default transport. + +### Known issues: + +* Leveraging HTTP/3 with Jetty suffers from [poor performance due to usage of the native Quiche Rust library] +(https://github.com/jetty/jetty.project/discussions/13469#discussioncomment-14125855). From cf2b43a244fd3b0884cf04bf1f05dc0d667f23cb Mon Sep 17 00:00:00 2001 From: Konrad Windszus Date: Thu, 9 Jul 2026 16:37:22 +0200 Subject: [PATCH 5/8] Reenable all JDKs --- .github/workflows/maven-verify.yml | 2 +- .../maven-resolver-demo-snippets/pom.xml | 25 ++++++++++++++++++- .../http/RFC9457/RFC9457Reporter.java | 7 ------ maven-resolver-tools/pom.xml | 19 ++++++++++++++ src/site/markdown/transporter-known-issues.md | 3 ++- 5 files changed, 46 insertions(+), 10 deletions(-) diff --git a/.github/workflows/maven-verify.yml b/.github/workflows/maven-verify.yml index 7efea17bb3..f9b104c56b 100644 --- a/.github/workflows/maven-verify.yml +++ b/.github/workflows/maven-verify.yml @@ -30,4 +30,4 @@ jobs: ff-site-run: false maven4-enabled: true verify-fail-fast: false - jdk-matrix: '[ "26" ]' + jdk-matrix: '[ "21", "25", "26" ]' diff --git a/maven-resolver-demos/maven-resolver-demo-snippets/pom.xml b/maven-resolver-demos/maven-resolver-demo-snippets/pom.xml index 2da27f51ab..d77ae4ebad 100644 --- a/maven-resolver-demos/maven-resolver-demo-snippets/pom.xml +++ b/maven-resolver-demos/maven-resolver-demo-snippets/pom.xml @@ -53,7 +53,6 @@ - org.apache.maven.resolver @@ -147,4 +146,28 @@ test + + + + + org.apache.maven.plugins + maven-enforcer-plugin + + + enforce-bytecode-version + + + + + + org.eclipse.jetty.quic:jetty-quic-quiche-foreign + + + + + + + + + diff --git a/maven-resolver-spi/src/main/java/org/eclipse/aether/spi/connector/transport/http/RFC9457/RFC9457Reporter.java b/maven-resolver-spi/src/main/java/org/eclipse/aether/spi/connector/transport/http/RFC9457/RFC9457Reporter.java index 784076e07e..7fb51a945d 100644 --- a/maven-resolver-spi/src/main/java/org/eclipse/aether/spi/connector/transport/http/RFC9457/RFC9457Reporter.java +++ b/maven-resolver-spi/src/main/java/org/eclipse/aether/spi/connector/transport/http/RFC9457/RFC9457Reporter.java @@ -19,7 +19,6 @@ package org.eclipse.aether.spi.connector.transport.http.RFC9457; import java.io.IOException; -import java.io.UncheckedIOException; /** * A reporter for RFC 9457 messages. @@ -93,12 +92,6 @@ public void generateException(T response, BiConsumerChecked throw new HttpRFC9457Exception(statusCode, reasonPhrase, rfc9457Payload); } throw new HttpRFC9457Exception(statusCode, reasonPhrase, RFC9457Payload.INSTANCE); - } else { - try { - System.err.println("Received " + statusCode + " with body :" + getBody(response)); - } catch (IOException e) { - throw new UncheckedIOException("Failed to read response body", e); - } } baseException.accept(statusCode, reasonPhrase); } diff --git a/maven-resolver-tools/pom.xml b/maven-resolver-tools/pom.xml index 677b73a59b..d1ebde2bbc 100644 --- a/maven-resolver-tools/pom.xml +++ b/maven-resolver-tools/pom.xml @@ -170,6 +170,25 @@ + + org.apache.maven.plugins + maven-enforcer-plugin + + + enforce-bytecode-version + + + + + + org.eclipse.jetty.quic:jetty-quic-quiche-foreign + + + + + + + org.codehaus.mojo exec-maven-plugin diff --git a/src/site/markdown/transporter-known-issues.md b/src/site/markdown/transporter-known-issues.md index 57fbdbf775..09b4bc1dba 100644 --- a/src/site/markdown/transporter-known-issues.md +++ b/src/site/markdown/transporter-known-issues.md @@ -26,6 +26,7 @@ Given this transporter uses the Java HttpClient (available since Java 11), it is to use latest patch version of Java, as HttpClient is getting bugfixes regularly. ### Known issues: + * Does not properly support `aether.transport.http.requestTimeout` configuration prior Java 26, see [JDK-8208693](https://bugs.openjdk.org/browse/JDK-8208693) * No TLS Proxy support, see [here](https://dev.to/kdrakon/httpclient-can-t-connect-to-a-tls-proxy-118a) * No SOCKS proxy support, see [JDK-8214516](https://bugs.openjdk.org/browse/JDK-8214516) @@ -50,7 +51,7 @@ To use this transporter in Maven 4, you need to specify `-Dmaven.resolver.transp ## The `jetty` (Jetty HttpClient) Transporter -Transporter based on Jetty HttpClient. +Transporter based on Jetty HttpClient. It requires Java 17 or above. In Maven 4 this transport is not available by default (is not bundled). To use it, you need to add `org.apache.maven.resolver.transport:transport-http-jetty` artifact with its runtime dependencies to From c5e71feebe214e2780946f78d3e4c6bf5f4fcfe1 Mon Sep 17 00:00:00 2001 From: Konrad Windszus Date: Fri, 10 Jul 2026 17:57:27 +0200 Subject: [PATCH 6/8] Don't combine mutual TLS with basic auth as leading to concurrency errors with Jetty --- .../aether/internal/test/util/http/HttpTransporterTest.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/maven-resolver-test-http/src/main/java/org/eclipse/aether/internal/test/util/http/HttpTransporterTest.java b/maven-resolver-test-http/src/main/java/org/eclipse/aether/internal/test/util/http/HttpTransporterTest.java index 2175736b2e..7587c5e653 100644 --- a/maven-resolver-test-http/src/main/java/org/eclipse/aether/internal/test/util/http/HttpTransporterTest.java +++ b/maven-resolver-test-http/src/main/java/org/eclipse/aether/internal/test/util/http/HttpTransporterTest.java @@ -1235,11 +1235,11 @@ protected void testPut_ProxyUnauthenticated() throws Exception { @Test protected void testPut_SSL() throws Exception { httpServer.addHttp2ConnectorWithMutualTLS(); - httpServer.setAuthentication("testuser", "testpass"); + /*httpServer.setAuthentication("testuser", "testpass"); auth = new AuthenticationBuilder() .addUsername("testuser") .addPassword("testpass") - .build(); + .build();*/ newTransporter(httpServer.getHttpsUrl()); RecordingTransportListener listener = new RecordingTransportListener(); PutTask task = @@ -1247,7 +1247,7 @@ protected void testPut_SSL() throws Exception { transporter.put(task); assertEquals(0L, listener.getDataOffset()); assertEquals(6L, listener.getDataLength()); - assertEquals(supportsPreemptiveAuth() ? 1 : 2, listener.getStartedCount()); + assertEquals(1, listener.getStartedCount()); assertTrue(listener.getProgressedCount() > 0, "Count: " + listener.getProgressedCount()); assertEquals("upload", TestFileUtils.readString(new File(repoDir, "file.txt"))); } From 82f74ae8a2830b939cc85cc8b7500e0b6b3ad7c2 Mon Sep 17 00:00:00 2001 From: Konrad Windszus Date: Fri, 10 Jul 2026 20:43:55 +0200 Subject: [PATCH 7/8] Explicitly depend on quiche-jna --- maven-resolver-transport-jetty/pom.xml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/maven-resolver-transport-jetty/pom.xml b/maven-resolver-transport-jetty/pom.xml index d3011dd60c..08e1cabe38 100644 --- a/maven-resolver-transport-jetty/pom.xml +++ b/maven-resolver-transport-jetty/pom.xml @@ -80,6 +80,10 @@ org.eclipse.jetty.quic jetty-quic-quiche-client + + org.eclipse.jetty.quic + jetty-quic-quiche-jna + org.eclipse.jetty.compression From 1c2c4634ce6a97d84391b5facb7274ce2abfaa3f Mon Sep 17 00:00:00 2001 From: Konrad Windszus Date: Sun, 12 Jul 2026 18:33:38 +0200 Subject: [PATCH 8/8] Restore original test Always consume request body to prevent connection resets --- .../eclipse/aether/internal/test/util/http/HttpServer.java | 3 +++ .../aether/internal/test/util/http/HttpTransporterTest.java | 6 +++--- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/maven-resolver-test-http/src/main/java/org/eclipse/aether/internal/test/util/http/HttpServer.java b/maven-resolver-test-http/src/main/java/org/eclipse/aether/internal/test/util/http/HttpServer.java index 44fd023400..6e5b3c86cd 100644 --- a/maven-resolver-test-http/src/main/java/org/eclipse/aether/internal/test/util/http/HttpServer.java +++ b/maven-resolver-test-http/src/main/java/org/eclipse/aether/internal/test/util/http/HttpServer.java @@ -545,6 +545,9 @@ public boolean handle(Request req, Response response, Callback callback) throws // capture response headers after other handlers modified them // at this point in time the connection may have been already closed (i.e. last chunk already sent) logEntry.setResponseHeaders(toUnmodifiableMap(response.getHeaders())); + // make sure to always consume the request body to avoid connection reset, even if the handler did not read + // it + Content.Source.consumeAll(req); if (result) { callback.succeeded(); } diff --git a/maven-resolver-test-http/src/main/java/org/eclipse/aether/internal/test/util/http/HttpTransporterTest.java b/maven-resolver-test-http/src/main/java/org/eclipse/aether/internal/test/util/http/HttpTransporterTest.java index 7587c5e653..2175736b2e 100644 --- a/maven-resolver-test-http/src/main/java/org/eclipse/aether/internal/test/util/http/HttpTransporterTest.java +++ b/maven-resolver-test-http/src/main/java/org/eclipse/aether/internal/test/util/http/HttpTransporterTest.java @@ -1235,11 +1235,11 @@ protected void testPut_ProxyUnauthenticated() throws Exception { @Test protected void testPut_SSL() throws Exception { httpServer.addHttp2ConnectorWithMutualTLS(); - /*httpServer.setAuthentication("testuser", "testpass"); + httpServer.setAuthentication("testuser", "testpass"); auth = new AuthenticationBuilder() .addUsername("testuser") .addPassword("testpass") - .build();*/ + .build(); newTransporter(httpServer.getHttpsUrl()); RecordingTransportListener listener = new RecordingTransportListener(); PutTask task = @@ -1247,7 +1247,7 @@ protected void testPut_SSL() throws Exception { transporter.put(task); assertEquals(0L, listener.getDataOffset()); assertEquals(6L, listener.getDataLength()); - assertEquals(1, listener.getStartedCount()); + assertEquals(supportsPreemptiveAuth() ? 1 : 2, listener.getStartedCount()); assertTrue(listener.getProgressedCount() > 0, "Count: " + listener.getProgressedCount()); assertEquals("upload", TestFileUtils.readString(new File(repoDir, "file.txt"))); }