This is an automated email from the ASF dual-hosted git repository. kwin pushed a commit to branch feature/enhanced-transport-listener in repository https://gitbox.apache.org/repos/asf/maven-resolver.git
commit 7fa703be78eafd0cbe86238113c72f74906f1ee3 Author: Konrad Windszus <[email protected]> AuthorDate: Mon Jan 26 19:47:18 2026 +0100 Expose additional transport details to TransportListener For HTTP Transporters this is: - HTTP Version - SSL Protocol (only HTTPS) - SSL Cipher Suite (only HTTPS) - Compression Algorithm (if used) - Transport Size (if it differs from data size) This closes #1761 --- .../connector/transport/AbstractTransporter.java | 62 +++++++++++++++++++++- .../spi/connector/transport/TransportListener.java | 30 +++++++++++ .../connector/transport/http/HttpTransporter.java | 32 ++++++++++- .../test/util/http/RecordingTransportListener.java | 6 ++- .../aether/transport/apache/ApacheTransporter.java | 17 +++++- .../aether/transport/jdk/JdkTransporter.java | 23 +++++++- .../aether/transport/jetty/JettyTransporter.java | 24 ++++++++- 7 files changed, 184 insertions(+), 10 deletions(-) diff --git a/maven-resolver-spi/src/main/java/org/eclipse/aether/spi/connector/transport/AbstractTransporter.java b/maven-resolver-spi/src/main/java/org/eclipse/aether/spi/connector/transport/AbstractTransporter.java index 7c4f680d6..2f665a257 100644 --- a/maven-resolver-spi/src/main/java/org/eclipse/aether/spi/connector/transport/AbstractTransporter.java +++ b/maven-resolver-spi/src/main/java/org/eclipse/aether/spi/connector/transport/AbstractTransporter.java @@ -22,6 +22,8 @@ import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.nio.ByteBuffer; +import java.util.Collections; +import java.util.Map; import java.util.concurrent.atomic.AtomicBoolean; import org.eclipse.aether.transfer.TransferCancelledException; @@ -87,11 +89,42 @@ public abstract class AbstractTransporter implements Transporter { * download starts at the first byte of the resource. * @throws IOException If the transfer encountered an I/O error. * @throws TransferCancelledException If the transfer was cancelled. + * @deprecated Use {@link #utilGet(GetTask, InputStream, boolean, long, boolean, Map)} instead. */ + @Deprecated protected void utilGet(GetTask task, InputStream is, boolean close, long length, boolean resume) throws IOException, TransferCancelledException { + utilGet(task, is, close, length, resume, Collections.emptyMap()); + } + + /** + * Performs stream-based I/O for the specified download task and notifies the configured transport listener. + * Subclasses might want to invoke this utility method from within their {@link #implGet(GetTask)} to avoid + * boilerplate I/O code. + * + * @param task The download to perform, must not be {@code null}. + * @param is The input stream to download the data from, must not be {@code null}. + * @param close {@code true} if the supplied input stream should be automatically closed, {@code false} to leave the + * stream open. + * @param length The size in bytes of the downloaded resource or {@code -1} if unknown, not to be confused with the + * length of the supplied input stream which might be smaller if the download is resumed. + * @param resume {@code true} if the download resumes from {@link GetTask#getResumeOffset()}, {@code false} if the + * download starts at the first byte of the resource. + * @param transportProperties the transport properties connected with this download. May be empty. + * @throws IOException If the transfer encountered an I/O error. + * @throws TransferCancelledException If the transfer was cancelled. + * @since NEXT + */ + protected void utilGet( + GetTask task, + InputStream is, + boolean close, + long length, + boolean resume, + Map<TransportListener.TransportPropertyKey, Object> transportProperties) + throws IOException, TransferCancelledException { try (OutputStream os = task.newOutputStream(resume)) { - task.getListener().transportStarted(resume ? task.getResumeOffset() : 0L, length); + task.getListener().transportStarted(resume ? task.getResumeOffset() : 0L, length, transportProperties); copy(os, is, task.getListener()); } finally { if (close) { @@ -126,11 +159,36 @@ public abstract class AbstractTransporter implements Transporter { * the stream open. * @throws IOException If the transfer encountered an I/O error. * @throws TransferCancelledException If the transfer was cancelled. + * @deprecated Use {@link #utilPut(PutTask, OutputStream, boolean, Map)} instead. */ + @Deprecated protected void utilPut(PutTask task, OutputStream os, boolean close) throws IOException, TransferCancelledException { + utilPut(task, os, close, Collections.emptyMap()); + } + + /** + * Performs stream-based I/O for the specified upload task and notifies the configured transport listener. + * Subclasses might want to invoke this utility method from within their {@link #implPut(PutTask)} to avoid + * boilerplate I/O code. + * + * @param task The upload to perform, must not be {@code null}. + * @param os The output stream to upload the data to, must not be {@code null}. + * @param close {@code true} if the supplied output stream should be automatically closed, {@code false} to leave + * the stream open. + * @param transportProperties the transport properties connected with this upload. May be empty. + * @throws IOException If the transfer encountered an I/O error. + * @throws TransferCancelledException If the transfer was cancelled. + * @since NEXT + */ + protected void utilPut( + PutTask task, + OutputStream os, + boolean close, + Map<TransportListener.TransportPropertyKey, Object> transportProperties) + throws IOException, TransferCancelledException { try (InputStream is = task.newInputStream()) { - task.getListener().transportStarted(0, task.getDataLength()); + task.getListener().transportStarted(0, task.getDataLength(), transportProperties); copy(os, is, task.getListener()); } finally { if (close) { diff --git a/maven-resolver-spi/src/main/java/org/eclipse/aether/spi/connector/transport/TransportListener.java b/maven-resolver-spi/src/main/java/org/eclipse/aether/spi/connector/transport/TransportListener.java index b6e60645a..e05acc840 100644 --- a/maven-resolver-spi/src/main/java/org/eclipse/aether/spi/connector/transport/TransportListener.java +++ b/maven-resolver-spi/src/main/java/org/eclipse/aether/spi/connector/transport/TransportListener.java @@ -19,6 +19,8 @@ package org.eclipse.aether.spi.connector.transport; import java.nio.ByteBuffer; +import java.util.Collection; +import java.util.Map; import org.eclipse.aether.transfer.TransferCancelledException; @@ -34,6 +36,8 @@ import org.eclipse.aether.transfer.TransferCancelledException; */ public abstract class TransportListener { + public interface TransportPropertyKey {} + /** * Enables subclassing. */ @@ -45,8 +49,26 @@ public abstract class TransportListener { * * @param dataOffset The byte offset in the resource at which the transfer starts, must not be negative. * @param dataLength The total number of bytes in the resource or {@code -1} if the length is unknown. + * @param transportProperties The transport properties associated with this transfer, may be empty. The keys are transporter specific and the value types are key specific. * @throws TransferCancelledException If the transfer should be aborted. + * @since NEXT */ + public void transportStarted( + long dataOffset, long dataLength, Map<TransportPropertyKey, Object> transportProperties) + throws TransferCancelledException { + transportStarted(dataOffset, dataLength); + } + + /** + * Notifies the listener about the start of the data transfer. This event may arise more than once if the transfer + * needs to be restarted (e.g. after an authentication failure). + * + * @param dataOffset The byte offset in the resource at which the transfer starts, must not be negative. + * @param dataLength The total number of bytes in the resource or {@code -1} if the length is unknown. + * @throws TransferCancelledException If the transfer should be aborted. + * @deprecated use {@link #transportStarted(long, long, Map)} instead + */ + @Deprecated public void transportStarted(long dataOffset, long dataLength) throws TransferCancelledException {} /** @@ -57,4 +79,12 @@ public abstract class TransportListener { * @throws TransferCancelledException If the transfer should be aborted. */ public void transportProgressed(ByteBuffer data) throws TransferCancelledException {} + + /** + * Notifies the listener about the completion of the data transfer. + * + * @param details The collection of detail strings about the transfer, may be empty but not {@code null}. + * @throws TransferCancelledException If the transfer should be aborted. + */ + public void transportCompleted(Collection<String> details) throws TransferCancelledException {} } diff --git a/maven-resolver-spi/src/main/java/org/eclipse/aether/spi/connector/transport/http/HttpTransporter.java b/maven-resolver-spi/src/main/java/org/eclipse/aether/spi/connector/transport/http/HttpTransporter.java index 954ce8891..5160087f7 100644 --- a/maven-resolver-spi/src/main/java/org/eclipse/aether/spi/connector/transport/http/HttpTransporter.java +++ b/maven-resolver-spi/src/main/java/org/eclipse/aether/spi/connector/transport/http/HttpTransporter.java @@ -18,6 +18,7 @@ */ package org.eclipse.aether.spi.connector.transport.http; +import org.eclipse.aether.spi.connector.transport.TransportListener; import org.eclipse.aether.spi.connector.transport.Transporter; /** @@ -25,4 +26,33 @@ import org.eclipse.aether.spi.connector.transport.Transporter; * * @since 2.0.0 */ -public interface HttpTransporter extends Transporter {} +public interface HttpTransporter extends Transporter { + + /** + * Transport property keys specific to HTTP transporters. + * @see org.eclipse.aether.spi.connector.transport.TransporterListener#transportStarted(long, long, java.util.Map) + */ + enum HttpTransportPropertyKey implements TransportListener.TransportPropertyKey { + /** + * Transport property key for HTTP version. Value is a String representing the HTTP version used (e.g., "HTTP/1.1", "HTTP/2"). + */ + HTTP_VERSION, + /** + * Transport property key for SSL protocol. Value is a String representing the SSL protocol used (e.g., "TLSv1.2", "TLSv1.3"). + */ + SSL_PROTOCOL, + /** + * Transport property key for SSL cipher suite. Value is a String representing the SSL cipher suite used (e.g., "TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256"). + */ + SSL_CIPHER_SUITE, + /** + * Transport property key for compression algorithm. Value is a String representing the compression algorithm used (e.g., "gzip", "br", or "zstd"). + */ + COMPRESSION_ALGORITHM, + /** + * Transport property key for number of bytes transferred. Value is a Long representing the total number of bytes transferred during the transport operation. + * This may be less than the content length in case of compression. + */ + NUM_BYTES_TRANSFERRED; + } +} diff --git a/maven-resolver-test-http/src/main/java/org/eclipse/aether/internal/test/util/http/RecordingTransportListener.java b/maven-resolver-test-http/src/main/java/org/eclipse/aether/internal/test/util/http/RecordingTransportListener.java index 7dac9b666..5d87f19c5 100644 --- a/maven-resolver-test-http/src/main/java/org/eclipse/aether/internal/test/util/http/RecordingTransportListener.java +++ b/maven-resolver-test-http/src/main/java/org/eclipse/aether/internal/test/util/http/RecordingTransportListener.java @@ -21,8 +21,10 @@ package org.eclipse.aether.internal.test.util.http; import java.io.ByteArrayOutputStream; import java.nio.Buffer; import java.nio.ByteBuffer; +import java.util.Map; import org.eclipse.aether.spi.connector.transport.TransportListener; +import org.eclipse.aether.spi.connector.transport.TransportListener.TransportPropertyKey; import org.eclipse.aether.transfer.TransferCancelledException; public class RecordingTransportListener extends TransportListener { @@ -42,7 +44,9 @@ public class RecordingTransportListener extends TransportListener { private boolean cancelProgress; @Override - public void transportStarted(long dataOffset, long dataLength) throws TransferCancelledException { + public void transportStarted( + long dataOffset, long dataLength, Map<TransportPropertyKey, String> transportProperties) + throws TransferCancelledException { startedCount++; progressedCount = 0; this.dataLength = dataLength; diff --git a/maven-resolver-transport-apache/src/main/java/org/eclipse/aether/transport/apache/ApacheTransporter.java b/maven-resolver-transport-apache/src/main/java/org/eclipse/aether/transport/apache/ApacheTransporter.java index 3084c2ad7..a49a7587d 100644 --- a/maven-resolver-transport-apache/src/main/java/org/eclipse/aether/transport/apache/ApacheTransporter.java +++ b/maven-resolver-transport-apache/src/main/java/org/eclipse/aether/transport/apache/ApacheTransporter.java @@ -33,6 +33,7 @@ import java.nio.file.Path; import java.nio.file.StandardCopyOption; import java.util.Collections; import java.util.Date; +import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; @@ -92,6 +93,7 @@ import org.eclipse.aether.spi.connector.transport.AbstractTransporter; import org.eclipse.aether.spi.connector.transport.GetTask; import org.eclipse.aether.spi.connector.transport.PeekTask; import org.eclipse.aether.spi.connector.transport.PutTask; +import org.eclipse.aether.spi.connector.transport.TransportListener; import org.eclipse.aether.spi.connector.transport.TransportTask; import org.eclipse.aether.spi.connector.transport.http.ChecksumExtractor; import org.eclipse.aether.spi.connector.transport.http.HttpTransporter; @@ -674,11 +676,13 @@ final class ApacheTransporter extends AbstractTransporter implements HttpTranspo } } + Map<TransportListener.TransportPropertyKey, String> transportProperties = + createTransportProperties(response); final boolean resume = offset > 0L; final Path dataFile = task.getDataPath(); if (dataFile == null) { try (InputStream is = entity.getContent()) { - utilGet(task, is, true, length, resume); + utilGet(task, is, true, length, resume, transportProperties); extractChecksums(response); } } else { @@ -690,7 +694,7 @@ final class ApacheTransporter extends AbstractTransporter implements HttpTranspo } } try (InputStream is = entity.getContent()) { - utilGet(task, is, true, length, resume); + utilGet(task, is, true, length, resume, transportProperties); } tempFile.move(); } finally { @@ -718,6 +722,15 @@ final class ApacheTransporter extends AbstractTransporter implements HttpTranspo } } + private static Map<TransportListener.TransportPropertyKey, String> createTransportProperties( + CloseableHttpResponse response) { + Map<TransportListener.TransportPropertyKey, String> properties = new HashMap<>(); + properties.put( + HttpTransporter.HttpTransportPropertyKey.HTTP_VERSION, + response.getProtocolVersion().toString()); + return properties; + } + private static Function<String, String> headerGetter(CloseableHttpResponse closeableHttpResponse) { return s -> { Header header = closeableHttpResponse.getFirstHeader(s); 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 f5b2b29a4..5b644f52a 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 @@ -77,6 +77,8 @@ import org.eclipse.aether.spi.connector.transport.AbstractTransporter; import org.eclipse.aether.spi.connector.transport.GetTask; import org.eclipse.aether.spi.connector.transport.PeekTask; import org.eclipse.aether.spi.connector.transport.PutTask; +import org.eclipse.aether.spi.connector.transport.TransportListener; +import org.eclipse.aether.spi.connector.transport.TransportListener.TransportPropertyKey; import org.eclipse.aether.spi.connector.transport.TransportTask; import org.eclipse.aether.spi.connector.transport.http.ChecksumExtractor; import org.eclipse.aether.spi.connector.transport.http.HttpTransporter; @@ -370,9 +372,11 @@ final class JdkTransporter extends AbstractTransporter implements HttpTransporte final boolean downloadResumed = offset > 0L; final Path dataFile = task.getDataPath(); + final Map<TransportListener.TransportPropertyKey, String> transportProperties = + createTransportProperties(response); if (dataFile == null) { try (InputStream is = response.body()) { - utilGet(task, is, true, length, downloadResumed); + utilGet(task, is, true, length, downloadResumed, transportProperties); } } else { try (PathProcessor.CollocatedTempFile tempFile = pathProcessor.newTempFile(dataFile)) { @@ -383,7 +387,7 @@ final class JdkTransporter extends AbstractTransporter implements HttpTransporte } } try (InputStream is = response.body()) { - utilGet(task, is, true, length, downloadResumed); + utilGet(task, is, true, length, downloadResumed, transportProperties); } tempFile.move(); } finally { @@ -415,6 +419,21 @@ final class JdkTransporter extends AbstractTransporter implements HttpTransporte } } + private Map<TransportPropertyKey, String> createTransportProperties(HttpResponse<?> response) { + Map<TransportPropertyKey, String> props = new HashMap<>(); + props.put(HttpTransportPropertyKey.HTTP_VERSION, response.version().toString()); + response.sslSession().ifPresent(ssl -> { + props.put( + HttpTransportPropertyKey.SSL_PROTOCOL, + response.sslSession().get().getProtocol()); + props.put( + HttpTransportPropertyKey.SSL_CIPHER_SUITE, + response.sslSession().get().getCipherSuite()); + }); + // TODO: add compression algorithm if any (https://github.com/mizosoft/methanol/issues/182) + return props; + } + private static Function<String, String> headerGetter(HttpResponse<?> response) { return s -> response.headers().firstValue(s).orElse(null); } 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 3a90132a7..abf19a6e5 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 @@ -47,6 +47,8 @@ import org.eclipse.aether.spi.connector.transport.AbstractTransporter; import org.eclipse.aether.spi.connector.transport.GetTask; import org.eclipse.aether.spi.connector.transport.PeekTask; import org.eclipse.aether.spi.connector.transport.PutTask; +import org.eclipse.aether.spi.connector.transport.TransportListener; +import org.eclipse.aether.spi.connector.transport.TransportListener.TransportPropertyKey; import org.eclipse.aether.spi.connector.transport.TransportTask; import org.eclipse.aether.spi.connector.transport.http.ChecksumExtractor; import org.eclipse.aether.spi.connector.transport.http.HttpTransporter; @@ -68,6 +70,7 @@ 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.io.ClientConnector; +import org.eclipse.jetty.io.EndPoint.SslSessionData; import org.eclipse.jetty.util.ssl.SslContextFactory; import static org.eclipse.aether.spi.connector.transport.http.HttpConstants.ACCEPT_ENCODING; @@ -246,6 +249,7 @@ final class JettyTransporter extends AbstractTransporter implements HttpTranspor boolean resume = task.getResumeOffset() > 0L && task.getDataPath() != null; Response response; InputStreamResponseListener listener; + Map<TransportListener.TransportPropertyKey, String> transportProperties; while (true) { Request request = client.newRequest(resolve(task)).method("GET"); @@ -287,6 +291,7 @@ final class JettyTransporter extends AbstractTransporter implements HttpTranspor throw new HttpTransporterException(statusCode); }); } + transportProperties = createTransportProperties(request, response); break; } @@ -311,7 +316,7 @@ final class JettyTransporter extends AbstractTransporter implements HttpTranspor final Path dataFile = task.getDataPath(); if (dataFile == null) { try (InputStream is = listener.getInputStream()) { - utilGet(task, is, true, length, downloadResumed); + utilGet(task, is, true, length, downloadResumed, transportProperties); } } else { try (PathProcessor.CollocatedTempFile tempFile = pathProcessor.newTempFile(dataFile)) { @@ -322,7 +327,7 @@ final class JettyTransporter extends AbstractTransporter implements HttpTranspor } } try (InputStream is = listener.getInputStream()) { - utilGet(task, is, true, length, downloadResumed); + utilGet(task, is, true, length, downloadResumed, transportProperties); } tempFile.move(); } finally { @@ -342,6 +347,21 @@ final class JettyTransporter extends AbstractTransporter implements HttpTranspor } } + private Map<TransportPropertyKey, String> createTransportProperties(Request request, Response response) { + SslSessionData sslSessionData = request.getConnection().getSslSessionData(); + Map<TransportPropertyKey, String> properties = new HashMap<>(); + properties.put(HttpTransportPropertyKey.HTTP_VERSION, response.getVersion().toString()); + if (sslSessionData != null && sslSessionData.sslSession() != null) { + properties.put( + HttpTransportPropertyKey.SSL_PROTOCOL, + sslSessionData.sslSession().getProtocol()); + properties.put( + HttpTransportPropertyKey.SSL_CIPHER_SUITE, + sslSessionData.sslSession().getCipherSuite()); + } + return properties; + } + private static Function<String, String> headerGetter(Response response) { return s -> response.getHeaders().get(s); }
